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
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.
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.
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.
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")
}
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 {
}
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() }
}
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).
That's what companion objects are for, in general: replacing static stuff.