Simple trick for getting a logger


#1

I have seen all sorts of recommendations for obtaining a static logger without having to specify the class name or for obtaining a logger from the top level. The problem stems from not being able to get the current class from the top level and not wanting the static logger to have the name of the companion object.

With this simple top level function you can get loggers with the desired name for instance, static, and top level loggers.

inline fun getLogger(): Logger {
    return LoggerFactory.getLogger(MethodHandles.lookup().lookupClass())
}

Then use like this:

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

MethodHandles.lookup().lookupClass() gets the current class and because it is inlined it gets the calling class, but requires Java 7. This example is for slf4j and I have created a small library which handles this and lazy logging at https://github.com/kxtra/kxtra-slf4j


#2

Does it work under the native compiler?

Edit: OK, I see it depends on a Java library.


#3

Seems like it’d be an expensive way to get a handle to a logger. You’d still use something like this to save an instance in your class I hope, right?


#4

Yes you should definitely call it once and keep the instance around in your companion object. But for that one call it also is very fast although the method names sound expensive.


#5

Inline function is NOT macro.

Maybe you need to redesign and refactor your code.
A logger must be associated with an instance, not a class.
If you want to bind a logger to a singleton, you need to declare it as object and use INSTANCE as its only instance.

As for a simple logger getter, you can use a inline-reified function:

inline val <reified T> T.logger
    get() = LoggerFactory.getLogger(T::class.java)

Then use it like

class MyClass {
    private val instanceLogger = MyClass().logger
}

or

class MyClass {
     companion object {
        private val staticLogger = MyClass().logger
    }
}

or

object MyObject {
    private val singletonLogger = INSTANCE.logger
}

#6

Another trick I used. Not sure if it’s faster or not. I didn’t store logger instance anywhere, in my benchmarks logback is very fast so it doesn’t make sense to store it IMO.

private fun getFunctionLogger(anchorFunction: () -> String): Logger {
  fun getLoggerName(): String {
    val anchorClassName = anchorFunction.javaClass.name
    // anchorClassName: my.pckg.FileKt$func$1, we should return my.pckg.FileKt.func
    val lastDollarIndex = anchorClassName.lastIndexOf('$')
    if (lastDollarIndex == -1 ) return anchorClassName
    val prevLastDollarIndex = anchorClassName.lastIndexOf('$', startIndex = lastDollarIndex - 1)
    if (prevLastDollarIndex == -1) return anchorClassName
    return anchorClassName.substring(0, prevLastDollarIndex) + '.' +
        anchorClassName.substring(prevLastDollarIndex + 1, lastDollarIndex)
  }
  return LoggerFactory.getLogger(getLoggerName())
}

fun logTrace(message: () -> String) {
  val logger = getFunctionLogger(message)
  if (logger.isTraceEnabled) logger.trace(message())
}

Basically when you’re using logTrace, you passing lambda which is instance of anonymous Java class and its name could be used to create logger name.