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 the 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 100 but the same theory applies to any logging library

Static-like(常见,相当于问题中的Java代码)

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

class MyClass {
    companion object {
        val LOG = Logger.getLogger(MyClass::class.java.name) 
    }

    fun foo() {
        LOG.warning("Hello from MyClass")
    }
}  

创建输出:

2015年12月26日11:28:32上午org.stackoverflow.kotlin.test.MyClass英尺 信息:来自MyClass的问候

更多关于同伴物品的信息,请点击这里:Companion Objects.还要注意,在上面的示例中,MyClass::class.java获取记录器的类型Class<MyClass>的实例,而this.javaClass将获取类型Class<MyClass.Companion>的实例.

Per Instance of a Class (common)

但是,没有理由不在实例级别调用和获取记录器.您提到的惯用Java方式已经过时,而且基于对性能的担忧,而每个类的记录器已经被地球上几乎所有合理的日志(log)系统缓存.只需创建一个成员来保存logger对象.

class MyClass {
  val LOG = Logger.getLogger(this.javaClass.name)

  fun foo() {
        LOG.warning("Hello from MyClass")
  }
} 

创建输出:

2015年12月26日上午11:28:44.堆栈溢出.Kotlin .测验MyClass foo

您可以测试每个实例和每个类的性能变化,看看大多数应用是否存在实际差异.

Property Delegates (common, most elegant)

@Jire在另一个答案中建议的另一种方法是创建一个属性委托,然后您可以使用它在您想要的任何其他类中统一执行逻辑.有一种更简单的方法可以做到这一点,因为Kotlin已经提供了一个Lazy委托,我们可以把它包装成一个函数.这里的一个技巧是,如果我们想知道当前使用委托的类的类型,我们将其作为任何类的扩展函数:

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"

这段代码还确保,如果在伴生对象中使用它,记录器名称将与在类本身上使用它时相同.现在你可以简单地:

class Something {
    val LOG by logger()

    fun foo() {
        LOG.info("Hello from Something")
    }
}

for per class instance, 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 output from calling foo() on both of these classes would be:

2015年12月26日上午11:30:55.堆栈溢出.Kotlin .测验什么东西

2015年12月26日上午11:30:55.堆栈溢出.Kotlin .测验有什么事吗

Extension Functions(在这种情况下不常见,因为"污染"了任何名称空间)

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 in any class. But there is a secret benefit to extending Any 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:

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

Now within a class (or companion object), I can simply call this extension on my own class:

class SomethingDifferent {
    val LOG = logger()

    fun foo() {
        LOG.info("Hello from SomethingDifferent")
    }
}

生产输出:

2015年12月26日上午11:29:12组织.堆栈溢出.Kotlin .测验不一样的东西

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:

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

Extension Functions on Marker Interface(不确定有多普遍,但"trait "的共同模式)

To make the use of extensions cleaner and reduce "pollution", you could use a marker interface to extend:

interface Loggable {} 

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

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

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

并在课堂上使用以下任一变体:

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

生产输出:

2015年12月26日上午11:41:01 org.stackoverflow.kotlin.test.MarkedClass Foo 信息:来自MarkedClass的问候

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:

interface Loggable {
    val LOG: Logger  // abstract required field

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

现在,接口的实现者必须如下所示:

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

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:

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(小型帮助库)

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:

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

Pick whichever of those you want to keep, and here are all of the options in use:

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 the loggers created in this sample will produce the same logger name, and output:

2015年12月26日上午11:39:00组织.堆栈溢出.Kotlin .测验混合糖

Note: unwrapCompanionClass()方法确保我们不会生成以伴随对象命名的记录器,而是生成封闭类.这是当前推荐的查找包含伴生对象的类的方法.使用removeSuffix()从名称中剥离"$Companion"是不起作用的,因为可以为伴生对象指定自定义名称.

Kotlin相关问答推荐

UByte范围. Min_UTE.. UByte.MAX_UTE不符合预期

导入org.gradle.jvm.toolchain.internal.JavaToolchainFactory未解析引用:Java工具链工厂

Kotlin中的增广赋值语句中的难以理解的错误

为什么";";.equals(1)在柯特林语中是有效的,但";";=1是无效的?

Groovy Gradle文件的Kotlin类似功能

在调用父构造函数之前重写类属性

哪个更好? Integer.valueOf(readLine()) 或 readLine()!!.toInt()

MyType.()在 Kotlin 中是什么意思?

按钮无法在 Android Studio 上打开新活动

gradle 如何 Select 以-jvm结尾的库?

如何为 Kotlin 中的每个循环设置以避免越界异常

如何在 jOOQ 中两次加入同一张表?

在 Kotlin 中通过反射获取 Enum 值

如何从 Firestore 查询中排除元素?

变量后的Android问号

以Kotlin为单位的货币数据类型

在 Kotlin 中创建非绑定服务

如何计算Kotlin中的百分比

Kotlin - 如果不为空,则使用修改后的 Obj props 覆盖 Obj props

Kotlin - 错误:Could not find or load main class _DefaultPackage