Idiomatic registration method in Kotlin

Kotlin does not have the same concept of static fields as in Java. In Java, a common way of logging is:

public class Foo { private static final Logger LOG = LoggerFactory.getLogger(Foo.class); } 

Question - what is the idiomatic way of keeping a journal in Kotlin?

+129
kotlin kotlin-logging
Dec 22 '15 at 13:20
source share
15 answers

In most mature Kotlin codes, you will find one of these patterns below. The property delegate approach leverages the power of Kotlin to create the smallest code.

Note: the code here is for java.util.Logging but the same theory applies to any log library

Static (regular, equivalent of your java code in question)

If you cannot trust the performance of this hash search in the logging system, you can get behavior similar to your Java code using a companion object that can hold an instance and feel static for you.

 class MyClass { companion object { val LOG = Logger.getLogger(MyClass::class.java.name) } fun foo() { LOG.warning("Hello from MyClass") } } 

output creation:

December 26, 2015 11:28:32 org.stackoverflow.kotlin.test.MyClass foo INFO: Hello from MyClass

More information about companion objects here: Companion Objects ... Also note that in the above example, MyClass::class.java gets an instance of type Class<MyClass> for the registrar, while this.javaClass will get an instance of type Class<MyClass.Companion> .

Per class instance (shared)

But there really is no reason to avoid calling and getting the registrar at the instance level. The idiomatic Java method you mentioned is deprecated and based on a fear of performance, while the logging facility for each class is already cached by almost any intelligent logging system on the planet. Just create a member to store the registrar object.

 class MyClass { val LOG = Logger.getLogger(this.javaClass.name) fun foo() { LOG.warning("Hello from MyClass") } } 

output creation:

December 26, 2015 11:28:44 org.stackoverflow.kotlin.test.MyClass foo INFO: Hello from MyClass

You can test the performance for both each instance and for each class, and see if there is a real difference for most applications.

Property delegates (ordinary, most elegant)

Another approach suggested by @Jire in another answer is to create a property delegate, which you can then use to evenly execute logic in any other class you want. There is an easier way to do this, since Kotlin already provides a Lazy delegate, we can just wrap it in a function. There is one trick here: if we want to find out the type of class that the delegate is currently using, we will make it an extension function for any class:

 fun <R : Any> R.logger(): Lazy<Logger> { return lazy { Logger.getLogger(unwrapCompanionClass(this.javaClass).name) } } // see code for unwrapCompanionClass() below in "Putting it all Together section" 

This code also ensures that if you use it in a companion object, the registrar name will be the same as if you used it in the class itself. Now you can simply:

 class Something { val LOG by logger() fun foo() { LOG.info("Hello from Something") } } 

for each instance of the class, or if you want it to be more static with one instance per class:

 class SomethingElse { companion object { val LOG by logger() } fun foo() { LOG.info("Hello from SomethingElse") } } 

And your result of calling foo() for both of these classes will be:

December 26, 2015 11:30:55 org.stackoverflow.kotlin.test.Something foo INFORMATION: Hello from something

December 26, 2015 11:30:55 org.stackoverflow.kotlin.test.SomethingElse foo INFO: Hello from SomethingElse

Extension functions (unusual in this case due to the "pollution" of any namespace)

Kotlin has some hidden tricks that make this code even smaller. You can create extension functions for classes and therefore provide them with additional functionality. One suggestion in the comments above was the Any extension using the logger function. This can make noise at any time when someone uses code completion in their IDE in any class. But there is a secret advantage in extending the Any interface or some other token: you can imply that you are expanding your own class and therefore find the class you are in. BUT? To be less confusing, here is the code:

 // extend any class with the ability to get a logger fun <T: Any> T.logger(): Logger { return Logger.getLogger(unwrapCompanionClass(this.javaClass).name) } 

Now inside the class (or companion object), I can simply call this extension in my own class:

 class SomethingDifferent { val LOG = logger() fun foo() { LOG.info("Hello from SomethingDifferent") } } 

To produce products:

December 26, 2015 11:29:12 org.stackoverflow.kotlin.test.SomethingDifferent foo INFO: Hello from SomethingDifferent

Essentially, the code is seen as a call to the Something.logger() extension. The problem is that the following may also be true, creating “pollution” for other classes:

 val LOG1 = "".logger() val LOG2 = Date().logger() val LOG3 = 123.logger() 

Extension functions on the marker interface (not sure how widespread, but widespread model for "hell")

To make the use of extensions more understandable and to reduce "pollution", you can use the marker interface for the extension:

 interface Loggable {} fun Loggable.logger(): Logger { return Logger.getLogger(unwrapCompanionClass(this.javaClass).name) } 

Or even make the method part of the default implementation interface:

 interface Loggable { public fun logger(): Logger { return Logger.getLogger(unwrapCompanionClass(this.javaClass).name) } } 

And use any of these options in your class:

 class MarkedClass: Loggable { val LOG = logger() } 

To produce products:

December 26, 2015 11:41:01 org.stackoverflow.kotlin.test.MarkedClass foo INFO: Hello from MarkedClass

