8

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?

Martin Mlostek
  • 2,755
  • 1
  • 28
  • 57

5 Answers5

3

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")
    }
}
s1m0nw1
  • 76,759
  • 17
  • 167
  • 196
2

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>()
    }
    ...
}
Todd
  • 30,472
  • 11
  • 81
  • 89
1

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

}
Preston Garno
  • 1,175
  • 10
  • 33
0

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.

Jan Vladimir Mostert
  • 12,380
  • 15
  • 80
  • 137
  • that is what i already tried. But I would even like to kill the redundant call `Log.X(this, ...)`... thinking of inlining like in c/c++ . And in this case I want to stick to the SLF4J approach – Martin Mlostek Jan 02 '18 at 11:23
  • AspectJ can do this for you, but you will need to setup your application to support compile-time AspectJ Weaving. Personally I would rather use the boilerplate than go the AspectJ route. Here's a good introduction: https://blog.jayway.com/2015/09/03/aspectj-and-aop-the-black-magic-of-programming/ – Jan Vladimir Mostert Jan 02 '18 at 11:28
  • What are the reasons you wouldn't go with AspectJ? – Martin Mlostek Jan 02 '18 at 12:29
  • It adds a lot of complexity to your build process (you'll need to add a precompile step that weaves in the Aspects and this might fail sometimes or certain aspects might not weave in due to misconfiguration), you need an IDE that supports AspectJ Weaving, otherwise you will just get red squiggles under everything and you'll need to learn a new "language" in order to write these aspects. Maybe try it out and see how much work it is to setup, it'll just be a once-off thing you have to write. – Jan Vladimir Mostert Jan 02 '18 at 12:36
-2

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)
}
kocka
  • 657
  • 6
  • 14