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?
In majority of mature Kotlin code, you will find one of these patterns below. The approach using Property Delegates takes advantage of the power of Kotlin to produce the smallest code.
Note: the code here is for
java.util.Logging
but the same theory applies to any logging libraryStatic-like (common, equivalent of your Java code in the question)
If you cannot trust in performance of that hash lookup inside the logging system, you can get similar behaviour to your Java code by using a companion object which can hold an instance and feel like a static to you.
creating output:
More on companion objects here: Companion Objects ... Also note that in the sample above
MyClass::class.java
gets the instance of typeClass<MyClass>
for the logger, whereasthis.javaClass
would get the instance of typeClass<MyClass.Companion>
.Per Instance of a Class (common)
But, there is really no reason to avoid calling and getting a logger at the instance level. The idiomatic Java way you mentioned is outdated and based on fear of performance, whereas the logger per class is already cached by almost any reasonable logging system on the planet. Just create a member to hold the logger object.
creating output:
You can performance test both per instance and per class variations and see if there is a realistic difference for most apps.
Property Delegates (common, most elegant)
Another approach, which is suggested by @Jire in another answer, is to create a property delegate, which you can then use to do the logic uniformly in any other class that you want. There is a simpler way to do this since Kotlin provides a
Lazy
delegate already, we can just wrap it in a function. One trick here is that if we want to know the type of the class currently using the delegate, we make it an extension function on any class:This code also makes sure that if you use it in a Companion Object that the logger name will be the same as if you used it on the class itself. Now you can simply:
for per class instance, or if you want it to be more static with one instance per class:
And your output from calling
foo()
on both of these classes would be:Extension Functions (uncommon in this case because of "pollution" of Any namespace)
Kotlin has a few hidden tricks that let you make some of this code even smaller. You can create extension functions on classes and therefore give them additional functionality. One suggestion in the comments above was to extend
Any
with a logger function. This can create noise anytime someone uses code-completion in their IDE on any class. But there is a secret benefit to extendingAny
or some other marker interface: you can imply that you are extending your own class and therefore detect the class you are within. Huh? To be less confusing, here is the code:Now within a class (or companion object) I can simply call this extension on my own class:
Producing output:
Basically, the code is seen as a call to extension
Something.logger()
. The problem is that the following could also be true creating "pollution" on other classes:Extension Functions on Marker Interface (not sure how common, but common model for "traits")
To make the use of extensions cleaner and reduce "pollution", you could use a marker interface to extend:
Or even make the method part of the interface with a default implementation:
And use either of these variations in your class:
Producing output:
If you wanted to force the creation of a uniform field to hold the logger, then while using this interface you could easily require the implementer to have a field such as
LOG
:Now the implementer of the interface must look like this:
Of course an abstract base class can do the same, having the option of both the interface and an abstract class implementing that interface allows flexibility and uniformity:
Putting it All Together (A small helper library)
Here is a small helper library to make any of the options above easy to use. It is common in Kotlin to extend API's to make them more to your liking. Either in extension or top-level functions. Here is a mix to give you options for how to create loggers, and a sample showing all variations:
Pick whichever of those you want to keep, and here are all of the options in use:
All 13 instances of the loggers created in this sample will produce the same logger name, and output:
Note: The
unwrapCompanionClass()
method ensures that we do not generate a logger named after the companion object but rather the enclosing class. This is the current recommended way to find the class containing the companion object. Stripping "$Companion" from the name usingremoveSuffix()
does not work since companion objects can be given custom names.I have heard of no idiom in this regard. The simpler the better, so I would use a top-level property
This practice serves well in Python, and as different as Kotlin and Python might appear, I believe they are quite similar in there "spirit" (speaking of idioms).