ITADN
seancorfield/logging4j2
seancorfield/logging4j2 · 文件 下载 ZIP
文件最后提交记录最后更新时间
README.md
以下内容由 AI 翻译,如有问题请点此提交 issue 反馈

com.github.seancorfield/logging4j2

Clojars cljdoc

一个用于 log4j2 的 Clojure 封装库,旨在作为 clojure.tools.logging 的部分替代品, 直接支持 MDC(映射诊断上下文)、NDC(嵌套诊断上下文) 和标记,并以符合 Clojure 习惯的方式提供。

该库显式依赖于 log4j2 以及所有将其他日志框架路由到 log4j2 的桥接库 (jcl、jul、log4j 1.x、slf4j 1.x 和 2.x)。

注意:需要 Clojure 1.11 或更高版本。

我的 AI 使用声明

安装

将以下依赖项添加到您的 deps.edn 文件中:

com.github.seancorfield/logging4j2 {:mvn/version "1.0.9"}

此版本的库依赖于 log4j2 2.26.1,这是截至 2026 年 5 月的最新稳定版本。

用法

引入主 org.corfield.logging4j2 命名空间(:as 任意你喜欢的别名 -- 以下示例使用 logger):

该库提供以下宏,可用于记录信息: tracedebuginfowarnerrorfatal。此外还有一个 通用的 log 宏,其第一个参数接受一个级别关键字。

(logger/info "Hello, world!")
(logger/log :info "Hello, world!") ; equivalent to the above

对 MDC 和 NDC 的支持通过 with-log-contextwith-log-tagwith-log-uuid 宏提供(有关更多详细信息,请参阅下文 MDC and NDC):

  • with-log-context 接受一个哈希映射作为其第一个参数,后跟要执行的代码主体。哈希映射中的键和值将作为字符串添加到上下文中,用于执行主体。
  • with-log-tag 接受一个关键字或字符串作为其第一个参数,后跟要执行的代码主体。该标签被推送到上下文中,用于执行主体。
  • with-log-uuid 接受要执行的代码主体。一个唯一的标签被推送到上下文中,用于执行主体。
(logger/with-log-context {:uid (:id user)}
  (logger/info "Hello, world!")) ; INFO {uid=1234} Hello, world!

(logger/with-log-tag (str "user_" (:id user))
  (logger/info "Hello, world!")) ; INFO [user_1234] Hello, world!

(logger/with-log-uuid
  (logger/info "Hello, world!")) ; is equivalent to
(logger/with-log-tag (str (random-uuid))
  (logger/info "Hello, world!")) ; INFO [8b21769c-33c5-42cb-b6c4-146ce8bb875f] Hello, world!

对标记的支持由 as-marker 函数提供,该函数接受一个或多个关键字或字符串,并返回一个标记对象,该对象可以作为任何日志宏的第一个参数(或通用 log 宏的第二个参数)传递。

(as-marker :sql-update :sql) 返回一个标记对象,该对象表示字符串 "SQL_UPDATE",其父级为标记 "SQL"

可能的参数

大多数日志记录看起来像这样:

(logger/info "some message")
;; or
(logger/info my-exception "some message")
;; or
(logger/info my-marker "some message")
;; or
(logger/info my-marker my-exception "some message")

如果提供了多个消息参数,它们通常会被转换为字符串,并在它们之间用空格连接,但以下情况除外:

  • 如果第一个消息参数是字符串,并且包含一个或多个 {} 占位符,则其余参数被视为这些占位符的值,并 构造一个 ParameterizedMessage
  • 如果只提供了一个消息参数,且它是一个哈希映射,则 构造一个 MapMessage ,其中 Clojure 哈希映射的键被转换为字符串, 值保持不变。

你可以直接使用 logger/as-message 构建一个 Message

(logger/as-message "Hello, {}!" "Parameter")
;; => ParameterizedMessage
(logger/as-message {:hello "world"})
;; => MapMessage
(logger/as-message "Hello," "World!")
;; => SimpleMessage

如果您使用的是 Clojure 1.12(或更高版本),您可以提供一个 (fn [] ...), 它将用作 MessageSupplier 对象:

