Kotlin Coroutine Multiplatform Logging Library
-
💪 Powerful - Heavily inspired by Log4J2, Lumberjack enables powerful logging API concepts such as MDC (Mapped Diagnostic Context), markers, custom levels, and an abstract
Message
API to be utilized incommon
and non-JVM platforms. -
🧰 Configurable - For JVM targets, Lumberjack delegates to Log4J2 API which enables extensive customization in runtime implementation including delegating to SLF4J or, if using
log4j-core
, powerful configuration options.For non-JVM targets, Lumberjack delegates to Sawtooth; an in-house logging engine and configuration API. Sawtooth configurations can be changed during runtime and its abstract APIs allow potentially infinite customization.
-
🚀 Efficient - By reusing buffers and objects, utilizing caches, and inlining functions; Lumberjack operates with minimal memory overhead and contributes little to no additional pressure for the garbage collector.
-
🌌 Universal - Lumberjack supports all Kotlin Multiplatform targets which makes it perfect for any Kotlin library or application for any platform.
Lumberjack can be installed like any other Kotlin library via Gradle. For all proceeding examples, $VERSION
should be replaced with the latest available version on Bintray. While it is not recommended to manually install dependencies, raw artifacts can also be found on Bintray.
repositories {
jcenter()
}
dependencies {
// Replace $PLATFORM with jvm, js, linuxx64, macosx64, mingwx64, etc.
implementation("dev.neontech.lumberjack:lumberjack-$PLATFORM:$VERSION")
}
For Kotlin Mutliplatform projects, Lumberjack provides Gradle Metadata to resolve individual platforms automatically.
repositories {
jcenter()
}
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
// Implementations for jvm, js, etc. automatically resolve
implementation("dev.neontech.lumberjack:lumberjack:$VERSION")
}
}
}
}
Alternatively, implementations may manually be resolved with an explicit -common
declaration in commonMain
.
repositories {
jcenter()
}
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation("dev.neontech.lumberjack:lumberjack-common:$VERSION")
}
}
val jvmMain by getting {
dependencies {
implementation("dev.neontech.lumberjack:lumberjack-jvm:$VERSION")
}
}
// etc.
}
}
Logger
is the core class of the Lumberjack API and is responsible for logging.
fun myFunction() {
// May optionally provide a Marker, cause (Throwable), and/or CoroutineContext
logger.logc(Level.Info, ThreadLocalMutableObjectMessage.message("Hello World!"))
}
Lumberjack provides many extension functions to automatically apply a Level
and, lazily, construct a Message
from any arbitrary object.
fun myFunction() {
logger.infoc { "Hello World!" }
}
Lumberjack enables users to embed additional context in log outputs via MDC
. For non-suspend
functions, the context must be manually provided via a CoroutineContext
instance; hence the c
suffix. For suspend
functions, the CoroutineContext
can be automatically provided by removing the c
suffix.
suspend fun myFunction() {
logger.info { "Hello World" }
}
Loggers can be instantiated with the Logger.fromName
or the Logger.fromKClass
factory functions.
Messages can act as wrappers around objects so that the user can have control over converting objects to String instances without requiring complicated formatters. The conversion is lazy and may never be executed (for example, insufficient logging level) which prevents unnecessary allocation of garbage.
Levels enable users to categorize log statements by urgency. This categorization allows filtering persistent logs, controlling the amount of information logged, or a combination of both and additional factors.
Lumberjack provides the following standard logging levels; accessible via Level
subclasses:
Object | Name | Value |
---|---|---|
None |
OFF | 0 |
Fatal |
FATAL | 100 |
Error |
ERROR | 200 |
Warn |
WARN | 300 |
Info |
INFO | 400 |
Debug |
DEBUG | 500 |
Trace |
TRACE | 600 |
All |
ALL | Int.MAX_VALUE |
Lumberjack additionally supports custom logging levels. Invoking the Level.toLevel
factory function will instantiate a Level.Custom
instance, or return an existing Level
if previously instantiated. Level.fromName
is also provided to only return previously instantiated Level
instances, or otherwise return the provided defaultLevel
or Level.Debug
.
Markers can be used to color or mark a single log statement. Markers can be instantiated with the Marker.fromName
factory function; optionally, with related parent instances.
val NOTIFY: Marker = Marker.fromName("NOTIFY")
val NOTIFY_ADMIN: Marker = Marker.fromName("NOTIFY_ADMIN", NOTIFY)
Mapped Diagnostic Context enables users to embed additional context in log outputs without exposing abstractions to unnecessary information. For example, a rate limiter would log when a rate limit has been triggered, but, assuming an abstracted rate limiter API, it would not be possible to log what triggered the rate limit; especially in highly concurrent applications.
Lumberjack uses CoroutineContext
instances to pass context around in highly concurrent applications. MDC
is a special CoroutineContext
that Lumberjack will automatically utilize and provides an interface for storing generic information as a Map<String, String>
.
Calling the mdc
top-level function will return the current MDC
of any coroutine. An optional function can be provided to add or remove key/value pairs to instantiate a new MDC
for new coroutines.
suspend fun myFunction() {
val context = mdc { it + ("key" to "value") }
withContext(context) {
logger.info { "Hello World" }
}
}
The MDC.fromContext
factory function instantiates a new MDC
from non-suspend
functions. This function discards previous contexts and is not recommended.
fun myFunction() {
val context = MDC.fromContext(mapOf("key" to "value"))
logger.infoc(context = context) { "Hello World" }
}