Idiomatic way of logging in Kotlin

后端 未结 16 1434
情歌与酒
情歌与酒 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:44
    fun <R : Any> R.logger(): Lazy<Logger> = lazy { 
        LoggerFactory.getLogger((if (javaClass.kotlin.isCompanion) javaClass.enclosingClass else javaClass).name) 
    }
    
    class Foo {
        val logger by logger()
    }
    
    class Foo {
        companion object {
            val logger by logger()
        }
    }
    
    0 讨论(0)
  • 2020-12-07 07:45

    Slf4j example, same for others. This even works for creating package level logger

    /**  
      * Get logger by current class name.  
      */ 
    
    fun getLogger(c: () -> Unit): Logger = 
            LoggerFactory.getLogger(c.javaClass.enclosingClass)
    

    Usage:

    val logger = getLogger { }
    
    0 讨论(0)
  • 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 <reified T:Any> 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<SomeClass>()
      ...
    }
    

    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 <reified T : Any> T.logger(): Lazy<FunctionalLogger> =
      lazy { FunctionalLogger(LogManager.getLogger(unwrapCompanionClass(T::class.java))) }
    
    // unwrap companion class to enclosing class given a Java Class
    fun <T : Any> unwrapCompanionClass(ofClass: Class<T>): 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).

    0 讨论(0)
  • 2020-12-07 07:47

    First, you can add extension functions for logger creation.

    inline fun <reified T : Any> getLogger() = LoggerFactory.getLogger(T::class.java)
    fun <T : Any> T.getLogger() = LoggerFactory.getLogger(javaClass)
    

    Then you will be able to create a logger using the following code.

    private val logger1 = getLogger<SomeClass>()
    private val logger2 = getLogger()
    

    Second, you can define an interface that provides a logger and its mixin implementation.

    interface LoggerAware {
      val logger: Logger
    }
    
    class LoggerAwareMixin(containerClass: Class<*>) : LoggerAware {
      override val logger: Logger = LoggerFactory.getLogger(containerClass)
    }
    
    inline fun <reified T : Any> loggerAware() = LoggerAwareMixin(T::class.java)
    

    This interface can be used in the following way.

    class SomeClass : LoggerAware by loggerAware<SomeClass>() {
      // Now you can use a logger here.
    }
    
    0 讨论(0)
  • 2020-12-07 07:51

    Have a look at the kotlin-logging library.
    It allows logging like that:

    private val logger = KotlinLogging.logger {}
    
    class Foo {
      logger.info{"wohoooo $wohoooo"}
    }
    

    Or like that:

    class FooWithLogging {
      companion object: KLogging()
      fun bar() {
        logger.info{"wohoooo $wohoooo"}
      }
    }
    

    I also wrote a blog post comparing it to AnkoLogger: Logging in Kotlin & Android: AnkoLogger vs kotlin-logging

    Disclaimer: I am the maintainer of that library.

    Edit: kotlin-logging now has multiplatform support: https://github.com/MicroUtils/kotlin-logging/wiki/Multiplatform-support

    0 讨论(0)
  • 2020-12-07 07:51

    Anko

    You can use Anko library to do it. You would have code like below:

    class MyActivity : Activity(), AnkoLogger {
        private fun someMethod() {
            info("This is my first app and it's awesome")
            debug(1234) 
            warn("Warning")
        }
    }
    

    kotlin-logging

    kotlin-logging(Github project - kotlin-logging ) library allows you to write logging code like below:

    class FooWithLogging {
      companion object: KLogging()
      fun bar() {
        logger.info{"Item $item"}
      }
    }
    

    StaticLog

    or you can also use this small written in Kotlin library called StaticLog then your code would looks like:

    Log.info("This is an info message")
    Log.debug("This is a debug message")
    Log.warn("This is a warning message","WithACustomTag")
    Log.error("This is an error message with an additional Exception for output", "AndACustomTag", exception )
    
    Log.logLevel = LogLevel.WARN
    Log.info("This message will not be shown")\
    

    The second solution might better if you would like to define an output format for logging method like:

    Log.newFormat {
        line(date("yyyy-MM-dd HH:mm:ss"), space, level, text("/"), tag, space(2), message, space(2), occurrence)
    }
    

    or use filters, for example:

    Log.filterTag = "filterTag"
    Log.info("This log will be filtered out", "otherTag")
    Log.info("This log has the right tag", "filterTag")
    

    timberkt

    If you'd already used Jake Wharton's Timber logging library check timberkt.

    This library builds on Timber with an API that's easier to use from Kotlin. Instead of using formatting parameters, you pass a lambda that is only evaluated if the message is logged.

    Code example:

    // Standard timber
    Timber.d("%d %s", intVar + 3, stringFun())
    
    // Kotlin extensions
    Timber.d { "${intVar + 3} ${stringFun()}" }
    // or
    d { "${intVar + 3} ${stringFun()}" }
    

    Check also: Logging in Kotlin & Android: AnkoLogger vs kotlin-logging

    Hope it will help

    0 讨论(0)
提交回复
热议问题