小编典典

Kotlin 的惯用登录方式

all

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

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

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


阅读 57

收藏
2022-07-27

共1个答案

小编典典

在大多数成熟的 Kotlin 代码中,您会在下面找到其中一种模式。使用 Property Delegates 的方法利用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")
    }
}

创建输出:

2015 年 12 月 26 日上午 11:28:32 org.stackoverflow.kotlin.test.MyClassfoo 信息:来自
MyClass 的您好

更多关于伴随对象的信息:Companion
Objects

… 另请注意,在上面的示例中,MyClass::class.java获取Class<MyClass>记录器的 type
实例,而this.javaClass将获取 type 的实例Class<MyClass.Companion>

每个类的实例 (常见)

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

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 你好

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

属性委托 (常见,最优雅)

@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"

此代码还确保如果您在 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()这两个类的输出将是:

2015 年 12 月 26 日上午 11:30:55 org.stackoverflow.kotlin.test.Something foo
INFO:Hello from Something

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

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

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

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

生产输出:

2015 年 12 月 26 日上午 11:29:12 org.stackoverflow.kotlin.test.SomethingDifferent
foo 信息:来自SomethingDifferent的你好

基本上,代码被视为对 extension 的调用Something.logger()。问题是以下情况也可能在其他类上造成“污染”:

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

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

为了使扩展的使用更干净并减少“污染”,您可以使用标记接口来扩展:

interface Loggable {}

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

或者甚至使用默认实现使方法成为接口的一部分:

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 你好

如果你想强制创建一个统一的字段来保存记录器,那么在使用这个接口时,你可以很容易地要求实现者有一个字段,例如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()
}

当然,抽象基类也可以做同样的事情,同时选择接口和实现该接口的抽象类允许灵活性和统一性:

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 个记录器实例都将生成相同的记录器名称,并输出:

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

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

2022-07-27