Convenient Kotlin LoggerFactory simplification

2.3k Views Asked by At

What is the most convenient way to use SLF4J or other logging approaches with kotlin?

Usually the developer is busy with boilerplate code like

private val logger: Logger = LoggerFactory.getLogger(this::class.java)

in each and every class to get a proper logger?

What are the most convenient ways to unify/simplify this with Kotlin?

5

There are 5 best solutions below

0
On BEST ANSWER

Here's a simple example which returns a lazily-initialized logger from a bound callable reference or a standard property. I prefer calling from a callable reference because the :: denotes reflection (related to logging).

The class which provides the Lazy<Logger>:

class LoggingProvider<T : Any>(val clazz: KClass<T>) {

  operator fun provideDelegate(inst: Any?, property: KProperty<*>) =
      lazy { LoggerFactory.getLogger(clazz.java) }
}

Inline functions to call them:

inline fun <reified T : Any> KCallable<T>.logger() = 
  LoggingProvider(T::class)

inline fun <reified T : Any> T.logger() = 
  LoggingProvider(T::class)

Here's an example of using them. The require assertion in the initializer shows that the loggers share a reference:

class Foo {

  val self: Foo = this

  val logger by this.logger()
  val callableLogger by this::self.logger()

  init {
    require(logger === callableLogger)
  }

}
4
On

if you don't like the boilerplate, you can always wrap the log.info with your own logger helper:

mylog.info(this, "data that needs to be logged")

Then in the background, have some sort of hashmap that keeps track of classes of the this param that can instantiate a logger for that class.

Other options might be using AspectJ Weaving to weave a logger into each class, but this is overkill in my opinion.

1
On

I have defined a utility method for this

fun getLogger(cl: KClass<*>): Logger {
    return LoggerFactory.getLogger(cl.java)!!
}

and now in each class I can use the logger like this

companion object {
    private val logger = getLogger(MyClass::class)
}
10
On

You can define an extension property on every type:

val <T : Any> T.logger: Logger
    get() = LoggerFactory.getLogger(this::class.java)

use it as follows:

class X {
    init {
        logger.debug("init")
    }
}
0
On

I define this function in my projects to make defining a logger easier for me. It takes advantage of Kotlin's reified types.

// Defined in Utilities.kt
inline fun <reified T:Any> logFor() =
    LoggerFactory.getLogger(T::class.java)

Usage:

class MyClass {
    private val log = logFor<MyClass>()
    ...
 }

Or if you are creating a lot of them:

class MyClass {
    companion object {
        private val log = logFor<MyClass>()
    }
    ...
}