If you want to force the creation of a single field for storing the registrar, then using this interface you can easily require that the developer have a field, such as LOG :

 interface Loggable { val LOG: Logger // abstract required field public fun logger(): Logger { return Logger.getLogger(unwrapCompanionClass(this.javaClass).name) } } 

The interface designer should now look like this:

 class MarkedClass: Loggable { override val LOG: Logger = logger() } 

Of course, an abstract base class can do the same, with the ability of both an interface and an abstract class that implements this interface, which provides flexibility and uniformity:

 abstract class WithLogging: Loggable { override val LOG: Logger = logger() } // using the logging from the base class class MyClass1: WithLogging() { // ... already has logging! } // providing own logging compatible with marker interface class MyClass2: ImportantBaseClass(), Loggable { // ... has logging that we can understand, but doesn't change my hierarchy override val LOG: Logger = logger() } // providing logging from the base class via a companion object so our class hierarchy is not affected class MyClass3: ImportantBaseClass() { companion object : WithLogging() { // we have the LOG property now! } } 

Putting it all together (a small helper library)

Here is a small helper library that makes it easy to use any of the options above. Kotlin has decided to extend the APIs to make them more suitable. Either in the extension, or in the functions of the top level. Here is a mix to give you options for creating registrars, and an example showing all the options:

 // Return logger for Java class, if companion object fix the name fun <T: Any> logger(forClass: Class<T>): Logger { return Logger.getLogger(unwrapCompanionClass(forClass).name) } // unwrap companion class to enclosing class given a Java Class fun <T : Any> unwrapCompanionClass(ofClass: Class<T>): Class<*> { return ofClass.enclosingClass?.takeIf { ofClass.enclosingClass.kotlin.companionObject?.java == ofClass } ?: ofClass } // unwrap companion class to enclosing class given a Kotlin Class fun <T: Any> unwrapCompanionClass(ofClass: KClass<T>): KClass<*> { return unwrapCompanionClass(ofClass.java).kotlin } // Return logger for Kotlin class fun <T: Any> logger(forClass: KClass<T>): Logger { return logger(forClass.java) } // return logger from extended class (or the enclosing class) fun <T: Any> T.logger(): Logger { return logger(this.javaClass) } // return a lazy logger property delegate for enclosing class fun <R : Any> R.lazyLogger(): Lazy<Logger> { return lazy { logger(this.javaClass) } } // return a logger property delegate for enclosing class fun <R : Any> R.injectLogger(): Lazy<Logger> { return lazyOf(logger(this.javaClass)) } // marker interface and related extension (remove extension for Any.logger() in favour of this) interface Loggable {} fun Loggable.logger(): Logger = logger(this.javaClass) // abstract base class to provide logging, intended for companion objects more than classes but works for either abstract class WithLogging: Loggable { val LOG = logger() } 

Select any of the ones you want to save, and here are all the options:

 class MixedBagOfTricks { companion object { val LOG1 by lazyLogger() // lazy delegate, 1 instance per class val LOG2 by injectLogger() // immediate, 1 instance per class val LOG3 = logger() // immediate, 1 instance per class val LOG4 = logger(this.javaClass) // immediate, 1 instance per class } val LOG5 by lazyLogger() // lazy delegate, 1 per instance of class val LOG6 by injectLogger() // immediate, 1 per instance of class val LOG7 = logger() // immediate, 1 per instance of class val LOG8 = logger(this.javaClass) // immediate, 1 instance per class } val LOG9 = logger(MixedBagOfTricks::class) // top level variable in package // or alternative for marker interface in class class MixedBagOfTricks : Loggable { val LOG10 = logger() } // or alternative for marker interface in companion object of class class MixedBagOfTricks { companion object : Loggable { val LOG11 = logger() } } // or alternative for abstract base class for companion object of class class MixedBagOfTricks { companion object: WithLogging() {} // instance 12 fun foo() { LOG.info("Hello from MixedBagOfTricks") } } // or alternative for abstract base class for our actual class class MixedBagOfTricks : WithLogging() { // instance 13 fun foo() { LOG.info("Hello from MixedBagOfTricks") } } 

All 13 instances of registrars created in this sample will produce the same registrar name and output:

December 26, 2015 11:39:00 org.stackoverflow.kotlin.test.MixedBagOfTricks foo INFO: Hello from MixedBagOfTricks

Note. The unwrapCompanionClass() method ensures that we are not an unwrapCompanionClass() registrar named after a companion object, but rather a class. This is the current recommended way to find a class containing a companion object. Removing $ Companion from a name using removeSuffix() does not work, since companion objects can be given their own names.

+209
Dec 25 '15 at 11:49
source share

Check out the kotlin-logging library.
This allows you to register as follows:

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

Or like this:

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

I also wrote a blog post comparing it with AnkoLogger : AnkoLogger in Kotlin and Android: AnkoLogger vs kotlin-logging

Disclaimer: I am the custodian of this library.

Change: kotlin-logging now has multi-platform support: https://github.com/MicroUtils/kotlin-logging/wiki/Multiplatform-support

+25
Jul 04 '16 at 21:34
source share

As a good example of a registration implementation, I would like to mention Anko , which uses the special AnkoLogger interface, which class needs to be AnkoLogger . Inside the interface there is code that generates a registration tag for the class. Logging is done through extension functions that can be called inside the interace implementation without prefixes or even creating an instance of the log.

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




The code below is mainly AnkoLogger , simplified and rewritten for Android agnostic use.

First, there is an interface that behaves like a marker interface:

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

It allows its implementation to use the extension functions for MyLogger inside its code by simply calling them on this . And it also contains a registration tag.

Next, there is a common 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 the tag from the MyLogger implementation, checks the logging options, and then calls one of the two handlers, one that has the Throwable argument and one that doesn't.

You can then define as many logging methods as you like, as follows:

 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() }) 

They are defined once for both message-only logging and Throwable , this is done with the optional Throwable parameter.

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




It allows you to use the following:
 class MyClass : MyLogger { fun myFun() { info("Info message") } } 

There is a small drawback: to enter the package level functions, you need a registrar object:

 private object MyPackageLog : MyLogger fun myFun() { MyPackageLog.info("Info message") } 
+7
Dec 22 '15 at 21:03
source share

KISS: for Java teams migrating to Kotlin

If you don't mind providing a class name in every instance of the logger (just like in java), you can simplify it by defining it as a top-level function somewhere in your project:

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

In this case, the parameter of the specified type Kotlin is used.

Now you can use it as follows:

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

This approach is very simple and close to the Java equivalent, but adds a bit of syntactic sugar.

Next step: extensions or delegates

I personally prefer to go further and use the extension or delegate approach. This is well summarized in @JaysonMinard's answer, but here is TL; DR for the "Delegate" approach with the log4j2 API ( UPDATE : you no longer need to write this code manually since it was released as the official module for the log4j2 project, see below). Since log4j2, unlike slf4j, supports logging with Supplier , I also added a delegate to simplify the use of these methods.

 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 was directly adapted to create the Kotlin Logging API module, which is now the official part of Log4j2 (disclaimer: I am the main author). You can download it directly from Apache or through Maven Central .

Usage is basically the same as described above, but the module supports both interface access to the logger function of the logger extension for Any for use where this defined, and a named logger function for use where this not defined (for example, top-level functions) .).

