(patch) work in progress
This commit is contained in:
parent
7917cdf4dc
commit
6999f02da0
12 changed files with 278 additions and 27 deletions
|
@ -12,6 +12,6 @@ repos:
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
- repo: https://git.garrity.co/garrity-software/gs-pre-commit-scala
|
- repo: https://git.garrity.co/garrity-software/gs-pre-commit-scala
|
||||||
rev: v1.0.0
|
rev: v1.0.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: scalafmt
|
- id: scalafmt
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// See: https://github.com/scalameta/scalafmt/tags for the latest tags.
|
// See: https://github.com/scalameta/scalafmt/tags for the latest tags.
|
||||||
version = 3.7.17
|
version = 3.8.1
|
||||||
runner.dialect = scala3
|
runner.dialect = scala3
|
||||||
maxColumn = 80
|
maxColumn = 80
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ val sharedSettings = Seq(
|
||||||
|
|
||||||
lazy val testSettings = Seq(
|
lazy val testSettings = Seq(
|
||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
"org.scalameta" %% "munit" % "1.0.0-M10" % Test
|
"org.scalameta" %% "munit" % "1.0.0-M12" % Test
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -43,6 +43,6 @@ lazy val api = project
|
||||||
.settings(name := s"${gsProjectName.value}-api-v${semVerMajor.value}")
|
.settings(name := s"${gsProjectName.value}-api-v${semVerMajor.value}")
|
||||||
.settings(
|
.settings(
|
||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
"gs" %% "gs-uuid-v0" % "0.2.2"
|
"gs" %% "gs-uuid-v0" % "0.2.3"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
57
modules/api/src/main/scala/gs/log/v0/DataRenderer.scala
Normal file
57
modules/api/src/main/scala/gs/log/v0/DataRenderer.scala
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package gs.log.v0
|
||||||
|
|
||||||
|
import scala.reflect.ClassTag
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
trait DataRenderer:
|
||||||
|
def render[A](data: A)(using CT: ClassTag[A]): String
|
||||||
|
|
||||||
|
object DataRenderer:
|
||||||
|
|
||||||
|
abstract class SingleTypeRenderer[A]:
|
||||||
|
def render(data: A): String
|
||||||
|
|
||||||
|
object Defaults:
|
||||||
|
|
||||||
|
final class StringRenderer extends SingleTypeRenderer[String]:
|
||||||
|
override def render(data: String): String = data
|
||||||
|
|
||||||
|
final class BooleanRenderer extends SingleTypeRenderer[Boolean]:
|
||||||
|
override def render(data: Boolean): String = data.toString()
|
||||||
|
|
||||||
|
final class IntRenderer extends SingleTypeRenderer[Int]:
|
||||||
|
override def render(data: Int): String = data.toString()
|
||||||
|
|
||||||
|
final class LongRenderer extends SingleTypeRenderer[Long]:
|
||||||
|
override def render(data: Long): String = data.toString()
|
||||||
|
|
||||||
|
final class FloatRenderer extends SingleTypeRenderer[Float]:
|
||||||
|
override def render(data: Float): String = data.toString()
|
||||||
|
|
||||||
|
final class DoubleRenderer extends SingleTypeRenderer[Double]:
|
||||||
|
override def render(data: Double): String = data.toString()
|
||||||
|
|
||||||
|
final class DateRenderer extends SingleTypeRenderer[LocalDate]:
|
||||||
|
override def render(data: LocalDate): String = data.toString()
|
||||||
|
|
||||||
|
final class InstantRenderer extends SingleTypeRenderer[Instant]:
|
||||||
|
override def render(data: Instant): String = data.toString()
|
||||||
|
|
||||||
|
end Defaults
|
||||||
|
|
||||||
|
class RegistryRenderer(
|
||||||
|
private val registry: Map[Class[?], SingleTypeRenderer[?]]
|
||||||
|
):
|
||||||
|
|
||||||
|
def render[A](data: A)(using CT: ClassTag[A]): String =
|
||||||
|
getRendererFor[A].render(data)
|
||||||
|
|
||||||
|
private def getRendererFor[A](using CT: ClassTag[A]): SingleTypeRenderer[A] =
|
||||||
|
Option(registry.get(CT.runtimeClass)) match
|
||||||
|
case Some(r) => r.asInstanceOf[SingleTypeRenderer[A]]
|
||||||
|
case None => throw new IllegalArgumentException("No renderer in registry!")
|
||||||
|
|
||||||
|
end RegistryRenderer
|
||||||
|
|
||||||
|
end DataRenderer
|
|
@ -19,24 +19,55 @@ final class Log(
|
||||||
private var internalMessage: Option[LogMessage] = None,
|
private var internalMessage: Option[LogMessage] = None,
|
||||||
private var internalException: Option[Throwable] = None
|
private var internalException: Option[Throwable] = None
|
||||||
):
|
):
|
||||||
|
/**
|
||||||
|
* Set the value for a single key.
|
||||||
|
*
|
||||||
|
* @param key The key to set.
|
||||||
|
* @param value The safe value for the key.
|
||||||
|
* @return This [[Log]].
|
||||||
|
*/
|
||||||
def data(key: LogData.Key, value: LogData.Safe): Log =
|
def data(key: LogData.Key, value: LogData.Safe): Log =
|
||||||
val _ = this.internalData.put(key, value)
|
val _ = this.internalData.put(key, value)
|
||||||
this
|
this
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a list of key/value pairs.
|
||||||
|
*
|
||||||
|
* @param kvp The list of key/value pairs.
|
||||||
|
* @return This [[Log]].
|
||||||
|
*/
|
||||||
def data(kvp: (LogData.Key, LogData.Safe)*): Log =
|
def data(kvp: (LogData.Key, LogData.Safe)*): Log =
|
||||||
val _ = kvp.foreach {
|
val _ = kvp.foreach {
|
||||||
case (k, v) => val _ = this.internalData.put(k, v)
|
case (k, v) => val _ = this.internalData.put(k, v)
|
||||||
}
|
}
|
||||||
this
|
this
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the current list of tags with a new list of tags.
|
||||||
|
*
|
||||||
|
* @param newTags The new list of tags for this log.
|
||||||
|
* @return This [[Log]].
|
||||||
|
*/
|
||||||
def tagged(newTags: String*): Log =
|
def tagged(newTags: String*): Log =
|
||||||
this.internalTags = newTags.toList
|
this.internalTags = newTags.toList
|
||||||
this
|
this
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the current message with a new message.
|
||||||
|
*
|
||||||
|
* @param newMessage The new message.
|
||||||
|
* @return This [[Log]].
|
||||||
|
*/
|
||||||
def message(newMessage: LogMessage): Log =
|
def message(newMessage: LogMessage): Log =
|
||||||
this.internalMessage = Some(newMessage)
|
this.internalMessage = Some(newMessage)
|
||||||
this
|
this
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the current exception with a new exception.
|
||||||
|
*
|
||||||
|
* @param ex The new exception.
|
||||||
|
* @return This [[Log]].
|
||||||
|
*/
|
||||||
def exception(ex: Throwable): Log =
|
def exception(ex: Throwable): Log =
|
||||||
this.internalException = Some(ex)
|
this.internalException = Some(ex)
|
||||||
this
|
this
|
||||||
|
@ -49,19 +80,62 @@ final class Log(
|
||||||
|
|
||||||
def getException: Option[Throwable] = internalException
|
def getException: Option[Throwable] = internalException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if _any_ of the fields on this [[Log]] are set. Logs that are
|
||||||
|
* empty are always discarded.
|
||||||
|
*
|
||||||
|
* @return Whether or not this [[Log]] is empty.
|
||||||
|
*/
|
||||||
|
def isEmpty: Boolean =
|
||||||
|
internalData.isEmpty
|
||||||
|
&& internalTags.isEmpty
|
||||||
|
&& internalMessage.isEmpty
|
||||||
|
&& internalException.isEmpty
|
||||||
|
|
||||||
object Log:
|
object Log:
|
||||||
|
/**
|
||||||
|
* Instantiate a new log with the given key/value pair.
|
||||||
|
*
|
||||||
|
* @param key The key.
|
||||||
|
* @param value The value.
|
||||||
|
* @return New [[Log]] instance.
|
||||||
|
*/
|
||||||
def data(key: LogData.Key, value: LogData.Safe): Log =
|
def data(key: LogData.Key, value: LogData.Safe): Log =
|
||||||
new Log().data(key, value)
|
new Log().data(key, value)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate a new log with the given list of key/value pairs.
|
||||||
|
*
|
||||||
|
* @param kvp The list of key/value pairs.
|
||||||
|
* @return New [[Log]] instance.
|
||||||
|
*/
|
||||||
def data(kvp: (LogData.Key, LogData.Safe)*): Log =
|
def data(kvp: (LogData.Key, LogData.Safe)*): Log =
|
||||||
new Log().data(kvp*)
|
new Log().data(kvp*)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate a new log with the given list of tags.
|
||||||
|
*
|
||||||
|
* @param tags The list of tags.
|
||||||
|
* @return New [[Log]] instance.
|
||||||
|
*/
|
||||||
def tagged(tags: String*): Log =
|
def tagged(tags: String*): Log =
|
||||||
new Log().tagged(tags*)
|
new Log().tagged(tags*)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate a new log with the given message.
|
||||||
|
*
|
||||||
|
* @param tags The message.
|
||||||
|
* @return New [[Log]] instance.
|
||||||
|
*/
|
||||||
def message(msg: LogMessage): Log =
|
def message(msg: LogMessage): Log =
|
||||||
new Log().message(msg)
|
new Log().message(msg)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate a new log with the given exception.
|
||||||
|
*
|
||||||
|
* @param tags The exception.
|
||||||
|
* @return New [[Log]] instance.
|
||||||
|
*/
|
||||||
def exception(ex: Throwable): Log =
|
def exception(ex: Throwable): Log =
|
||||||
new Log().exception(ex)
|
new Log().exception(ex)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package gs.log.v0
|
package gs.log.v0
|
||||||
|
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
object LogData:
|
object LogData:
|
||||||
|
|
||||||
/** Opaque type (String) that represents a key that identifies some piece of
|
/** Opaque type (String) that represents a key that identifies some piece of
|
||||||
|
@ -39,31 +41,34 @@ object LogData:
|
||||||
* The data to log.
|
* The data to log.
|
||||||
*/
|
*/
|
||||||
def data: A
|
def data: A
|
||||||
def render(): String
|
|
||||||
|
|
||||||
case class Str(data: String) extends Value:
|
case class Str(data: String) extends Value:
|
||||||
override type A = String
|
override type A = String
|
||||||
override def render(): String = data.toString()
|
|
||||||
|
|
||||||
case class Bool(data: Boolean) extends Value:
|
case class Bool(data: Boolean) extends Value:
|
||||||
override type A = Boolean
|
override type A = Boolean
|
||||||
override def render(): String = data.toString()
|
|
||||||
|
|
||||||
case class Int32(data: Int) extends Value:
|
case class Int32(data: Int) extends Value:
|
||||||
override type A = Int
|
override type A = Int
|
||||||
override def render(): String = data.toString()
|
|
||||||
|
|
||||||
case class Int64(data: Long) extends Value:
|
case class Int64(data: Long) extends Value:
|
||||||
override type A = Long
|
override type A = Long
|
||||||
override def render(): String = data.toString()
|
|
||||||
|
|
||||||
case class Float32(data: Float) extends Value:
|
case class Float32(data: Float) extends Value:
|
||||||
override type A = Float
|
override type A = Float
|
||||||
override def render(): String = data.toString()
|
|
||||||
|
|
||||||
case class Float64(data: Double) extends Value:
|
case class Float64(data: Double) extends Value:
|
||||||
override type A = Double
|
override type A = Double
|
||||||
override def render(): String = data.toString()
|
|
||||||
|
case class Date(data: LocalDate) extends Value:
|
||||||
|
override type A = LocalDate
|
||||||
|
|
||||||
|
case class Instant(data: java.time.Instant) extends Value:
|
||||||
|
override type A = java.time.Instant
|
||||||
|
|
||||||
|
// TODO: IS THIS APPROPRIATE?
|
||||||
|
case class Generic[AA](data: AA) extends Value:
|
||||||
|
override type A = AA
|
||||||
|
|
||||||
/** Indicates data (in terms of [[Value]]) that has been explicitly marked as
|
/** Indicates data (in terms of [[Value]]) that has been explicitly marked as
|
||||||
* _safe_ to log in some form.
|
* _safe_ to log in some form.
|
||||||
|
@ -157,12 +162,70 @@ object LogData:
|
||||||
|
|
||||||
end RequiresSecureHash
|
end RequiresSecureHash
|
||||||
|
|
||||||
|
/** The specified data is safe to log in clear text. This means that it
|
||||||
|
* will show up in output and be human-readable. Use this for values that
|
||||||
|
* are not sensitive.
|
||||||
|
*
|
||||||
|
* If you have configured a log index where it is considered safe to log
|
||||||
|
* all values (e.g. PHI) in clear text, you still should _NOT_ use this
|
||||||
|
* type! In those cases, still mark the data appropriately
|
||||||
|
* (encrypted/hashed) but use engine configuration to disable those
|
||||||
|
* functions and log in clear text. This practice provides better
|
||||||
|
* auditing and portability.
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* The value that is safe to log in clear text.
|
||||||
|
*/
|
||||||
def logClearText[A: Loggable](data: A): ClearText =
|
def logClearText[A: Loggable](data: A): ClearText =
|
||||||
ClearText(Loggable[A].renderForLogs(data))
|
ClearText(Loggable[A].renderForLogs(data))
|
||||||
|
|
||||||
|
/** The specified data is _not_ safe to log in clear text, and must be
|
||||||
|
* encrypted before being output. In this use case, the data is valuable
|
||||||
|
* to have available for debugging/investigation, but it is not safe to
|
||||||
|
* include in the clear. Users must _decrypt_ the data before using it.
|
||||||
|
*
|
||||||
|
* Note that `gs-log` does not provide a general decryption tool, nor
|
||||||
|
* should it! It is your responsibility to provide a secure way to
|
||||||
|
* decrypt sensitive information extracted from logs.
|
||||||
|
*
|
||||||
|
* As an alternative, if you have a log index that is certified to store
|
||||||
|
* all types of data in the clear due to other protections, use this type
|
||||||
|
* but turn off encryption in engine configuration.
|
||||||
|
*
|
||||||
|
* This type is not suitable for data that needs to be _searchable_. For
|
||||||
|
* that use case, please refer to [[RequiresSecureHash]].
|
||||||
|
*
|
||||||
|
* Example Algorithm: AES GCM (256-bit)
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* The value to encrypt.
|
||||||
|
*/
|
||||||
def logEncrypted[A: Loggable](data: A): RequiresEncryption =
|
def logEncrypted[A: Loggable](data: A): RequiresEncryption =
|
||||||
RequiresEncryption(Loggable[A].renderForLogs(data))
|
RequiresEncryption(Loggable[A].renderForLogs(data))
|
||||||
|
|
||||||
|
/** The contianed data is _not_ safe to log in clear text, and must be
|
||||||
|
* hashed before being output. In this use case, the data is valuable to
|
||||||
|
* be searched upon to narrow results, but is not safe to include in the
|
||||||
|
* clear. Users must generate a hash using the same parameters to search.
|
||||||
|
*
|
||||||
|
* For example, consider a case where in rare cases certain customers
|
||||||
|
* have isolated issues and searching by first and last name is useful
|
||||||
|
* for narrowing results. In this case, the first and last names are
|
||||||
|
* hashed in the log output -- the user must select a user, hash their
|
||||||
|
* first and last name, and enter those hashes as search criteria.
|
||||||
|
*
|
||||||
|
* As an alternative, if you have a log index that is certified to store
|
||||||
|
* all types of data in the clear due to other protections, use this type
|
||||||
|
* but turn off hashing in engine configuration.
|
||||||
|
*
|
||||||
|
* This type is not suitable for data that needs to be _decrypted_. For
|
||||||
|
* that use case, please refer to [[RequiresEncryption]].
|
||||||
|
*
|
||||||
|
* Example algorithm: HMAC + SHA-256
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* The value to hash.
|
||||||
|
*/
|
||||||
def logHashed[A: Loggable](data: A): RequiresSecureHash =
|
def logHashed[A: Loggable](data: A): RequiresSecureHash =
|
||||||
RequiresSecureHash(Loggable[A].renderForLogs(data))
|
RequiresSecureHash(Loggable[A].renderForLogs(data))
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,8 @@ sealed abstract class LogLevel(
|
||||||
override def compare(that: LogLevel): Int = ordinal - that.ordinal
|
override def compare(that: LogLevel): Int = ordinal - that.ordinal
|
||||||
|
|
||||||
object LogLevel:
|
object LogLevel:
|
||||||
|
given CanEqual[LogLevel, LogLevel] = CanEqual.derived
|
||||||
|
|
||||||
/** Most-detailed log level. This level should (likely) not be turned on most
|
/** Most-detailed log level. This level should (likely) not be turned on most
|
||||||
* of the time. It should be used to provide extremely verbose output.
|
* of the time. It should be used to provide extremely verbose output.
|
||||||
*/
|
*/
|
||||||
|
@ -59,6 +61,4 @@ object LogLevel:
|
||||||
/** Indicates that logging is completely disabled.
|
/** Indicates that logging is completely disabled.
|
||||||
*/
|
*/
|
||||||
case object Off extends LogLevel("off", -1)
|
case object Off extends LogLevel("off", -1)
|
||||||
|
|
||||||
given CanEqual[LogLevel, LogLevel] = CanEqual.derived
|
|
||||||
end LogLevel
|
end LogLevel
|
||||||
|
|
|
@ -27,7 +27,7 @@ object LogMessage:
|
||||||
|
|
||||||
trait Builder:
|
trait Builder:
|
||||||
|
|
||||||
extension (sc: StringContext)
|
extension (sc: StringContext)(using R: DataRenderer)
|
||||||
|
|
||||||
/** String interpolator for instantiating a [[LogMessage]].
|
/** String interpolator for instantiating a [[LogMessage]].
|
||||||
*
|
*
|
||||||
|
@ -38,6 +38,6 @@ object LogMessage:
|
||||||
* The new [[LogMessage]].
|
* The new [[LogMessage]].
|
||||||
*/
|
*/
|
||||||
def log(args: LogData.ClearText*): LogMessage =
|
def log(args: LogData.ClearText*): LogMessage =
|
||||||
sc.s(args.map(_.value.render())*)
|
sc.s(args.map(d => R.render(d.value))*)
|
||||||
|
|
||||||
end LogMessage
|
end LogMessage
|
||||||
|
|
|
@ -6,6 +6,13 @@ import java.time.LocalDate
|
||||||
/** Type class for data that can be logged by the `gs-log` library.
|
/** Type class for data that can be logged by the `gs-log` library.
|
||||||
*/
|
*/
|
||||||
trait Loggable[A]:
|
trait Loggable[A]:
|
||||||
|
/**
|
||||||
|
* Render the given data as some primitive supported by `gs-log`. Note that
|
||||||
|
* this type class does **not** support structured types.
|
||||||
|
*
|
||||||
|
* @param data The data to render.
|
||||||
|
* @return The [[LogData.Value]] housing the output primitive.
|
||||||
|
*/
|
||||||
def renderForLogs(data: A): LogData.Value
|
def renderForLogs(data: A): LogData.Value
|
||||||
|
|
||||||
object Loggable:
|
object Loggable:
|
||||||
|
@ -21,15 +28,7 @@ object Loggable:
|
||||||
given Loggable[Long] = LogData.Int64(_)
|
given Loggable[Long] = LogData.Int64(_)
|
||||||
given Loggable[Float] = LogData.Float32(_)
|
given Loggable[Float] = LogData.Float32(_)
|
||||||
given Loggable[Double] = LogData.Float64(_)
|
given Loggable[Double] = LogData.Float64(_)
|
||||||
|
given Loggable[LocalDate] = LogData.Date(_)
|
||||||
|
given Loggable[Instant] = LogData.Instant(_)
|
||||||
|
|
||||||
/** Default implementation for `java.time.LocalDate`. Uses the default
|
|
||||||
* `toString`, which formats the date as `uuuu-MM-dd` (ISO_LOCAL_DATE).
|
|
||||||
*/
|
|
||||||
def forDate(): Loggable[LocalDate] = data => LogData.Str(data.toString())
|
|
||||||
|
|
||||||
/** Default implementation for `java.time.Instant`. Uses the default
|
|
||||||
* `toString`, which formats the date using ISO_INSTANT. Produces strings
|
|
||||||
* that look like: `2011-12-03T10:15:30Z`
|
|
||||||
*/
|
|
||||||
def forInstant(): Loggable[Instant] = data => LogData.Str(data.toString())
|
|
||||||
end Loggable
|
end Loggable
|
||||||
|
|
|
@ -5,13 +5,65 @@ package gs.log.v0
|
||||||
* users of this library will interact with.
|
* users of this library will interact with.
|
||||||
*/
|
*/
|
||||||
trait Logger[F[_]]:
|
trait Logger[F[_]]:
|
||||||
|
/**
|
||||||
|
* Emit the given [[Log]] at the [[LogLevel.Trace]] level.
|
||||||
|
*
|
||||||
|
* @param log The [[Log]] to emit.
|
||||||
|
* @return Side-effect.
|
||||||
|
*/
|
||||||
def trace(log: => Log): F[Unit]
|
def trace(log: => Log): F[Unit]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit the given [[Log]] at the [[LogLevel.Debug]] level.
|
||||||
|
*
|
||||||
|
* @param log The [[Log]] to emit.
|
||||||
|
* @return Side-effect.
|
||||||
|
*/
|
||||||
def debug(log: => Log): F[Unit]
|
def debug(log: => Log): F[Unit]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit the given [[Log]] at the [[LogLevel.Info]] level.
|
||||||
|
*
|
||||||
|
* @param log The [[Log]] to emit.
|
||||||
|
* @return Side-effect.
|
||||||
|
*/
|
||||||
def info(log: => Log): F[Unit]
|
def info(log: => Log): F[Unit]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit the given [[Log]] at the [[LogLevel.Warn]] level.
|
||||||
|
*
|
||||||
|
* @param log The [[Log]] to emit.
|
||||||
|
* @return Side-effect.
|
||||||
|
*/
|
||||||
def warn(log: => Log): F[Unit]
|
def warn(log: => Log): F[Unit]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit the given [[Log]] at the [[LogLevel.Error]] level.
|
||||||
|
*
|
||||||
|
* @param log The [[Log]] to emit.
|
||||||
|
* @return Side-effect.
|
||||||
|
*/
|
||||||
def error(log: => Log): F[Unit]
|
def error(log: => Log): F[Unit]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit the given [[Log]] at the [[LogLevel.Fatal]] level.
|
||||||
|
*
|
||||||
|
* @param log The [[Log]] to emit.
|
||||||
|
* @return Side-effect.
|
||||||
|
*/
|
||||||
def fatal(log: => Log): F[Unit]
|
def fatal(log: => Log): F[Unit]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The name of this logger.
|
||||||
|
*/
|
||||||
def name(): Logger.Name
|
def name(): Logger.Name
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether this logger has the given level enabled.
|
||||||
|
*
|
||||||
|
* @param level The [[LogLevel]] to check.
|
||||||
|
* @return True if the level is enabled, false otherwise.
|
||||||
|
*/
|
||||||
def isLevelEnabled(level: LogLevel): Boolean
|
def isLevelEnabled(level: LogLevel): Boolean
|
||||||
|
|
||||||
object Logger:
|
object Logger:
|
||||||
|
@ -24,6 +76,12 @@ object Logger:
|
||||||
|
|
||||||
object Name:
|
object Name:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate a new [[Name]] from the given string.
|
||||||
|
*
|
||||||
|
* @param loggerName The logger's name.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
def apply(loggerName: String): Name = loggerName
|
def apply(loggerName: String): Name = loggerName
|
||||||
|
|
||||||
given CanEqual[Name, Name] = CanEqual.derived
|
given CanEqual[Name, Name] = CanEqual.derived
|
||||||
|
|
|
@ -20,6 +20,6 @@ class LogMessageTests extends munit.FunSuite:
|
||||||
val x = "foo".logClearText()
|
val x = "foo".logClearText()
|
||||||
val y = 10.logClearText()
|
val y = 10.logClearText()
|
||||||
val msg: LogMessage = log"x = $x, y = $y"
|
val msg: LogMessage = log"x = $x, y = $y"
|
||||||
val expected = s"x = ${x.value.render()}, y = ${y.value.render()}"
|
val expected = s"x = ${x.value.renderBytes()}, y = ${y.value.renderBytes()}"
|
||||||
assert(msg.str() == expected)
|
assert(msg.str() == expected)
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,6 @@ externalResolvers := Seq(
|
||||||
"Garrity Software Releases" at "https://maven.garrity.co/gs"
|
"Garrity Software Releases" at "https://maven.garrity.co/gs"
|
||||||
)
|
)
|
||||||
|
|
||||||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.11")
|
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.12")
|
||||||
addSbtPlugin("gs" % "sbt-garrity-software" % "0.3.0")
|
addSbtPlugin("gs" % "sbt-garrity-software" % "0.3.0")
|
||||||
addSbtPlugin("gs" % "sbt-gs-semver" % "0.3.0")
|
addSbtPlugin("gs" % "sbt-gs-semver" % "0.3.0")
|
||||||
|
|
Loading…
Add table
Reference in a new issue