ChatGPT解决这个技术问题 Extra ChatGPT

Kotlin 的惯用登录方式

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

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

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

没有将此作为答案发布,因为它与 Java 方式相去甚远,但我考虑过在 Any 上编写一个扩展函数以进行日志记录。当然,您需要缓存 Logger,但我认为这是一个不错的方法。
@mhlz 扩展功能不会被静态解析吗?例如,它不会应用于所有对象,仅应用于 Any 类型的对象(因此需要强制转换)?
@mhlz 扩展功能没有意义,因为它没有状态来保留记录器。它可能是返回记录器的扩展,但为什么系统中的每个已知类都有它呢?将扩展放在 Any 上往往会在以后在 IDE 中变得草率。 @Jire 扩展将适用于 Any 的所有后代,仍然会为每个后代返回正确的 this.javaClass。但我不建议将其作为解决方案。

2
26 revs, 4 users 98%

在大多数成熟的 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 你好

更多关于伴随对象的信息:Companion Objects ... 另请注意,在上面的示例中,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 委托,因此有一种更简单的方法可以做到这一点,我们可以将它包装在一个函数中。这里的一个技巧是,如果我们想知道当前使用委托的类的类型,我们将其作为任何类的扩展函数:

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

基本上,该代码被视为对扩展 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() 方法确保我们不会生成以伴随对象命名的记录器,而是生成以封闭类命名的记录器。这是当前推荐的查找包含伴随对象的类的方法。使用 removeSuffix() 从名称中去除“$Companion”不起作用,因为可以为伴随对象指定自定义名称。


一些依赖注入框架使用委托,就像您在此处的另一个答案中看到的那样。它们看起来像 `val log: Logger by injectLogger()` 并允许注入日志系统并且使用代码不知道。 (我的注入框架显示在 github.com/kohesive/injekt
感谢您的广泛回答。信息量很大。我特别喜欢 Property Delegates(常见的、最优雅的)实现。
我认为 kotlin 语法发生了变化。并且展开应该是 ofClass.enclosingClass.kotlin.objectInstance?.javaClass 而不是 ofClass.enclosingClass.kotlin.companionObject?.java
啊,没关系,正如这里所说的 kotlinlang.org/docs/reference/reflection.html,反射 jar 与 stdlib 分开发货,对于 gradle,我们需要这个:compile 'org.jetbrains.kotlin:kotlin-reflect:1.0.2'
除了返回类型之外,创建“属性委托”和“扩展函数”的代码似乎相同。属性委托 (public fun <R : Any> R.logger(): Lazy<Logger> { return lazy{Logger.getLogger(unwrapCompanionClass(this.javaClass).name)}}) 的代码示例似乎创建了一个扩展函数,使得 "".logger() 现在是一个东西,这应该是这样吗?
o
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 进行比较:Logging in Kotlin & Android: AnkoLogger vs kotlin-logging

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

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


我是否可以建议您编辑您的答案以显示 logger.info() 调用的输出,就像 Jayson 在他接受的答案中所做的那样。
R
Raman

KISS:适用于迁移到 Kotlin 的 Java 团队

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

import org.slf4j.LoggerFactory

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

这使用了 Kotlin reified type parameter

现在,您可以按如下方式使用它:

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

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

下一步:扩展或委托

我个人更喜欢更进一步并使用扩展或委托方法。 @JaysonMinard 的回答很好地总结了这一点,但这里是 TL;DR 用于使用 log4j2 API 的“委托”方法(更新:不再需要手动编写此代码,因为它已经作为 log4j2 项目的官方模块发布,见下文)。由于 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
  }
}

Log4j2 Kotlin 日志记录 API

前一部分的大部分内容已直接改编为 Kotlin Logging API 模块,该模块现已成为 Log4j2 的官方部分(免责声明:我是主要作者)。您可以下载此 directly from Apache,或通过 Maven Central

Usage 基本上如上所述,但该模块支持基于接口的记录器访问,Any 上的 logger 扩展函数用于定义 this 的情况,以及命名记录器函数用于未定义 {4 } 被定义(例如顶级函数)。


如果我是对的,您可以通过将方法签名更改为 T.logger() 来避免在您提供的第一个解决方案中输入类名
@IPat yup,第一个解决方案故意不这样做以保持接近“java方式”。答案的第二部分涵盖了扩展案例 T.logger() - 请参阅代码示例的底部。
h
hotkey

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

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

AnkoLogger

首先,有一个行为类似于标记接口的接口:

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

作为 handlerthrowableHandler 传递的函数对于不同的日志记录方法可能有所不同,例如,它们可以将日志写入文件或将其上传到某处。为简洁起见,省略了 isLoggingEnabledLoggingLevels,但使用它们可以提供更大的灵活性。

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

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

private object MyPackageLog : MyLogger

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

这个答案是 Android 特定的,问题没有提到也没有 Android 标签。
@JaysonMinard 为什么会这样?这种方法是通用的,例如,为每个类设置一个唯一的日志标记在非 Android 项目中也很有用。
不清楚您是在说“实现类似于 Anko 所做的事情”,而更像是“使用 Anko”……这需要一个名为 Anko 的 Android 库。它有一个接口,该接口具有调用 android.util.Log 进行日志记录的扩展函数。你的意图是什么?用安科?在使用 Anko 作为示例时构建类似的东西(最好将建议的代码内联并为非 Android 修复它,而不是说“将此移植到非 Android,这是链接”。相反,您添加示例代码打电话给安科)
@JaysonMinard,感谢您的评论,我重写了这篇文章,现在它解释了这种方法,而不是引用 Anko。
P
Prags

安科