+6
Mar 08 '16 at 0:56
source share

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() } } 
+4
Dec 24 '15 at 23:39
source share

Anko

You can use the Anko library to do this. You will have the code as shown below:

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

Kotlin Logging

The kotlin-logging library ( Github project - kotlin-logging ) allows you to write a registration code, as shown below:

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

StaticLog

or you can also use this small Kotlin-written library called StaticLog then your code will look like this:

 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 be better if you want to define an output format for the logging method, for example:

 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've already used the Jake Wharton Timber Log Library check out timberkt .

This library is based on Timber with an API that is easier to use from Kotlin. Instead of using formatting options, you pass in a lambda that is evaluated only 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: Login to Kotlin & Android: AnkoLogger vs kotlin-logging

Hope this helps

+4
Aug 17 '16 at 11:45
source share

I have not heard a word about this. The simpler the better, so I would use a top-level property

 val logger = Logger.getLogger("package_name") 

This practice works well in Python, and as Kotlin and Python may seem, I think they are very similar to “spirit” (speaking of idioms).

+1
Dec 22 '15 at 13:32
source share

What about an extension function instead of a class? So you get:

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

Note. I have not tested this at all, so this may not be entirely correct.

+1
Jun 02 '16 at 12:28
source share

First, you can add extension functions to create a log.

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

Then you can create a registrar using the following code.

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

Secondly, you can define the interface that the registrar provides 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) 

.

 class SomeClass : LoggerAware by loggerAware<SomeClass>() { // Now you can use a logger here. } 
+1
08 . '16 10:01
source share

@JvmStatic

+1
06 '18 3:09
source share

, , : .

0
22 . '15 20:35
source share

Slf4j, .

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

Using:

 val logger = getLogger { } 
0
06 . '16 3:54
source share
 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
21 . '18 5:52
source share

, , , ?

, , :

 package nieldw.test import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.Logger import org.junit.jupiter.api.Test fun logger(lambda: () -> Unit): Lazy<Logger> = lazy { LogManager.getLogger(getClassName(lambda.javaClass)) } private fun <T : Any> getClassName(clazz: Class<T>): String = clazz.name.replace(Regex("""\$.*$"""), "") val topLog by logger { } class TopLevelLoggingTest { val classLog by logger { } @Test fun 'What is the javaClass?'() { topLog.info("THIS IS IT") classLog.info("THIS IS IT") } } 
0
25 . '18 9:06
source share

WIP ( ), : https://github.com/leandronunes85/log-format-enforcer#kotlin-soon-to-come-in-version-14

- . Kotlin, , . , :

 private val LOG = LogFormatEnforcer.loggerFor<Foo>() class Foo { } 
0
23 . '19 12:43
source share



All Articles