Simple and robust Kotlin Multiplatform logging.
Add the core Khronicle dependency in your Gradle configuration:
implementation("com.juul.khronicle:khronicle-core:$version")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.
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:
verbosedebuginfowarnerrorassert
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 Loggers 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 Loggers 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 Loggers 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."
}A basic Ktor Logger is available via the com.juul.khronicle:khronicle-ktor-client:$version artifact.
To route a Ktor HttpClient instance's logs to Khronicle for logging, simply set
KhronicleKtorClientLogger as the HttpClient's logger:
HttpClient {
install(Logging) {
logger = KhronicleKtorClientLogger()
}
}This will cause the HttpClient's logs to be sent to installed Khronicle log dispatchers
(that were installed via Log.dispatcher.install).