Log4j Kotlin API

Log4j Kotlin API 提供了一个 Kotlin 友好的接口,用于针对 Log4j API 进行日志记录。最低要求是 Java 8 和 Kotlin 1.6.21

这只是一个日志记录 API。您的应用程序仍然需要配置一个日志记录后端(例如,Log4j)。

依赖项

您需要在类路径中包含 org.apache.logging.log4j:log4j-api-kotlin 依赖项

<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-api-kotlin</artifactId>
  <version>1.5.0-SNAPSHOT</version>
</dependency>

Java 模块名称和 OSGi Bundle-SymbolicName 设置为 org.apache.logging.log4j.api.kotlin

创建日志记录器

Logger 是用户与 Log4j Kotlin 交互的主要接口。您可以通过两种方式创建 Logger

创建类日志记录器

对于大多数应用程序,我们建议您为每个类定义创建一个单一的日志记录器实例,而不是 每个类实例!这不仅避免了为每个实例创建额外的日志记录器字段,其访问模式还透明地传达了实现:Logger 静态绑定到类定义。您可以通过以下几种方式创建类日志记录器

在伴随对象中创建日志记录器

这是创建类日志记录器的传统方法。它也是最有效的方法,因为日志记录器查找只执行一次,其结果存储在由该类所有实例共享的伴随对象中。

import org.apache.logging.log4j.kotlin.logger

class DbTableService {

  companion object {

    private val LOGGER = logger() (1)

  }

  fun truncateTable(tableName: String) {
    LOGGER.warn { "truncating table `${tableName}`" }
    db.truncate(tableName)
  }

}
1 创建一个与所有类实例共享的静态类定义关联的 Logger

Logging 扩展伴随对象

Logging 接口包含一个 logger getter,您可以通过从 Logging 类扩展伴随对象来使用它

import org.apache.logging.log4j.kotlin.Logging

class DbTableService {

  companion object: Logging (1)

  fun truncateTable(tableName: String) {
    logger.warn { "truncating table `${tableName}`" }
    db.truncate(tableName)
  }

}
1 Logging 扩展伴随对象实际上创建了一个单一的 Logger 实例
  1. 分配给 logger 字段

  2. 与所有类实例共享的静态类定义关联

这种基于 getter 的方法会产生额外的开销(与 在伴随对象中创建日志记录器 相比),因为在运行时涉及日志记录器查找。

创建实例日志记录器

虽然我们建议您 创建类日志记录器,但在某些情况下(最显著的是在 Jakarta EE 环境中共享类 时),可能需要与每个实例关联的日志记录器。您可以通过以下方式实现这一点

在类中创建日志记录器

这是创建实例日志记录器的传统方法。它也是最有效的方法,因为日志记录器查找只执行一次,其结果存储在实例字段中。

import org.apache.logging.log4j.kotlin.logger

class DbTableService {

  private val logger = logger() (1)

  fun truncateTable(tableName: String) {
    logger.warn { "truncating table `${tableName}`" }
    db.truncate(tableName)
  }

}
1 创建一个与类实例关联的 Logger

Logging 扩展类

Logging 接口包含一个 logger getter,您可以通过从 Logging 扩展类来使用它

import org.apache.logging.log4j.kotlin.Logging

class DbTableService: Logging { (1)

  fun truncateTable(tableName: String) {
    logger.warn { "truncating table `${tableName}`" }
    db.truncate(tableName)
  }

}
1 Logging 扩展类实际上创建了一个单一的 Logger 实例
  1. 分配给 logger 字段

  2. 专门与类实例关联(即,不在实例之间共享!)

这种基于 getter 的方法会产生额外的开销(与 在类中创建日志记录器 相比),因为在运行时涉及日志记录器查找。

使用 logger 扩展属性

您可以使用 logger 扩展属性在现场动态注入日志记录器

import org.apache.logging.log4j.kotlin.logger

class DbTableService {

  fun truncateTable(tableName: String) {
    logger.warn { "truncating table `${tableName}`" } (1)
    db.truncate(tableName)
  }

}
1 logger 将查找与封装类的 Logger 实例关联的实例

这种基于 getter 的方法会产生额外的开销(与 在类中创建日志记录器 相比),因为在运行时涉及日志记录器查找。

线程上下文

ThreadContext API 提供了两个外观对象:ContextMapContextStack

import org.apache.logging.log4j.kotlin.ContextMap
import org.apache.logging.log4j.kotlin.ContextStack

ContextMap["key"] = "value"
assert(ContextMap["key"] == "value")
assert("key" in ContextMap)

ContextMap += "anotherKey" to "anotherValue"
ContextMap -= "key"

ContextStack.push("message")
assert(!ContextStack.empty)
assert(ContextStack.depth == 1)
val message = ContextStack.peek()
assert(message == ContextStack.pop())
assert(ContextStack.empty)

提供了一个 CoroutineThreadContext 上下文元素,用于将日志记录上下文与协程集成。

我们提供了便利函数 loggingContextadditionalLoggingContext,用于创建具有适当上下文数据的 CoroutineThreadContext 实例。这些函数的结果可以直接传递给协程构建器,以设置协程的上下文。

要设置上下文,忽略当前作用域中的任何上下文

launch(loggingContext(mapOf("myKey" to "myValue"), listOf("test"))) {
  assertEquals("myValue", ContextMap["myKey"])
  assertEquals("test", ContextStack.peek())
}

或者保留现有上下文并添加额外的日志记录上下文

launch(additionalLoggingContext(mapOf("myKey" to "myValue"), listOf("test"))) {
  assertEquals("myValue", ContextMap["myKey"])
  assertEquals("test", ContextStack.peek())
}

或者,要更改上下文而不启动新的协程,提供了 withLoggingContextwithAdditionalLoggingContext 函数

withAdditionalLoggingContext(mapOf("myKey" to "myValue"), listOf("test")) {
  assertEquals("myValue", ContextMap["myKey"])
  assertEquals("test", ContextStack.peek())
}

这些函数是 withContext(loggingContext(…​))withContext(additionalLoggingContext(…​)) 的简写。

参数替换

与 Java 不同,Kotlin 提供了对 字符串模板 的原生功能。但是,如果日志记录器级别未启用,使用字符串模板仍然会产生消息构造成本。为了避免这种情况,最好传递一个 lambda,它只有在必要时才会被评估

logger.debug { "Logging in user ${user.name} with birthday ${user.calcBirthday()}" }

日志记录器名称

大多数日志记录实现使用分层方案来匹配日志记录器名称与日志记录配置。

在此方案中,日志记录器名称层次结构由日志记录器名称中的 .(点)字符表示,其方式与用于 Java/Kotlin 包名称的层次结构非常相似。Logging 接口添加的 Logger 属性遵循此约定:该接口确保 Logger 自动根据其使用所在的类进行命名。

调用 logger() 扩展方法时返回的值取决于扩展的接收器。当在对象内调用时,接收器是 this,因此日志记录器将再次根据其使用所在的类进行命名。但是,也可以获得通过另一个类命名的日志记录器

import org.apache.logging.log4j.kotlin

class MyClass: BaseClass {

  val logger = SomeOtherClass.logger()

  // ...

}

显式命名的日志记录器

可以通过接受 name 参数的 logger 函数获得显式命名的日志记录器

import org.apache.logging.log4j.kotlin

class MyClass: BaseClass {

  val logger = logger("MyCustomLoggerName")

  // ...

}

这也需要在没有 this 对象的作用域中,例如顶级函数。