聊一下 Java 的日志系列二

聊一下 Java 的日志系列二

💡 原文中文,约19900字,阅读约需48分钟。
📝

内容提要

本文以 Spring Boot 启动时获取 Logger 为例,梳理 SLF4J 与 Logback 的日志初始化流程:LogFactory 经 SLF4JLoggerFactory 调用 LoggerFactory.getLogger,首次触发 performInitialization,并通过 StaticLoggerBinder 绑定 Logback;StaticLoggerBinder 静态块初始化 LoggerContext 并调用 ContextInitializer.autoConfig,依次查找 logback-test.xml、logback.groovy、logback.xml 配置,解析 XML 时由 RuleStore 匹配规则并执行对应 Action,最终创建或缓存 Logger。

🔎

延伸解读

SLF4J 绑定机制:门面模式下的静态绑定

文章展示了 SLF4J 通过 StaticLoggerBinder 完成与 Logback 的绑定。首次调用 LoggerFactory.getLogger 时,performInitialization 会触发 bind(),在类路径下查找 org/slf4j/impl/StaticLoggerBinder.class。若找到则通过 getSingleton() 获取实例并标记初始化成功;若找不到则回退到 NOP 实现。这种设计让日志门面与具体实现解耦,但绑定发生在类加载阶段,且一个类路径下只应存在一个绑定。

Logback 配置文件的查找顺序与硬编码

ContextInitializer.autoConfig 会按固定顺序查找配置文件:先检查系统属性 logback.configurationFile,再依次尝试 logback-test.xml、logback.groovy、logback.xml。这些文件名在 ContextInitializer 中以常量硬编码。若均未找到,则使用 BasicConfigurator 提供默认配置。这意味着开发者无法通过自定义文件名直接让 Logback 自动加载,除非通过系统属性指定。

XML 解析:规则匹配与 Action 执行

解析 logback.xml 时,JoranConfigurator 会构建 SimpleRuleStore,并为 configuration、logger、root、appender 等标签注册对应的 Action。解析过程中,每个 StartEvent 会通过 ruleStore.matchActions 匹配适用的 Action 列表,然后调用 Action.begin 执行。例如 logger 标签会触发 LoggerAction,读取 name、level、additivity 属性并操作 LoggerC

Logger 的缓存与层级创建

LoggerContext.getLogger 方法会先检查 loggerCache 中是否已存在目标 Logger,若存在则直接返回。若不存在,则从 root 开始按名称中的分隔符逐级查找或创建子 Logger,并将新创建的 Logger 放入缓存。这种层级结构确保了 Logger 名称的继承关系,同时通过缓存避免重复创建,提升了获取 Logger 的效率。

❓

Q&A

Spring Boot 启动时获取 Logger 的初始调用链是怎样的?

启动类中通过 LogFactory.getLog(SpringApplication.class) 获取 Log,LogFactory.getLog 调用 getFactory().getInstance(clazz),实际使用 SLF4JLogFactory,其 getInstance 方法先查缓存,未命中则调用 LoggerFactory.getLogger(name) 获取 SLF4J Logger,再包装为 SLF4JLog 或 SLF4JLocationAwareLog 并放入缓存。

SLF4J 的 LoggerFactory 第一次获取 Logger 时做了什么初始化?

LoggerFactory.getILoggerFactory 发现未初始化时,会加锁并调用 performInitialization,其中先执行 bind() 绑定日志实现,绑定成功后进行版本检查,并将初始化状态置为 SUCCESSFUL_INITIALIZATION。

SLF4J 是如何绑定到 Logback 的?

SLF4J 在 bind() 中通过 ClassLoader 查找 org/slf4j/impl/StaticLoggerBinder.class 资源,然后调用 StaticLoggerBinder.getSingleton() 获取绑定实例,从而完成与 Logback 的绑定。

Logback 的 StaticLoggerBinder 静态块做了哪些初始化?

StaticLoggerBinder 的静态块调用 init(),其中创建 LoggerContext 实例,并调用 ContextInitializer.autoConfig() 自动配置日志上下文,最后通过 contextSelectorBinder 完成初始化。

Logback 自动配置时按什么顺序查找配置文件?

ContextInitializer.autoConfig 调用 findURLOfDefaultConfigurationFile,依次查找系统属性 logback.configurationFile、logback-test.xml、logback.groovy、logback.xml,找到第一个存在的文件即使用。

Logback 解析 logback.xml 时如何匹配规则并执行动作?

解析 XML 时,GenericConfigurator 构建 Interpreter,其中使用 SimpleRuleStore 注册规则(如 configuration/logger 对应 LoggerAction),然后 EventPlayer 遍历 SAX 事件,在 startElement 中通过 ruleStore.matchActions 获取适用的 Action 列表,并调用 action.begin 执行具体处理。

Logback 中 Logger 是如何创建和缓存的?

LoggerContext.getLogger 方法先检查 loggerCache 中是否已有对应名称的 Logger,有则直接返回;否则从根 Logger 开始,按名称中的点分隔逐级查找或创建子 Logger,并放入 loggerCache 缓存。

🏷️

标签

➡️

继续阅读