Idiomatic way of logging in Kotlin

后端 未结 16 1473
情歌与酒
情歌与酒 2020-12-07 07:34

Kotlin doesn\'t have the same notion of static fields as used in Java. In Java, the generally accepted way of doing logging is:

public class Foo {
    privat         


        
16条回答
  •  星月不相逢
    2020-12-07 07:46

    KISS: For Java Teams Migrating to Kotlin

    If you don't mind providing the class name on each instantiation of the logger (just like java), you can keep it simple by defining this as a top-level function somewhere in your project:

    import org.slf4j.LoggerFactory
    
    inline fun  logger() = LoggerFactory.getLogger(T::class.java)
    

    This uses a Kotlin reified type parameter.

    Now, you can use this as follows:

    class SomeClass {
      // or within a companion object for one-instance-per-class
      val log = logger()
      ...
    }
    

    This approach is super-simple and close to the java equivalent, but just adds some syntactical sugar.

    Next Step: Extensions or Delegates

    I personally prefer going one step further and using the extensions or delegates approach. This is nicely summarized in @JaysonMinard's answer, but here is the TL;DR for the "Delegate" approach with the log4j2 API (UPDATE: no need to write this code manually any more, as it has been released as an official module of the log4j2 project, see below). Since log4j2, unlike slf4j, supports logging with Supplier's, I've also added a delegate to make using these methods simpler.

    import org.apache.logging.log4j.LogManager
    import org.apache.logging.log4j.Logger
    import org.apache.logging.log4j.util.Supplier
    import kotlin.reflect.companionObject
    
    /**
     * An adapter to allow cleaner syntax when calling a logger with a Kotlin lambda. Otherwise calling the
     * method with a lambda logs the lambda itself, and not its evaluation. We specify the Lambda SAM type as a log4j2 `Supplier`
     * to avoid this. Since we are using the log4j2 api here, this does not evaluate the lambda if the level
     * is not enabled.
     */
    class FunctionalLogger(val log: Logger): Logger by log {
      inline fun debug(crossinline supplier: () -> String) {
        log.debug(Supplier { supplier.invoke() })
      }
    
      inline fun debug(t: Throwable, crossinline supplier: () -> String) {
        log.debug(Supplier { supplier.invoke() }, t)
      }
    
      inline fun info(crossinline supplier: () -> String) {
        log.info(Supplier { supplier.invoke() })
      }
    
      inline fun info(t: Throwable, crossinline supplier: () -> String) {
        log.info(Supplier { supplier.invoke() }, t)
      }
    
      inline fun warn(crossinline supplier: () -> String) {
        log.warn(Supplier { supplier.invoke() })
      }
    
      inline fun warn(t: Throwable, crossinline supplier: () -> String) {
        log.warn(Supplier { supplier.invoke() }, t)
      }
    
      inline fun error(crossinline supplier: () -> String) {
        log.error(Supplier { supplier.invoke() })
      }
    
      inline fun error(t: Throwable, crossinline supplier: () -> String) {
        log.error(Supplier { supplier.invoke() }, t)
      }
    }
    
    /**
     * A delegate-based lazy logger instantiation. Use: `val log by logger()`.
     */
    @Suppress("unused")
    inline fun  T.logger(): Lazy =
      lazy { FunctionalLogger(LogManager.getLogger(unwrapCompanionClass(T::class.java))) }
    
    // unwrap companion class to enclosing class given a Java Class
    fun  unwrapCompanionClass(ofClass: Class): Class<*> {
      return if (ofClass.enclosingClass != null && ofClass.enclosingClass.kotlin.companionObject?.java == ofClass) {
        ofClass.enclosingClass
      } else {
        ofClass
      }
    }
    

    Log4j2 Kotlin Logging API

    Most of the previous section has been directly adapted to produce the Kotlin Logging API module, which is now an official part of Log4j2 (disclaimer: I am the primary author). You can download this directly from Apache, or via Maven Central.

    Usage is basically as describe above, but the module supports both interface-based logger access, a logger extension function on Any for use where this is defined, and a named logger function for use where no this is defined (such as top-level functions).

提交回复
热议问题