Kotlin的惯用日志记录方式 [英] Idiomatic way of logging in Kotlin

查看:187
本文介绍了Kotlin的惯用日志记录方式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Kotlin与Java中使用的静态字段没有相同的概念.在Java中,公认的记录方式是:

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

问题是在Kotlin中执行日志记录的惯用方式是什么?

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

推荐答案

在大多数成熟的Kotlin代码中,您将在下面找到这些模式之一.使用财产代表的方法利用了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.

注意:此处的代码适用于java.util.Logging,但相同的理论适用于任何日志记录库

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

类似静态(通常等同于问题中的Java代码)

Static-like (common, equivalent of your Java code in the question)

如果您不能相信日志记录系统内部的哈希查找的性能,则可以通过使用一个伴侣对象来获得与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 foo INFO:您好,来自MyClass

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

更多有关随播对象的信息:伴侣对象 ...也请注意,在上面的示例中,MyClass::class.java获取记录器的类型为Class<MyClass>的实例,而this.javaClass将获取类型为Class<MyClass.Companion>的实例.

More on companion objects here: Companion Objects ... Also note that in the sample above MyClass::class.java gets the instance of type Class<MyClass> for the logger, whereas this.javaClass would get the instance of type Class<MyClass.Companion>.

每个类的实例(常见)

但是,实际上没有理由避免在实例级别调用和获取记录器.您提到的惯用Java方法已经过时并且基于对性能的担心,而地球上几乎所有合理的日志记录系统都已经缓存了每个类的记录器.只需创建一个成员来保存记录器对象即可.

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.

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

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

创建输出:

2015年12月26日上午11:28:44 org.stackoverflow.kotlin.test.MyClass foo INFO:您好,来自MyClass

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

您可以对每个实例和每个类的变体进行性能测试,并查看大多数应用程序是否存在实际差异.

You can performance test both per instance and per class variations and see if there is a realistic difference for most apps.

财产代表(常见,最优雅)

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

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:

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"

此代码还确保如果在Companion Object中使用它,则记录器名称将与在类本身上使用的记录器名称相同.现在您可以简单地:

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:

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

在这两个类上调用foo()的输出将是:

And your output from calling foo() on both of these classes would be:

2015年12月26日上午11:30:55 org.stackoverflow.kotlin.test.Something foo INFO:某事你好

Dec 26, 2015 11:30:55 AM org.stackoverflow.kotlin.test.Something foo INFO: Hello from Something

2015年12月26日上午11:30:55 org.stackoverflow.kotlin.test.Something其他foo INFO:您好,来自SomethingElse

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

扩展功能(在这种情况下不常见,因为任何名称空间的污染")

Extension Functions (uncommon in this case because of "pollution" of Any namespace)

Kotlin有一些隐藏的技巧,可让您使其中的一些代码变得更小.您可以在类上创建扩展函数,从而为它们提供其他功能.上面评论中的一个建议是使用记录器功能扩展Any.每当有人在任何类的IDE中使用代码完成功能时,这都会产生噪音.但是扩展Any或某些其他标记接口有一个秘密好处:您可以暗示您正在扩展自己的类,因此可以检测您所在的类. ??为了减少混乱,下面是代码:

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 org.stackoverflow.kotlin.test.SomethingDifferent foo INFO:您好,来自SomethingDifferent

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

基本上,该代码被视为对扩展名Something.logger()的调用.问题在于,在其他类上创建污染"也可能是以下情况:

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 (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:

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

并在您的课程中使用以下任一变体:

And use either of these variations in your class:

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

产生输出:

2015年12月26日上午11:41:01 org.stackoverflow.kotlin.test.MarkedClass foo INFO:MarkedClass的您好

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

如果您想强制创建一个统一的字段来保存记录器,则在使用此界面时,您可以轻松地要求实现者拥有一个字段,例如LOG:

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

现在接口的实现者必须看起来像这样:

Now the implementer of the interface must look like this:

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 (A small helper library)

这里是一个小型帮助程序库,使上面的任何选项都易于使用.在Kotlin中,通常会扩展API以使其更符合您的喜好.扩展功能或顶级功能.以下是为您提供有关如何创建记录器的选项的混合以及显示所有变化的示例:

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

在此示例中创建的所有13个记录器实例将产生相同的记录器名称,并输出:

All 13 instances of the loggers created in this sample will produce the same logger name, and output:

2015年12月26日上午11:39:00 org.stackoverflow.kotlin.test.MixedBagOfTricks foo INFO:您好,来自MixedBagOfTricks

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

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

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 using removeSuffix() does not work since companion objects can be given custom names.

这篇关于Kotlin的惯用日志记录方式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