Idiomatic way of logging in Kotlin

2019-01-16 00:45发布

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 {
    private static final Logger LOG = LoggerFactory.getLogger(Foo.class);
}

Question is what is the idiomatic way of performing logging in Kotlin?

14条回答
小情绪 Triste *
2楼-- · 2019-01-16 01:15

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.
}
查看更多
Lonely孤独者°
3楼-- · 2019-01-16 01:15

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

查看更多
再贱就再见
4楼-- · 2019-01-16 01:17

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")
}
查看更多
你好瞎i
5楼-- · 2019-01-16 01:20

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

查看更多
霸刀☆藐视天下
6楼-- · 2019-01-16 01:25

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

查看更多
祖国的老花朵
7楼-- · 2019-01-16 01:25

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() }
}
查看更多
登录 后发表回答