Kotlin 中的惯用登录方式 [英] Idiomatic way of logging in Kotlin

查看:32
本文介绍了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信息:来自 MyClass 的你好

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

更多关于伴生对象的信息:伴生对象 ...还有请注意,在上面的示例中,MyClass::class.java 获取记录器的 Class 类型的实例,而 this.javaClass将获得 Class 类型的实例.

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信息:来自 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.

Property Delegates(常见的,最优雅的)

@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信息:你好

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.SomethingElse foo信息:来自别的东西你好

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

扩展函数(在这种情况下不常见,因为 Any 命名空间的污染")

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信息:你好来自不同的东西

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信息:来自 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信息:来自 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天全站免登陆