Simple and robust Kotlin Multiplatform logging.
Logging can be initialized via install
:
Log.dispatcher.install(ConsoleLogger)
If no Logger
is installed, then log blocks are not called at runtime.
Custom loggers can be created by implementing the Logger
interface.
Because Kotlin Multiplatform does not yet nicely support Android and JVM as a hierarchical dependency, Logcat logging is provided as a separate artifact.
implementation("com.juul.khronicle:khronicle-android:$version")
After adding the Gradle dependency, it can be installed as follows.
Log.dispatcher.install(AndroidLogger)
Log to the Apple System Log by installing the AppleSystemLogger
.
Log.dispatcher.install(AppleSystemLogger)
Log message can be logged via:
Log.verbose { "Example" }
The following [log level] functions are available:
verbose
debug
info
warn
error
assert
Optional tag
and throwable
may be provided. tag
defaults to an autogenerated value, but behavior can be customized
via Log.tagGenerator
property.
Khronicle supports associating arbitrary metadata to each log. A WriteMetadata
instance is passed to each of
the logging functions above.
Log.verbose { metadata ->
metadata[Sensitivity] = Sensitivity.Sensitive
"My social security number is 123 45 6789"
}
This can be read inside of a Logger
via the ReadMetadata
passed into it.
class SampleLogger : Logger {
override fun verbose(tag: String, message: String, metadata: ReadMetadata, throwable: Throwable?) {
if (metadata[Sensitivity] != Sensitivity.Sensitive) {
// report to a destination that cannot include sensitive data
}
}
// ...
}
To create your own metadata fields, use an object
that implements the Key
interface. The value of the generic
controls the type of the value in the metadata map.
object YourMetadata : Key<String>
A helpful pattern for many types is using the companion object
as the metadata key.
enum class Sample {
A, B, C;
companion object : Key<Sample>
}
Khronicle Logger
s may filter logs directly in their implementation, or may have filtering added after the fact via decorators.
This decorator pattern is especially useful when filtering pre-existing Logger
s such as the built-in ConsoleLogger
.
Log level filters are installed with Logger.withMinimumLogLevel
. Because the filtering is based on which log call is
made, instead of the content of the log call, these can be used as an optimization: if all Logger
s installed in the
root DispatchLogger
have a minimum log level higher than the log call being made, then the log block is never called.
Log.dispatcher.install(
ConsoleLogger
.withMinimumLogLevel(LogLevel.Warn)
)
Log.debug { "This is not called." }
Log.warn { "This still gets called." }
Log content filters are installed with Logger.withFilter
, and have full access to the content of a log.
Log.dispatcher.install(
ConsoleLogger
.withFilter { tag, message, metadata, throwable ->
metadata[Sensitivity] == Sensitivity.NotSensitive
}
)
Log.debug { "This block is evaluated, but does not get printed to the console." }
Log.warn { metadata ->
metadata[Sensitivity] = Sensitivity.NotSensitive
"This is also evaluated, and does print to the console."
}