Kotlin中的惯用日志记录方式


164

Kotlin与Java使用的静态字段概念不同。在Java中,通常公认的日志记录方式是:

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

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


1
不将其发布为答案是因为它与Java方式相去甚远,但是我已经考虑过在Any上编写扩展功能以进行日志记录。当然,您需要缓存Logger,但是我认为这是一种不错的方法。
mhlz 2015年

1
@mhlz扩展函数不能静态解析吗?例如,它不会应用于所有对象,而不会应用于所有类型的对象Any(因此需要强制转换)吗?
吉雷2015年

1
@mhlz扩展功能没有意义,因为它没有保持记录器状态的状态。它可以是返回记录器的扩展,但是为什么在系统中的每个已知类中都有它?以后在IDE中将扩展放在Any上往往会变得草率。@Jire扩展名将适用于Any的所有后代,仍将this.javaClass为每个后裔返回正确的名称。但我不建议将其作为解决方案。
杰森·米纳德

Answers:


250

在大多数成熟的Kotlin代码中,您将在下面找到这些模式之一。使用属性委托的方法利用了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的您好

有关随播对象的更多信息,请参见:随播对象 ...还应注意,在上面的示例中,它MyClass::class.java获取Class<MyClass>记录器的类型实例,而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.foo信息:您好!

2015年12月26日,上午11:30:55 org.stackoverflow.kotlin.test.SomethingElse foo信息:SomethingElse的问好

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

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 INFO:Hello from 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()方法确保我们不会生成以伴随对象命名的记录器,而是生成封闭的类。这是当前推荐的查找包含伴随对象的类的方法。从名称中删除“ $ CompanionremoveSuffix()不起作用,因为可以为伴随对象指定自定义名称。


某些依赖项注入框架使用委托,就像您在此处的另一个答案中看到的那样。它们看起来像val日志:通过injectLogger()进行日志记录,并允许注入日志记录系统,而使用代码未知。(我的注入框架表示这是在github.com/kohesive/injekt
杰森米纳德

10
感谢您的广泛回答。非常丰富。我特别喜欢 Property Delegates(常见,最优雅)的实现。
mchlstckl 2015年

6
我认为Kotlin语法有所变化。而应该ofClass.enclosingClass.kotlin.objectInstance?.javaClass改用解包ofClass.enclosingClass.kotlin.companionObject?.java
oshai

1
啊,没关系,如此处所述kotlinlang.org/docs/reference/reflection.html反射罐与stdlib分开运输,要进行摇动,我们需要这样做:compile 'org.jetbrains.kotlin:kotlin-reflect:1.0.2'
Hoang Tran

1
除了返回类型外,用于创建“属性委托”和“扩展功能”的代码似乎相同。属性委托(public fun <R : Any> R.logger(): Lazy<Logger> { return lazy{Logger.getLogger(unwrapCompanionClass(this.javaClass).name)}})的代码示例似乎可以创建扩展功能,例如"".logger()成为一种事物,这应该以这种方式表现吗?
Mike Rylander

32

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

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


我建议你修改你的答案,显示输出logger.info()呼叫,杰森在他接受的答案一样。
Paulo Merson

7

作为记录实现的一个很好的例子,我想提到使用特殊接口的AnkoAnkoLogger,需要记录的类应该实现该。在界面内部,有代码为该类生成日志记录标记。然后通过扩展功能完成记录,这些功能可以在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")
}

这个答案是特定于Android的,该问题未提及也没有Android标签。
杰森·米纳德

@JaysonMinard为什么?这种方法是通用的,因为例如对于每个类都有唯一的日志记录标记在非Android项目中也很有用。
热键2015年

1
不清楚您是在说“实现与Anko相似的功能”,而是看起来更像是“ use Anko”……然后需要一个名为Anko的Android库。具有接口的扩展功能可以调用android.util.Log日志记录。你的意图是什么?使用Anko?以Anko为例进行类似的构建(最好将内联建议的代码并针对非Android进行修复,而不是说“将其移植到非Android,这是链接”。)而是添加示例代码打电话给Anko)
Jayson Minard,2015年

1
@JaysonMinard,感谢您的评论,我将帖子重写了,以便现在说明方法而不是引用Anko。
热键2015年

6

吻:对于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的答案对此进行了很好的总结,但这是使用log4j2 API的“委托”方法的TL; 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 <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
  }
}

Log4j2 Kotlin日志记录API

上一节的大部分内容已经直接改编为产生Kotlin Logging API模块,该模块现在是Log4j2的正式部分(免责声明:我是第一作者)。您可以直接从Apache或通过Maven Central下载

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


1
如果我是对的,则可以通过将方法签名更改为T.logger()来避免在提供的第一个解决方案中键入类名称
IPat

1
@IPat是的,第一个解决方案故意不这样做,以保持与“ java方式”接近。答案的第二部分涵盖扩展名的情况T.logger()-请参见代码示例的底部。
拉曼

5

安科

您可以使用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项目-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

希望对你有帮助


4

这样的事情对您有用吗?

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() }
}

1
这个答案需要更多的解释,如果询问的人不了解伴侣对象,那么他们可能还没有到达委托对象,因此将不知道自己在做什么。另外,使用此模型几乎没有代码节省。而且我怀疑在同伴对象中的缓存是否确实会提高性能,而不是在具有小型CPU(例如Android)的受限系统中。
杰森·米纳德

