Best practices for loggers

Just use Log4j 2 and don’t bother with the class name any longer.

import org.apache.logging.log4j.LogManager

class MyClass {

    companion object {
         private val logger = LogManager.getLogger()
    }
}

The getLogger method (without parameters) creates a logger with the name of the calling class.

1 Like

I came to this thread looking for how to approach logging with SLF4J in Kotlin. After a quick review and trying it out, Kotlin-Logging is actually very simple and nice. It’s nice enough that I’m going to re-plug it since oshai’s comment got a little buried in the middle.
Kotlin-Logging: GitHub - oshai/kotlin-logging: Lightweight Multiplatform logging framework for Kotlin. A convenient and performant logging facade.
Kotlin Logging without the Fuss: Kotlin Logging Without the Fuss · RealJenius.com

1 Like

Hm, I feel that something is wrong with logging and especially kotlin-logging:

[kotlin-logging is a] logging library wrapping slf4j with Kotlin extensions.

So, you would have

  • a wrapper (kotlin-logging) for
  • a facade (slf4j) which, in the case of Log4j 2, would use
  • Log4j API which in turn would use
  • Log4j implementation.

And all that without any real benefit over using Log4j API directly. For the case that you are not familiar with Log4j 2 API – it is the equivalent to slf4j, but more modern (uses lambdas for example). Other frameworks could adapt this API as well. See Go ahead: program to the log4j2 API instead of slf4j

1 Like

I started out trying to use kotlin-logging but found its multiplatform support lacking. There was a forked lib named KLogging which had multiplatform implemented, but I didn’t like the classname prefixes it was generating in the logs.

So, since the world doesn’t have enough of these, I created yet another logging framework wrapper: kmulti-logging. The API is targeted to my typical use case: creating a single, static logger instance per class or top-level file.

2 Likes

kotlin-logging now has multiplatform support: Multiplatform support · MicroUtils/kotlin-logging Wiki · GitHub

3 Likes
val logster: ReadOnlyProperty<Any, Logger> get() = LoggerDelegate()

class LoggerDelegate : ReadOnlyProperty<Any, Logger> {
    lateinit var logger: Logger

    override fun getValue(thisRef: Any, property: KProperty<*>): Logger {
        if (!::logger.isInitialized) logger = LoggerFactory.getLogger(thisRef.javaClass)
        return logger
    }

}

then
val logger by logster

2 Likes

The best I could do was:

// property delegate for Log4J2 loggers

private fun logster(): ReadOnlyProperty<Any?, Logger> =
    object : ReadOnlyProperty<Any?, Logger> {
        lateinit var logger: Logger
        override fun getValue(thisRef: Any?, property: KProperty<*>): Logger {
            if (!::logger.isInitialized) logger =
                LogManager.getLogger(if (thisRef == null) LogManager.getLogger() else thisRef.javaClass.packageName)
            return logger
        }
    }

// then later

val logger by logster()

If I tried to use Any instead of Any? as the type of thisRef in the delegate, then Kotlin 1.4.30 whines that the delegate doesn’t have a getter that deals with a Nothing?

1 Like

I personally grew a fond of the approach documentede here: Logging in Kotlin - the right approach - Adrian Marszalek

class MyApp {
    val logger by loggerFactory()
}

fun <R : Any> R.loggerFactory(): Lazy<Logger> {
    return lazy { LoggerFactory.getLogger(getClassName(this.javaClass)) }
}

fun <T : Any> getClassName(clazz: Class<T>): String {
    return clazz.name.removeSuffix("\$Companion")
}
1 Like