If you use SLF4J (and possibly Logback) for logging, you are probably familiar with the following code:
val logger = LoggerFactory.getLogger(MyClass::class.java) The kotlin way We like short code, and we like DRY. So here are 3 other ways of getting a logger, to avoid repeating the tedious LoggerFactory stuff:
- Factory function Function definition is easy to understand, but usage requires the class name.
Gives the correct logger class name in companions.
Code:
inline fun <reified T> logger(): Logger {
return LoggerFactory.getLogger(T::class.java)
}
Usage:
class LogWithFactoryFunction {
val logger = logger<LogWithFactoryFunction>()
fun doSomething() {
logger.info("Hey from a factory function!")
}
}
class LogWithCompanionFactoryFunction {
companion object {
val logger = logger<LogWithFactoryFunction>()
}
fun doSomething() {
logger.info("Hey from a factory function!")
}
}
Alternatively, you can help kotlin figure out T to avoid passing it in. However, this would cause Companion to show up again:
Code:
inline fun <reified T> logger(from: T): Logger {
return LoggerFactory.getLogger(T::class.java)
}
Usage:
class LogWithFactoryFunction {
val logger = logger(this)
fun doSomething() {
logger.info("Hey from a factory function!")
}
}
Or even shorter, creating it as an extension function:
Code:
inline fun <reified T> T.logger(): Logger {
return LoggerFactory.getLogger(T::class.java)
}
Usage:
class LogWithFactoryFunction {
val logger = logger()
fun doSomething() {
logger.info("Hey from a factory function!")
}
}
- Companion with inheritance No visible logger property in your code; it's available through the companion object
Logger gets $Companion in the logger name
Interface version asks for a logger each time, causing slf4j to check its initialization state
Code:
abstract class Log {
val logger: Logger = LoggerFactory.getLogger(this.javaClass)
}
or
interface Log {
fun logger() = LoggerFactory.getLogger(this.javaClass)
}
Usage:
class LogWithCompanion {
companion object : Log() {}
fun doSomething() {
logger.info("Hey from a companion!")
}
}
or
class LogWithInterfaceCompanion {
companion object : Log {}
fun doSomething() {
logger().info("Hey from a companion!")
}
}
- Delegate property Harder to understand delegate source code
Logger gets $Companion in the logger name if placed in a companion
Code:
class LoggerDelegate : ReadOnlyProperty<Any?, Logger> {
companion object {
private fun <T>createLogger(clazz: Class<T>) : Logger {
return LoggerFactory.getLogger(clazz)
}
}
private var logger: Logger? = null
override operator fun getValue(thisRef: Any?, property: KProperty<*>): Logger {
if (logger == null) {
logger = createLogger(thisRef!!.javaClass)
}
return logger!!
}
}
Usage:
class LogWithDelegate {
val logger by LoggerDelegate()
fun doSomething() {
logger.info("Hey from a delegate!")
}
}
Opinions? Do you have a different way of doing it? Which version do you prefer, and why?
Thanks for reading. Hope you learned a cool new way of creating loggers! :)
Edit: Added 2 alternative factory functions. Edit2: Added a bonus below.
Bonus: If you have access to the KClass, this is an easy way to get rid of $Companion:
inline fun <reified T> T.logger(): Logger {
if (T::class.isCompanion) {
return LoggerFactory.getLogger(T::class.java.enclosingClass)
}
return LoggerFactory.getLogger(T::class.java)
}