Simple Logging Facade for Kotlin is a set of Kotlin extension for SLF4J.
- Use kotlin features to cleanly get the appropriate logger for your class
- Clean up kotlin class names to make logging easy to understand
- Typed logger delegates: a logger instantiated using a class reference is only valid in that class
- Lazy logger instantiation & logger cache
You can include SLF4K in your project by adding the following:
<dependency>
<groupId>ca.solo-studios</groupId>
<artifactId>slf4k</artifactId>
<version>[slf4j version]</version>
</dependency>
implementation 'ca.solo-studios:slf4k:[slf4j version]'
implementation("ca.solo-studios:slf4k:[slf4j version]")
The current version of SLF4K should be compatible with all versions that are greater than 2.0.0. However, it should still work with versions lower than 2.0.0, so long as you don't use the Fluent Logging extensions.
How to get a logger:
import org.slf4j.kotlin.toplevel.getLogger
val logger by getLogger()
import org.slf4j.kotlin.getLogger
class MyClass {
private val logger by getLogger() // or getLogger(MyClass::class)
}
val logger by getLogger("name of logger here")
SLF4K uses lazy log message evaluation to avoid expensive logging messages when they're not needed.
When you write the following:
logger.info { "expensive message: $someVariable" }
this code gets transformed at compiled time and inlined into:
if (logger.info)
logger.info("expensive message: $someVariable")
This way, the expensive message is only evaluated if the info log level is enabled.
Extensions have also been provided for every combination of level and argument. So, you can log errors as needed:
logger.warn(exception) { "message" }
Markers can also be really easily instantiated, as follows:
val myMarker = getMarker("MY_MARKER")
after which, they can be used normally.
The fluent logging api introduced in version 2.0.0 is also supported by SLF4K, here are some examples of how it can be used:
logger.atInfo {
message = "my message that uses string interpolation. Here is someArgument: $someArgument"
}
// equivalent to
logger.info { "my message that uses string interpolation. Here is someArgument: $someArgument" }
Arguments:
logger.atInfo {
message = "my message here. someArgument: {} someOtherArgument: {}"
arguments = listOf(someArgument, someOtherArgument)
}
// equivalent to
logger.info { "here is someArgument: $someArgument and here is someOtherArgument: $someOtherArgument" }
Key-values:
logger.atInfo {
message = "my message here."
keyValues = listOf("someArgument" to someArgument, "someOtherArgument" to someOtherArgument)
}
// equivalent to
logger.info { "someArgument=$someArgument someOtherArgument=$someOtherArgument my message here." }
Markers:
logger.atInfo {
message = "my message here."
// You can also provide several markers in the list
markers = listOf(myMarker)
}
// equivalent to
logger.info(myMarker) { "my message here." }
Errors:
try {
// do something that might thrown an exception
} catch (e: RuntimeException) {
logger.atError {
cause = e
message = "my error message here"
}
// equivalent to
logger.error(e) { "my error message here" }
}
To pass MDC context
between kotlinx coroutines, use
kotlinx-coroutines-slf4j
:
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-slf4j</artifactId>
<version>$kotlinxCoroutinesVersion</version>
</dependency>
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-slf4j:$kotlinxCoroutinesVersion'
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-slf4j:$kotlinxCoroutinesVersion")
You can propagate the MDC context through coroutines as follows:
MDC.put("kotlin", "rocks") // put a value into the MDC context
launch(MDCContext()) {
logger.info { "..." } // the MDC context will contain the mapping here
}