您可以使用 Anko 库来执行此操作。您将拥有如下代码:

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

kotlin 日志记录

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

木材

如果您已经使用过 Jake Wharton 的 Timber 日志库,请检查 timberkt

该库基于 Timber 构建,其 API 在 Kotlin 中更易于使用。您不使用格式化参数,而是传递一个仅在记录消息时才评估的 lambda。

代码示例:

// Standard timber
Timber.d("%d %s", intVar + 3, stringFun())

// Kotlin extensions
Timber.d { "${intVar + 3} ${stringFun()}" }
// or
d { "${intVar + 3} ${stringFun()}" }

还要检查:Logging in Kotlin & Android: AnkoLogger vs kotlin-logging

希望它会有所帮助


J
Jire

像这样的东西对你有用吗?

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

这个答案需要更多解释,如果问的人不理解同伴对象,他们可能还没有得到委托,因此不知道这是在做什么。此外,使用此模型可以节省很少的代码。而且我怀疑伴随对象中的缓存是否真的是一种性能提升,而不是在具有小 CPU 的受限系统(如 Android)中。
上面的代码显示的是创建一个充当代理的类(参见 kotlinlang.org/docs/reference/delegated-properties.html),它是第一个类 LoggerDelegate 然后它正在创建一个顶级函数,这使得创建一个实例变得更加容易代表(不是更容易,但有一点)。并且该函数应更改为 inline。然后它使用委托在需要时提供一个记录器。但它为同伴 Foo.Companion 而不是为类 Foo 提供了一个,因此可能与预期不符。
@JaysonMinard 我同意,但我会将答案留给想要“快速修复”或如何将其应用于自己的项目的示例的未来观众。如果没有 lambda,我不明白为什么 logger() 函数应该是 inline。 IntelliJ 建议在这种情况下不需要内联:i.imgur.com/YQH3NB1.png
我将您的答案合并到我的答案中,并通过删除自定义委托类来简化它,并改用 Lazy 周围的包装器。用一个技巧让它知道它属于哪个类。
G
Graham

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

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

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

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


M
Michael

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

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

c
cleaning agent

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


N
Niel de Wet

这里已经有很多很好的答案,但是它们都涉及向类添加记录器,但是您将如何在顶级函数中进行记录呢?

这种方法是通用且简单的,可以在类、伴生对象和顶级函数中很好地工作:

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

x
xdevs23

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

val logger = Logger.getLogger("package_name")

这种做法在 Python 中很好用,尽管 Kotlin 和 Python 可能看起来不同,但我相信它们的“精神”(说到成语)非常相似。


顶级也称为包级。
顶级变量就像说“使用全局变量”,我认为只有当您有其他需要使用记录器的顶级函数时才适用。不过,此时最好将记录器传递给任何想要记录的实用程序函数。
@JaysonMinard 我认为将 logger 作为参数传递将是一种反模式,因为您的日志记录永远不会影响您的 API,无论是外部的还是内部的
好的,然后回到我的观点,对于类级别的日志记录,将记录器放在类中,而不是顶级函数。
@voddan 至少提供了您正在创建的记录器类型的完整示例。 val log = what?!? ... 按名称创建记录器?忽略问题表明他想为特定类创建记录器的事实LoggerFactory.getLogger(Foo.class);
J
Jacob Zimmerman

一般来说,这就是伴随对象的用途:替换静态的东西。


伴随对象不是静态的,它是一个可以容纳成员的单例,如果您使用 JvmStatic 注释,这些成员可能会变为静态。将来可能会允许不止一个。另外,如果没有更多信息或样本,这个答案并不是很有帮助。
我没有说它是静态的。我说这是为了替换静力学。为什么会允许不止一个?那没有意义。最后,我很着急,我认为指出正确的方向会很有帮助。
伴随对象不用于替换静态,但它也可以使其元素成为静态。 Kotlin 支持的不止是同伴,并且允许它们有其他名称。一旦你开始命名它们,它们的行为就不像静态了。将来有多个命名的同伴是开放的。例如,一个可能是 Factory,另一个可能是 Helpers
H
Hsiafan

Slf4j 示例,对其他人也是如此。这甚至适用于创建包级记录器

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

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

用法:

val logger = getLogger { }

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

L
Leandro

这仍然是 WIP(几乎完成)所以我想分享它:https://github.com/leandronunes85/log-format-enforcer#kotlin-soon-to-come-in-version-14

这个库的主要目标是在整个项目中强制执行某种日志样式。通过让它生成 Kotlin 代码,我试图解决这个问题中提到的一些问题。关于最初的问题,我通常倾向于做的是简单地:

private val LOG = LogFormatEnforcer.loggerFor<Foo>()
class Foo {

}

r
rocammo

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

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

首先,确保在 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)
    }
}

R
Reed Ellsworth

对于 Kotlin Multiplaform 日志记录,我找不到一个包含我需要的所有功能的库,所以我最终写了一个。请查看KmLogging。它实现的功能是:

在每个平台上使用特定于平台的日志记录:在 Android 上登录,在 iOS 上使用 os_log,在 JavaScript 上使用控制台。

高性能。禁用时只有 1 个布尔检查。我喜欢放入大量的日志记录,并希望在发布时将其全部关闭,并且不想为拥有大量日志记录付出太多开销。此外,当日志记录打开时,它需要非常高效。

可扩展。需要能够添加其他记录器,例如记录到 Crashlytics 等。

每个记录器都可以在不同的级别进行记录。例如,您可能只希望将信息及以上信息转到 Crashlytics 以及在生产中禁用的所有其他记录器。

要使用:

val log = logging()
log.i { "some message" }