Idiomatic way of logging in Kotlin

后端 未结 16 1408
情歌与酒
情歌与酒 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:38

    What about an extension function on Class instead? That way you end up with:

    public fun KClass.logger(): Logger = LoggerFactory.getLogger(this.java)
    
    class SomeClass {
        val LOG = SomeClass::class.logger()
    }
    

    Note - I've not tested this at all, so it might not be quite right.

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

    As a good example of logging implementation I'd like to mention Anko which uses a special interface AnkoLogger which a class that needs logging should implement. Inside the interface there's code that generates a logging tag for the class. Logging is then done via extension functions which can be called inside the interace implementation without prefixes or even logger instance creation.

    I don't think this is idiomatic, but it seems a good approach as it requires minimum code, just adding the interface to a class declaration, and you get logging with different tags for different classes.


    The code below is basically AnkoLogger, simplified and rewritten for Android-agnostic usage.

    First, there's an interface which behaves like a marker interface:

    interface MyLogger {
        val tag: String get() = javaClass.simpleName
    }
    

    It lets its implementation use the extensions functions for MyLogger inside their code just calling them on this. And it also contains logging tag.

    Next, there is a general entry point for different logging methods:

    private inline fun log(logger: MyLogger,
                           message: Any?,
                           throwable: Throwable?,
                           level: Int,
                           handler: (String, String) -> Unit,
                           throwableHandler: (String, String, Throwable) -> Unit
    ) {
        val tag = logger.tag
        if (isLoggingEnabled(tag, level)) {
            val messageString = message?.toString() ?: "null"
            if (throwable != null)
                throwableHandler(tag, messageString, throwable)
            else
                handler(tag, messageString)
        }
    }
    

    It will be called by logging methods. It gets a tag from MyLogger implementation, checks logging settings and then calls one of two handlers, the one with Throwable argument and the one without.

    Then you can define as many logging methods as you like, in this way:

    fun MyLogger.info(message: Any?, throwable: Throwable? = null) =
            log(this, message, throwable, LoggingLevels.INFO,
                { tag, message -> println("INFO: $tag # $message") },
                { tag, message, thr -> 
                    println("INFO: $tag # $message # $throwable");
                    thr.printStackTrace()
                })
    

    These are defined once for both logging just a message and logging a Throwable as well, this is done with optional throwable parameter.

    The functions that are passed as handler and throwableHandler can be different for different logging methods, for example, they can write the log to file or upload it somewhere. isLoggingEnabled and LoggingLevels are omitted for brevity, but using them provides even more flexibility.


    It allows for the following usage:

    class MyClass : MyLogger {
        fun myFun() {
            info("Info message")
        }
    }
    

    There is a small drawback: a logger object will be needed for logging in package-level functions:

    private object MyPackageLog : MyLogger
    
    fun myFun() {
        MyPackageLog.info("Info message")
    }
    
    0 讨论(0)
  • 2020-12-07 07:41

    This is still WIP (almost finished) so I'd like to share it: https://github.com/leandronunes85/log-format-enforcer#kotlin-soon-to-come-in-version-14

    The main goal of this library is to enforce a certain log style across a project. By having it generate Kotlin code I'm trying to address some of the issues mentioned in this question. With regards to the original question what I usually tend to do is to simply:

    private val LOG = LogFormatEnforcer.loggerFor<Foo>()
    class Foo {
    
    }
    
    0 讨论(0)
  • 2020-12-07 07:42

    Would something like this work for you?

    class LoggerDelegate {
    
        private var logger: Logger? = null
    
        operator fun getValue(thisRef: Any?, property: KProperty<*>): Logger {
            if (logger == null) logger = Logger.getLogger(thisRef!!.javaClass.name)
            return logger!!
        }
    
    }
    
    fun logger() = LoggerDelegate()
    
    class Foo { // (by the way, everything in Kotlin is public by default)
        companion object { val logger by logger() }
    }
    
    0 讨论(0)
  • 2020-12-07 07:42

    I have heard of no idiom in this regard. The simpler the better, so I would use a top-level property

    val logger = Logger.getLogger("package_name")
    

    This practice serves well in Python, and as different as Kotlin and Python might appear, I believe they are quite similar in their "spirit" (speaking of idioms).

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

    That's what companion objects are for, in general: replacing static stuff.

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