当前位置:  开发笔记 > 编程语言 > 正文

在Kotlin登录的惯用方法

如何解决《在Kotlin登录的惯用方法》经验,为你挑选了5个好方法。

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

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

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



1> Jayson Minar..:

在大多数成熟的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 INFO:来自MyClass的你好

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

每类实例(常见)

但是,实际上没有理由避免在实例级别调用和获取记录器.您提到的惯用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:来自MyClass的Hello

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

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

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

fun  R.logger(): Lazy {
    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的Hello

扩展函数(在这种情况下由于任何命名空间的"污染"而不常见)

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

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

基本上,代码被视为对扩展的调用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 INFO:来自MarkedClass的Hello

如果您想强制创建统一字段来保存记录器,那么在使用此接口时,您可以轻松地要求实现者拥有如下字段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  logger(forClass: Class): Logger {
    return Logger.getLogger(unwrapCompanionClass(forClass).name)
}

// unwrap companion class to enclosing class given a Java Class
fun  unwrapCompanionClass(ofClass: Class): Class<*> { 
   return ofClass.enclosingClass?.takeIf { 
      ofClass.enclosingClass.kotlin.companionObject?.java == ofClass 
   } ?: ofClass 
}

// unwrap companion class to enclosing class given a Kotlin Class
fun  unwrapCompanionClass(ofClass: KClass): KClass<*> {
   return unwrapCompanionClass(ofClass.java).kotlin
}

// Return logger for Kotlin class
fun  logger(forClass: KClass): Logger {
    return logger(forClass.java)
}

// return logger from extended class (or the enclosing class)
fun  T.logger(): Logger {
    return logger(this.javaClass)
}

// return a lazy logger property delegate for enclosing class
fun  R.lazyLogger(): Lazy {
    return lazy { logger(this.javaClass) }
}

// return a logger property delegate for enclosing class
fun  R.injectLogger(): Lazy {
    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 INFO:来自MixedBagOfTricks的Hello

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


感谢您的广泛答复.非常翔实.我特别喜欢**Property Delegates(常见,最优雅)**实现.
我认为kotlin语法有变化.unwrap应该是`ofClass.enclosingClass.kotlin.objectInstance?.javaClass`而不是`ofClass.enclosingClass.kotlin.companionObject?.java`

2> oshai..:

看看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:登录Kotlin和Android:AnkoLogger vs kotlin-logging

免责声明:我是该库的维护者.

编辑:kotlin-logging现在有多平台支持:https://github.com/MicroUtils/kotlin-logging/wiki/Multiplatform-support



3> hotkey..:

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

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


下面的代码基本上是AnkoLogger,为Android无关的用法进行了简化和重写.

首先,有一个界面,其行为类似于标记界面:

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

它允许它的实现使用扩展函数MyLogger在其代码内部调用它们this.它还包含日志标记.

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

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参数完成的.

这是因为传递的功能handler,并throwableHandler可以针对不同测井方法不同,例如,它们可以写入文件或上传某处日志.isLoggingEnabledLoggingLevels不再赘述,但使用起来提供了更大的灵活性.


它允许以下用法:

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

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

private object MyPackageLog : MyLogger

fun myFun() {
    MyPackageLog.info("Info message")
}



4> Raman..:
KISS:为Java团队迁移到Kotlin

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

import org.slf4j.LoggerFactory

inline fun  logger() = LoggerFactory.getLogger(T::class.java)

这使用Kotlin reified类型参数.

现在,您可以使用如下:

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

这种方法非常简单并且接近java等价物,但只是增加了一些语法糖.

下一步:扩展或代表

我个人更喜欢更进一步,使用扩展或委托方法.这在@ JaysonMinard的答案中得到了很好的总结,但这里是TL;使用log4j2 API的"委托"方法的DR(更新:不再需要手动编写此代码,因为它已作为官方模块发布) log4j2项目,见下文).由于log4j2与slf4j不同,支持使用Suppliers进行日志记录,因此我还添加了一个委托,以便更简单地使用这些方法.

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  T.logger(): Lazy =
  lazy { FunctionalLogger(LogManager.getLogger(unwrapCompanionClass(T::class.java))) }

// unwrap companion class to enclosing class given a Java Class
fun  unwrapCompanionClass(ofClass: Class): Class<*> {
  return if (ofClass.enclosingClass != null && ofClass.enclosingClass.kotlin.companionObject?.java == ofClass) {
    ofClass.enclosingClass
  } else {
    ofClass
  }
}
Log4j2 Kotlin Logging API

上一节的大部分内容都直接适用于生成Kotlin Logging API模块,该模块现在是Log4j2的官方部分(免责声明:我是主要作者).您可以直接从Apache或Maven Central下载.

用法基本上如上所述,但该模块既支持基于接口的记录器访问,也支持在定义时使用的logger扩展功能,Any以及在this未定义的情况下使用的命名记录器功能this(例如顶级功能).



5> piotrek1543..:

安科

您可以使用Anko库来做到这一点。您将具有以下代码:

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

科特林测井

kotlin-logging(Github project-kotlin-logging)库允许您编写如下的日志记录代码:

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

静态日志

或者,您也可以使用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

如果您已经使用过Jake Wharton的Timber日志记录库check 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与kotlin-logging

希望对你有帮助

推荐阅读
ar_wen2402851455
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有