Skip to main content

Tips

About 2 minJavaKotlinTipsktktskotlincompanion-objectloggerslf4jlog4jlombok

Tips 관련


Kotlin

Slf4j loggers in 3 ways

If you use SLF4Jopen in new window (and possibly Logback) for logging, you are probably familiar with the following code:

val logger = LoggerFactory.getLogger(MyClass::class.java)

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:

1. Factory function

  • Function definition is easy to understand, but usage requires the class name.
  • Gives the correct logger class name in companions.
Usages

@tab:active Code

inline fun <reified T> logger(): Logger {
    return LoggerFactory.getLogger(T::class.java)
}

@tab Usage 1

class LogWithFactoryFunction {
    val logger = logger<LogWithFactoryFunction>()

    fun doSomething() {
        logger.info("Hey from a factory function!")
    }
}

@tab Usage 2

class LogWithCompanionFactoryFunction {
    companion object {
        val logger = logger<LogWithFactoryFunction>()
    }

    fun doSomething() {
        logger.info("Hey from a factory function!")
    }
}

@tab Usage 3

class LogWithFactoryFunction {
    val logger = logger(this)

    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:

@tab Usage 4

class LogWithFactoryFunction {
    val logger = logger()

    fun doSomething() {
        logger.info("Hey from a factory function!")
    }
}

Or even shorter, creating it as an extension function

2. 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
Usages

@tab:active Code

abstract class Log {
    val logger: Logger = LoggerFactory.getLogger(this.javaClass)
}

// or
interface Log {
    fun logger() = LoggerFactory.getLogger(this.javaClass)
}

@tab Usage 1

class LogWithCompanion {
    companion object : Log() {}

    fun doSomething() {
        logger.info("Hey from a companion!")
    }
}

@tab Usage 2

class LogWithInterfaceCompanion {
    companion object : Log {}

    fun doSomething() {
        logger().info("Hey from a companion!")
    }
}

3. Delegate property

  • Harder to understand delegate source code
  • Logger gets $Companion in the logger name if placed in a companion
Usage

@tab:active Code

import org.slf4j.Logger
import org.slf4j.LoggerFactory
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty

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

@tab Usage 1

class LogWithDelegate {
    val logger by LoggerDelegate()

    fun doSomething() {
        logger.info("Hey from a delegate!")
    }
}

😎4. 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)
}

이찬희 (MarkiiimarK)
Never Stop Learning.