Skip to content

Instantly share code, notes, and snippets.

@maxixcom
Created August 11, 2020 12:00
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save maxixcom/cd454e4b75730c17a515e8db15a4ae78 to your computer and use it in GitHub Desktop.
Save maxixcom/cd454e4b75730c17a515e8db15a4ae78 to your computer and use it in GitHub Desktop.
Kotlin SLF4J loggers in 3 ways

Original Article

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:

  1. 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!")
    }
}
  1. 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!")
    }
}
  1. 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)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment