登录Kotlin的习惯性的方式

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

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

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

在大多数成熟的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.MyClass foo信息:MyClass中的Hello

更多关于伴侣对象在这里: https MyClass::class.java …另外请注意,在上面的示例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 INFO:Hello from MyClass

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

物业代表 (普通,最优雅)

@Jire在另一个答案中提出的另一种方法是创建一个属性委托,然后您可以使用它来在您想要的任何其他类中统一执行逻辑。 有一个更简单的方法来做到这一点,因为Kotlin已经提供了一个Lazy委托,我们可以把它包装在一个函数中。 这里的一个窍门是,如果我们想知道当前使用委托的类的类型,我们把它作为任何类的扩展函数:

 public 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对象中使用它,那么记录器名称将与您在类本身上使用它相同。 现在你可以简单地:

 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

扩展功能 (在这种情况下,由于任何命名空间的“污染”而不常见)

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

 // extend any class with the ability to get a logger public 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 INFO:来自Something的Hello

基本上,代码被看作是对扩展Something.logger()的调用。 问题在于,以下也可能是真的造成其他类别的“污染”:

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

标记界面上的扩展函数 (不确定多么普遍,但是“特征”的通用模型)

为了使扩展名的使用更清洁并减少“污染”,可以使用标记界面来扩展:

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

甚至可以使方法成为默认实现的接口的一部分:

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

在你的课堂上使用这些变化中的任何一个:

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

生产产量:

2015年12月26日上午11:41:01 org.stackoverflow.kotlin.test.MarkedClass foo信息:来自MarkedClass的Hello

如果你想强制创建一个统一的字段来保存记录器,那么在使用这个接口的时候,你可以很容易地要求实现者有一个字段,如LOG

 interface Loggable { val LOG: Logger // abstract required field public fun logger(): Logger { return Logger.getLogger(unwrapCompanionClass(this.javaClass).name) } } 

现在接口的实现者必须如下所示:

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

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

 abstract class WithLogging: Loggable { override val LOG: Logger = logger() } // using the logging from the base class public class MyClass1: WithLogging() { // ... already has logging! } // providing own logging compatible with marker interface public 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 public 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 public fun <T: Any> logger(forClass: Class<T>): Logger { return Logger.getLogger(unwrapCompanionClass(forClass).name) } // unwrap companion class to enclosing class given a Java Class public fun <T: Any> unwrapCompanionClass(ofClass: Class<T>): Class<*> { return if (ofClass.enclosingClass != null && ofClass.enclosingClass.kotlin.companionObject?.java == ofClass) { ofClass.enclosingClass } else { ofClass } } // unwrap companion class to enclosing class given a Kotlin Class public fun <T: Any> unwrapCompanionClass(ofClass: KClass<T>): KClass<*> { return unwrapCompanionClass(ofClass.java).kotlin } // Return logger for Kotlin class public fun <T: Any> logger(forClass: KClass<T>): Logger { return logger(forClass.java) } // return logger from extended class (or the enclosing class) public fun <T: Any> T.logger(): Logger { return logger(this.javaClass) } // return a lazy logger property delegate for enclosing class public fun <R : Any> R.lazyLogger(): Lazy<Logger> { return lazy { logger(this.javaClass) } } // return a logger property delegate for enclosing class public 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 {} public fun Loggable.logger(): Logger = logger(this.javaClass) // abstract base class to provide logging, intended for companion objects more than classes but works for either public 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()方法确保我们不会生成以伴随对象命名的记录器,而是生成一个以封闭类为名的记录器。 这是目前推荐的方法来查找包含伴随对象的类。 从名称中使用removeSuffix()剥离“$ Companion”不起作用,因为随removeSuffix()对象可以被赋予自定义名称。

看看kotlin-logging库。
它允许这样的日志记录:

 private val logger = KotlinLogging.logger {} class Foo { logger.info{"wohoooo $wohoooo"} } 

或者像这样:

 class FooWithLogging { companion object: KLogging() fun bar() { logger.info{"wohoooo $wohoooo"} } } 

我还写了一篇博客文章,将其与AnkoLogger进行比较: https : AnkoLogger

免责声明:我是该图书馆的维护者;-)

作为日志实现的一个很好的例子,我想提一下使用特殊界面AnkoLogger ,它需要日志记录的类应该实现。 在界面里面有代码为这个类生成一个日志标记。 然后通过扩展函数完成日志记录,这些扩展函数可以在interace实现中调用,不需要前缀甚至创建记录器实例。

我不认为这是惯用的 ,但它似乎是一个很好的方法,因为它需要最少的代码,只需将接口添加到类声明,并为不同的类使用不同的标记进行日志记录。


下面的代码基本上是AnkoLogger ,简化和重写为Android不可知论的用法。

首先,有一个界面的行为就像一个标记界面:

 interface MyLogger { val tag: String get() = javaClass.simpleName } 

它允许它的实现在代码中使用MyLogger的扩展函数,只是调用它们。 还包含日志标记。

接下来,对于不同的日志记录方法,有一个通用的入口点:

 private inline fun log(logger: MyLogger, message: Any?, throwable: Throwable?, level: Int, handler: (String, String) -> Unit, throwableHandler: (String, String, Throwable) -> Unit ) { val tag = logger.tag if (isLoggingEnabled(tag, level)) { val messageString = message?.toString() ?: "null" if (throwable != null) throwableHandler(tag, messageString, throwable) else handler(tag, messageString) } } 

它将被记录方法调用。 它从MyLogger实现中获取一个标记,检查日志设置,然后调用两个处理程序之一,一个使用Throwable参数,另一个不使用。

然后,您可以按照自己喜欢的方式定义许多日志记录方法:

 fun MyLogger.info(message: Any?, throwable: Throwable? = null) = log(this, message, throwable, LoggingLevels.INFO, { tag, message -> println("INFO: $tag # $message") }, { tag, message, thr -> println("INFO: $tag # $message # $throwable"); thr.printStackTrace() }) 

这些都是定义一次记录只是一个消息和记录一个Throwable ,这是用可选的throwable参数完成的。

作为handlerthrowableHandler传递的handler对于不同的日志方法可以是不同的,例如,可以将日志写入文件或将其上传到某处。 isLoggingEnabledLoggingLevels被简化,但使用它们提供了更多的灵活性。


它允许以下用法:

 class MyClass : MyLogger { fun myFun() { info("Info message") } } 

有一个小缺点:登录包级函数需要一个记录器对象:

 private object MyPackageLog : MyLogger fun myFun() { MyPackageLog.info("Info message") } 

