Kotlin 没有与 Java 中使用的相同的静态字段概念。在 Java 中,通常接受的日志记录方式是:
public class Foo { private static final Logger LOG = LoggerFactory.getLogger(Foo.class); }
问题 是在 Kotlin 中执行日志记录的惯用方式是什么?
在大多数成熟的 Kotlin 代码中,您会在下面找到其中一种模式。使用 Property Delegates 的方法利用Kotlin 的强大功能来生成最小的代码。
注意:这里的代码是用于java.util.Logging但同样的理论适用于任何日志库
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 的您好
org.stackoverflow.kotlin.test.MyClass
更多关于伴随对象的信息:Companion Objects … 另请注意,在上面的示例中,MyClass::class.java获取Class<MyClass>记录器的 type 实例,而this.javaClass将获取 type 的实例Class<MyClass.Companion>。
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") } }
2015 年 12 月 26 日上午 11:28:44 org.stackoverflow.kotlin.test.MyClass foo 信息:MyClass 你好
您可以对每个实例和每个类变体进行性能测试,看看大多数应用程序是否存在实际差异。
属性委托 (常见,最优雅)
@Jire 在另一个答案中建议的另一种方法是创建一个属性委托,然后您可以使用它在您想要的任何其他类中统一执行逻辑。因为 Kotlin 已经提供了一个委托,所以有一种更简单的方法可以做到这一点Lazy,我们可以将它包装在一个函数中。这里的一个技巧是,如果我们想知道当前使用委托的类的类型,我们将其作为任何类的扩展函数:
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()这两个类的输出将是:
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的您好
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或其他一些标记接口有一个秘密的好处:您可以暗示您正在扩展自己的类,因此可以检测到您所在的类。嗯?为了减少混淆,这里是代码:
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()。问题是以下情况也可能在其他类上造成“污染”:
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:
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()不起作用,因为可以为伴随对象指定自定义名称。
unwrapCompanionClass()
removeSuffix()