Skip to content

JuulLabs/khronicle

Repository files navigation

Slack codecov

Khronicle

Simple and robust Kotlin Multiplatform logging.

Setup (Gradle)

Add the core Khronicle dependency in your Gradle configuration:

implementation("com.juul.khronicle:khronicle-core:$version")

Initialization

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.

Apple (NSLog)

Log to the Apple System Log by installing the AppleSystemLogger.

Log.dispatcher.install(AppleSystemLogger)

Log

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.

Metadata

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>
}

Filtering

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

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

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."
}

Ktor

A basic KtorLogger is available via the com.juul.khronicle:khronicle-ktor-client:$version artifact:

HttpClient {
    install(Logging) {
        logger = KhronicleKtorClientLogger()
    }
}