(logger/info #(logger/as-message "Hello, Supplier!"))

在 Clojure 1.11 中,你必须直接构造 MessageSupplier 对象, 例如通过实现 MessageSupplier 接口并使用 reify 来提供 get 方法:

(logger/info (reify org.apache.logging.log4j.message.MessageSupplier
               (get [_] (logger/as-message "Hello, Supplier!"))))

MDC 和 NDC

映射诊断上下文 (MDC) 和嵌套诊断上下文 (NDC) 由三个 with-log-* 宏支持 (如上所述)。对这些宏的嵌套调用 将在当前线程内自动累积映射上下文和标签栈。

log4j2 的底层上下文默认是线程本地的:每个线程 初始时具有空的上下文。该库还使用 Clojure 中的动态 var 来跟踪 MDC 和 NDC,因此你可以在生成的 线程内使用 with-log-inherited 来继承父线程的 MDC 和 NDC。

(logger/with-log-context {:uid (:id user)}
  (future
    (logger/with-log-inherited
      (logger/info "Hello, world!")))) ; INFO {uid=1234} Hello, world!

如果你在线程之间传递函数,你可能需要使用 bound-fnbound-fn* 以便将动态上下文传递到新的线程。

注意:即使子线程中存在未继承上下文的 with-log-contextwith-log-tagwith-log-uuid 的中间调用,with-log-inherited 也会从动态父上下文继承整个 MDC/NDC。

配置

Log4j2 非常灵活,可以通过 XML、JSON、YAML 或 properties 文件进行配置。请参阅 log4j2 配置文档

配置文件可以位于类路径上(例如,在你的 resources 文件夹中),或者你可以使用 log4j2.configurationFile JVM 属性或 LOG4J_CONFIGURATION_FILE 环境变量来指定名称/位置。

我更喜欢通过 properties 文件进行配置,因此我通常在 resources/log4j2.properties 中 包含类似以下内容:

rootLogger.level = info
rootLogger.appenderRef.stdout.ref = STDOUT
rootLogger.appenderRef.file.ref = logfile

appender.console.type = Console
appender.console.name = STDOUT
appender.console.filter.threshold.type = ThresholdFilter
appender.console.filter.threshold.level = info
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = [%c] %x %X %highlight{%m}%n

在开发过程中,我通常会有一个仅用于开发的目录(通过别名添加到类路径中)里的第二个属性文件,名为 log4j2-test.properties

# so that log4j2 checks every 30 seconds for changes in configuration:
monitorInterval = 30

rootLogger.level = debug
rootLogger.appenderRef.stdout.ref = STDOUT
rootLogger.appenderRef.file.ref = logfile

appender.console.type = Console
appender.console.name = STDOUT
appender.console.filter.threshold.type = ThresholdFilter
appender.console.filter.threshold.level = debug
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = [%c] %x %X %highlight{%m}%n

您可能还希望将某些在 infodebug 级别非常健谈的库的日志级别阈值配置得更高:

# Chatty libraries we need to suppress:
logger.sshj.name = net.schmizz.sshj
logger.sshj.level = warn

logger.hikari.name = com.zaxxer.hikari
logger.hikari.level = warn

logger.authnet.name = net.authorize
logger.authnet.level = warn

logger.eclipse1.name = com.eclipse
logger.eclipse1.level = warn

logger.eclipse2.name = org.eclipse
logger.eclipse2.level = warn

java.util.logging 桥接

为了正确地将 java.util.logging (JUL) 桥接到 log4j2,您需要 提供某种形式的配置。JUL-to-Log4j bridge docs 中描述了多种选项,同时也解释了 JUL 所施加的、使此操作成为必要的限制。

最简单的方法是设置 java.util.logging.manager JVM 属性。 如果您能够保证在您的应用程序获得 控制权之前(在您的 -main 函数中)不会进行任何日志记录,那么您可以在代码中设置此属性:

(System/setProperty "java.util.logging.manager"
                    "org.apache.logging.log4j.jul.LogManager")

您可以将此作为顶级表单放在主命名空间中,例如,紧跟在 ns 表单之后。

如果您的代码运行在可能在您的应用程序获得控制权之前初始化 JUL 的上下文中(例如,servlet 容器),则需要在启动 JVM 时将此属性设置为 JVM 属性,例如,

java -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \
  -jar your.jar

如果你正在使用 Clojure CLI,你可以直接设置它,或者通过别名进行设置。使用 CLI 的 -J 选项直接设置:

clojure -J-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \
  -M -m your.namespace

deps.edn 中通过别名设置:

{:aliases
 {:jul
  {:jvm-opts ["-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager"]}}}

然后你可以使用别名运行你的代码:

clojure -M:jul -m your.namespace

如果您为 log4j2 配置了日志级别,但它们似乎未按预期工作, 您可能遇到了这个 log4j-jul 问题, 其中某些代码正在以编程方式设置 JUL 日志级别, 从而覆盖了您的配置。在 log4j2 2.24.0 及更高版本中, 这不应再是一个问题,但如果您希望该编程设置生效, 即覆盖您的配置,您可以将 log4j2.julLoggerAdapter JVM 属性设置为 org.apache.logging.log4j.jul.CoreLoggerAdapter( 这是 log4j2 2.23.0 中的默认行为)。

如上所述,您可以通过 CLI 的 -J 选项或 deps.edn 中的别名 进行设置,或者直接针对 java 命令进行设置:

java -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \
  -Dlog4j2.julLoggerAdapter=org.apache.logging.log4j.jul.CoreLoggerAdapter \
  -jar your.jar

logj42 与 tools.build

如果你正在使用 tools.build (build.clj) 来构建 uberjars, 并且你正在使用本库或直接使用 log4j2, 你可能需要查看这个 log4j2 冲突处理器,用于 tools.build。 如果你的项目有提供 log4j2 插件的依赖项 (例如模板),使用冲突处理器将确保 这些插件的配置被正确合并。

clojure.tools.logging 迁移/常见问题

c.t.l 有 logf,但本库没有。对于大多数用途(配合 %s),你 可以使用一个 ParameterizedMessage -- 将 %s 替换为 {},你 应该会得到相同的行为。对于更复杂的案例,你可以使用 clojure.core/format 配合参数来构建用于日志记录的字符串。

你不再需要 -Dclojure.tools.logging.factory=clojure.tools.logging.impl/log4j2-factory 因为本库直接使用 log4j2(c.t.l 默认使用 slf4j)。

如果你想直接获取一个 Logger 对象,你可以使用 org.apache.logging.log4j.LogManager/getLogger,传入一个命名空间 名称作为字符串。c.t.l 在其 impl 命名空间中提供了一个通用的 get-logger

许可证

版权所有 © 2024-2026 Sean Corfield。

根据 Eclipse Public License 2.0 分发