会有这样的事情为你工作?

 class LoggerDelegate { private var logger: Logger? = null operator fun getValue(thisRef: Any?, property: KProperty<*>): Logger { if (logger == null) logger = Logger.getLogger(thisRef!!.javaClass.name) return logger!! } } fun logger() = LoggerDelegate() class Foo { // (by the way, everything in Kotlin is public by default) companion object { val logger by logger() } } 

安口

你可以使用Anko库来做到这一点。 你会有如下代码:

 class MyActivity : Activity(), AnkoLogger { private fun someMethod() { info("This is my first app and it's awesome") debug(1234) warn("Warning") } } 

科特林测井

kotlin-logging( https://github.com/MicroUtils/kotlin-logging )库允许你写如下的日志代码:

 class FooWithLogging { companion object: KLogging() fun bar() { logger.info{"Item $item"} } } 

StaticLog

或者你也可以使用这个小的写在Kotlin库中的名为StaticLog的代码,那么你的代码将如下所示:

 Log.info("This is an info message") Log.debug("This is a debug message") Log.warn("This is a warning message","WithACustomTag") Log.error("This is an error message with an additional Exception for output", "AndACustomTag", exception ) Log.logLevel = LogLevel.WARN Log.info("This message will not be shown")\ 

第二个解决方案可能会更好,如果你想定义一个输出格式的日志记录方法,如:

 Log.newFormat { line(date("yyyy-MM-dd HH:mm:ss"), space, level, text("/"), tag, space(2), message, space(2), occurrence) } 

或使用过滤器,例如:

 Log.filterTag = "filterTag" Log.info("This log will be filtered out", "otherTag") Log.info("This log has the right tag", "filterTag") 

timberkt

如果您已经使用了杰克·沃顿的Timber日志库检查timberkt

这个库建立在Timber上,使用Kotlin更易于使用的API。 不使用格式化参数,而是传递仅在记录消息时评估的lambda表达式。

代码示例:

 // Standard timber Timber.d("%d %s", intVar + 3, stringFun()) // Kotlin extensions Timber.d { "${intVar + 3} ${stringFun()}" } // or d { "${intVar + 3} ${stringFun()}" } 

还请检查: 登录Kotlin和Android:AnkoLogger vs kotlin-logging

希望它会有所帮助

KISS:Java团队迁移到Kotlin

如果你不介意在记录器的每个实例上提供类名(就像java一样),你可以通过将它定义为项目中某个顶级函数来简化它:

 import org.slf4j.LoggerFactory inline fun <reified T:Any> logger() = LoggerFactory.getLogger(T::class.java) 

这使用Kotlin实现类型参数 。

现在,你可以使用这个如下:

 class SomeClass { // or within a companion object for one-instance-per-class val log = logger<SomeClass>() ... } 

这种方法是超级简单的,接近于Java的等价物,只是增加了一些语法糖。

下一步:扩展或代表

我个人更喜欢更进一步,并使用扩展或代表方法。 这在@ JaysonMinard的答案中得到了很好的总结,但是这里是TL;使用log4j2 API的“委托”方法的DR。 由于log4j2与slf4j不同,支持与Supplier的日志记录,我还添加了一个委托使这些方法更简单。

 import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.Logger import org.apache.logging.log4j.util.Supplier import kotlin.reflect.companionObject /** * An adapter to allow cleaner syntax when calling a logger with a Kotlin lambda. Otherwise calling the * method with a lambda logs the lambda itself, and not its evaluation. We specify the Lambda SAM type as a log4j2 `Supplier` * to avoid this. Since we are using the log4j2 api here, this does not evaluate the lambda if the level * is not enabled. */ class FunctionalLogger(val log: Logger): Logger by log { inline fun debug(crossinline supplier: () -> String) { log.debug(Supplier { supplier.invoke() }) } inline fun debug(t: Throwable, crossinline supplier: () -> String) { log.debug(Supplier { supplier.invoke() }, t) } inline fun info(crossinline supplier: () -> String) { log.info(Supplier { supplier.invoke() }) } inline fun info(t: Throwable, crossinline supplier: () -> String) { log.info(Supplier { supplier.invoke() }, t) } inline fun warn(crossinline supplier: () -> String) { log.warn(Supplier { supplier.invoke() }) } inline fun warn(t: Throwable, crossinline supplier: () -> String) { log.warn(Supplier { supplier.invoke() }, t) } inline fun error(crossinline supplier: () -> String) { log.error(Supplier { supplier.invoke() }) } inline fun error(t: Throwable, crossinline supplier: () -> String) { log.error(Supplier { supplier.invoke() }, t) } } /** * A delegate-based lazy logger instantiation. Use: `val log by logger()`. */ @Suppress("unused") inline fun <reified T : Any> T.logger(): Lazy<FunctionalLogger> = lazy { FunctionalLogger(LogManager.getLogger(unwrapCompanionClass(T::class.java))) } // unwrap companion class to enclosing class given a Java Class fun <T : Any> unwrapCompanionClass(ofClass: Class<T>): Class<*> { return if (ofClass.enclosingClass != null && ofClass.enclosingClass.kotlin.companionObject?.java == ofClass) { ofClass.enclosingClass } else { ofClass } } 

那么Class上的扩展函数呢? 这样你最终:

 public fun KClass.logger(): Logger = LoggerFactory.getLogger(this.java) class SomeClass { val LOG = SomeClass::class.logger() } 

注意 – 我没有测试过,所以可能不太对。

首先,您可以为记录器创建添加扩展功能。

 inline fun <reified T : Any> getLogger() = LoggerFactory.getLogger(T::class.java) fun <T : Any> T.getLogger() = LoggerFactory.getLogger(javaClass) 

然后你将能够使用下面的代码创建一个记录器。

 private val logger1 = getLogger<SomeClass>() private val logger2 = getLogger() 

其次,你可以定义一个接口来提供一个记录器及其混合实现。

 interface LoggerAware { val logger: Logger } class LoggerAwareMixin(containerClass: Class<*>) : LoggerAware { override val logger: Logger = LoggerFactory.getLogger(containerClass) } inline fun <reified T : Any> loggerAware() = LoggerAwareMixin(T::class.java) 

这个接口可以用下面的方式。

 class SomeClass : LoggerAware by loggerAware<SomeClass>() { // Now you can use a logger here. } 

一般来说,这就是伴侣对象:替换静态内容。

在这方面我听说没有成语。 越简单越好,所以我会使用顶级属性

 val logger = Logger.getLogger("package_name") 

这种做法在Python中表现良好,与Kotlin和Python可能出现的不同,我相信它们在“精神”(说成语)方面非常相似。

Slf4j的例子,其他人一样。 这甚至可以用来创建包级记录器

 /** * Get logger by current class name. */ fun getLogger(c: () -> Unit): Logger = LoggerFactory.getLogger(c.javaClass.enclosingClass) 

用法:

 val logger = getLogger { }