1
上面的代码显示的是创建一个充当委托的类(请参阅kotlinlang.org/docs/reference/delegated-properties.html),这是第一个类LoggerDelegate ,然后它创建了一个顶层函数,创建委托的实例更加容易(不是那么容易,但是有点儿)。并且该功能应更改为inline。然后,只要需要,它就使用委托人提供一个记录器。但是它为同伴Foo.Companion而不是为班级提供了一个,Foo因此可能不是预期的。
杰森·米纳德

@JaysonMinard我同意,但我会将答案留给希望“快速修复”或如何将其应用于自己的项目的示例的未来观众。我不明白如果没有lambda的话,为什么logger()函数应该是inline这样。IntelliJ建议在这种情况下不必进行内联:i.imgur.com/YQH3NB1.png
Jire,2015年

1
我将您的答案纳入了我的答案,并通过删除自定义委托类简化了它,并使用了包装器Lazy。通过技巧让它知道它属于哪个类。
杰森·米纳德

1

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

val logger = Logger.getLogger("package_name")

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


顶层也称为包级别。
Caelum 2015年

顶层变量就像说“使用全局变量”,我认为只有在您需要使用记录器的其他顶层函数时,该变量才适用。但是,此时最好将记录器传递给任何要记录的实用程序函数。
杰森·米纳德

1
@JaysonMinard我认为将logger作为参数传递将是一种反模式,因为您的日志记录永远不会影响您的API(外部或内部)
voddan 2015年

好的,然后回到我的观点,对于类级别的日志记录,将记录器放在类中,而不是顶级函数中。
杰森·米纳德

1
@voddan至少提供您要创建哪种类型的记录器的完整示例。 val log = what?!? ...通过名称创建记录器?忽略了这个问题表明他想为特定班级创建记录器的事实LoggerFactory.getLogger(Foo.class);
Jayson Minard,2015年

1

那类的扩展函数呢?这样,您最终得到:

public fun KClass.logger(): Logger = LoggerFactory.getLogger(this.java)

class SomeClass {
    val LOG = SomeClass::class.logger()
}

注意-我根本没有测试过,所以可能不太正确。


1

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

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()

其次,您可以定义一个提供记录器及其mixin实现的接口。

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.
}

1

创建伴随对象并使用@JvmStatic批注标记适当的字段


1

这里已经有很多不错的答案,但是所有这些答案都涉及到在类中添加记录器,但是您将如何做才能在顶级函数中进行记录呢?

这种方法通用且简单,足以在两个类,伴随对象和顶级函数中正常工作:

package nieldw.test

import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
import org.junit.jupiter.api.Test

fun logger(lambda: () -> Unit): Lazy<Logger> = lazy { LogManager.getLogger(getClassName(lambda.javaClass)) }
private fun <T : Any> getClassName(clazz: Class<T>): String = clazz.name.replace(Regex("""\$.*$"""), "")

val topLog by logger { }

class TopLevelLoggingTest {
    val classLog by logger { }

    @Test
    fun `What is the javaClass?`() {
        topLog.info("THIS IS IT")
        classLog.info("THIS IS IT")
    }
}

0

通常,这就是伴随对象的用途:替换静态内容。


伴随对象不是静态对象,它是一个单例对象,可以容纳成员(如果使用JvmStatic注释),成员可能变为静态。将来可能会有不止一个。另外,如果没有更多信息或样本,此答案不是很有帮助。
杰森·米纳德

我不是说这是静态的。我说这是为了取代静电。为何会有不止一个?那没有道理。最后,我很着急,我认为指向正确的方向会有所帮助。
Jacob Zimmerman 2015年

1
伴随对象不用于替换静态对象,但也可以使它的元素变为静态对象。Kotlin不仅在同伴上支持了一段时间,还允许他们使用其他名字。一旦开始命名它们,它们的行为就不像静态函数。将来会有不只一个命名的同伴而开放。例如,一个可能是Factory与另Helpers
杰森米纳德

0

Slf4j示例,其他示例相同。这甚至适用于创建包级别记录器

/**  
  * Get logger by current class name.  
  */ 

fun getLogger(c: () -> Unit): Logger = 
        LoggerFactory.getLogger(c.javaClass.enclosingClass)

用法:

val logger = getLogger { }

0
fun <R : Any> R.logger(): Lazy<Logger> = lazy { 
    LoggerFactory.getLogger((if (javaClass.kotlin.isCompanion) javaClass.enclosingClass else javaClass).name) 
}

class Foo {
    val logger by logger()
}

class Foo {
    companion object {
        val logger by logger()
    }
}


0

您可以简单地构建自己的实用程序“库”。您不需要大型库来执行此任务,这会使您的项目更重,更复杂。

例如,您可以使用Kotlin Reflection获取任何类属性的名称,类型和值。

首先,确保在build.gradle中解决了元依赖问题:

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
}

之后,您可以简单地将此代码复制并粘贴到您的项目中:

import kotlin.reflect.full.declaredMemberProperties

class LogUtil {
    companion object {
        /**
         * Receives an [instance] of a class.
         * @return the name and value of any member property.
         */
        fun classToString(instance: Any): String {
            val sb = StringBuilder()

            val clazz = instance.javaClass.kotlin
            clazz.declaredMemberProperties.forEach {
                sb.append("${it.name}: (${it.returnType}) ${it.get(instance)}, ")
            }

            return marshalObj(sb)
        }

        private fun marshalObj(sb: StringBuilder): String {
            sb.insert(0, "{ ")
            sb.setLength(sb.length - 2)
            sb.append(" }")

            return sb.toString()
        }
    }
}

用法示例:

data class Actor(val id: Int, val name: String) {
    override fun toString(): String {
        return classToString(this)
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.