kotlin - Kotlin 中的惯用登录方式

Kotlin 没有与 Java 中使用的静态字段相同的概念。在 Java 中,普遍接受的日志记录方式是:

public class Foo {
    private static final Logger LOG = LoggerFactory.getLogger(Foo.class);
}

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

最佳答案

在大多数成熟的 Kotlin 代码中,您会在下面找到其中一种模式。使用 的方法属性(property)代表利用 Kotlin 的强大功能来生成最小的代码。

注意:这里的代码适用于 java.util.Logging但同样的理论适用于任何日志库

类静电 (常见的,相当于问题中的 Java 代码)

如果您不能相信日志系统内的哈希查找的性能,您可以通过使用一个伴随对象获得与您的 Java 代码类似的行为,该对象可以保存一个实例并让您感觉像是静态的。

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

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

创建输出:

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



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

每个类的实例 (常见)

但是,真的没有理由避免在实例级别调用和获取记录器。您提到的惯用 Java 方式已经过时并且基于对性能的恐惧,而每个类的记录器已经被地球上几乎所有合理的日志系统缓存。只需创建一个成员来保存记录器对象。
class MyClass {
  val LOG = Logger.getLogger(this.javaClass.name)

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

创建输出:

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



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

属性(property)代表 (常见的,最优雅的)

@Jire 在另一个答案中建议的另一种方法是创建一个属性委托(delegate),然后您可以使用它在您想要的任何其他类中统一执行逻辑。由于 Kotlin 提供了 Lazy,因此有一种更简单的方法可以做到这一点。已经委托(delegate)了,我们可以将它包装在一个函数中。这里的一个技巧是,如果我们想知道当前使用委托(delegate)的类的类型,我们将其设为任何类的扩展函数:
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 中使用它,记录器名称将与您在类本身上使用它时相同。现在您可以简单地:
class Something {
    val LOG by logger()

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

对于每个类实例,或者如果您希望它更静态,每个类有一个实例:
class SomethingElse {
    companion object {
        val LOG by logger()

    }

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

以及您拨打 foo() 的输出在这两个类上将是:

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

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



扩展功能 (在这种情况下不常见,因为 Any 命名空间的“污染”)

Kotlin 有一些隐藏的技巧,可以让您使这些代码中的一些更小。您可以在类上创建扩展函数,从而为它们提供附加功能。上述评论中的一项建议是扩展 Any带有记录器功能。任何时候有人在任何类的 IDE 中使用代码完成功能,这都会产生噪音。但是扩展 Any 有一个 secret 的好处。或其他一些标记接口(interface):您可以暗示您正在扩展自己的类,因此检测您所在的类。嗯?为了不那么困惑,这里是代码:
// extend any class with the ability to get a logger
fun <T: Any> T.logger(): Logger {
     return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}

现在在一个类(或伴随对象)中,我可以简单地在我自己的类上调用这个扩展:
class SomethingDifferent {
    val LOG = logger()

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

生产输出:

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



基本上,该代码被视为对分机 Something.logger() 的调用。 .问题是以下内容也可能对其他类产生“污染”:
val LOG1 = "".logger()
val LOG2 = Date().logger()
val LOG3 = 123.logger()

标记接口(interface)扩展函数 (不确定有多常见,但“特征”的常见模型)

为了使扩展的使用更干净并减少“污染”,您可以使用标记界面来扩展:
interface Loggable {} 

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

或者甚至使用默认实现使方法成为接口(interface)的一部分:
interface Loggable {
    public fun logger(): Logger {
        return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
    }
}

并在您的类(class)中使用以下任一变体:
class MarkedClass: Loggable {
    val LOG = logger()
}

生产输出:

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



如果你想强制创建一个统一的字段来保存记录器,那么在使用这个接口(interface)时你可以很容易地要求实现者拥有一个字段,例如 LOG :
interface Loggable {
    val LOG: Logger  // abstract required field

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

现在接口(interface)的实现者必须是这样的:
class MarkedClass: Loggable {
    override val LOG: Logger = logger()
}

当然,抽象基类可以做同样的事情,可以选择接口(interface)和实现该接口(interface)的抽象类,从而实现灵活性和统一性:
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!
    }
}

放在一起 (一个小助手库)

这是一个小型帮助库,可以使上述任何选项都易于使用。在 Kotlin 中扩展 API 以使它们更符合您的喜好是很常见的。在扩展或顶级函数中。这是一个组合,为您提供如何创建记录器的选项,以及一个显示所有变体的示例:
// 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()
}

选择您想要保留的任何一个,以下是所有正在使用的选项:
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 个记录器实例将生成相同的记录器名称,并输出:

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



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

https://stackoverflow.com/questions/34416869/

相关文章:

kotlin - 为什么我们使用 "companion object"作为 Kotlin 中 Jav

java - Java 的 String[] 的 Kotlin 等价物是什么?

design-patterns - 如何在 Kotlin 中实现 Builder 模式?

hibernate - 带有 JPA : default constructor hell 的 Ko

kotlin - 在 Kotlin 中试用资源

dictionary - 如何在 Kotlin 中将列表转换为 map ?

random - 如何在 Kotlin 中获取随机数?

inheritance - 在 Kotlin 中扩展数据类

kotlin - 什么是 Kotlin 双键 (!!) 运算符?

kotlin - 如何在 Kotlin 中将 String 转换为 Long?