Java日志桥接案例

wen java案例 1

本文目录导读:

Java日志桥接案例

  1. 前置知识:核心概念
  2. 案例一:项目用 SLF4J + Logback,但引用了老库(使用 Log4j 1.x)
  3. 案例二:项目用 SLF4J + Logback,但代码或依赖用了 JCL (Apache Commons Logging)
  4. 案例三:项目用 SLF4J + Logback,但代码直接调用了 java.util.logging (JUL)
  5. 案例四:反向桥接(项目用 Log4j2,但代码硬编码了 SLF4J)
  6. 案例五:最全综合排除法(检查“多余”的桥接包)
  7. 总结对照表

在Java生态中,日志系统的混乱常常源于日志门面(Facade)日志实现(Implementation)的冲突,以及多个日志框架共存导致的输出丢失或重复。

以下通过几个典型场景的案例,展示如何通过桥接解决这些问题。


前置知识:核心概念

  • 日志门面SLF4JApache Commons Logging (JCL) —— 只提供API,不输出日志。
  • 日志实现LogbackLog4j2java.util.logging (JUL) —— 真正输出日志的框架。
  • 桥接包:用来重定向/欺骗某个框架,让它的调用走另一个框架的通道。

项目用 SLF4J + Logback,但引用了老库(使用 Log4j 1.x)

问题:你的项目用 SLF4J + Logback 输出日志,但第三方依赖(如老版 Apache HttpClient)内部调用的是 Log4j 1.x API,由于项目没有配置 log4j.properties,该库的日志会全部丢失

解决方案:使用 log4j-over-slf4j 桥接包(Log4j 1.x → SLF4J)。

操作步骤

  1. 移除原有的 log4j:log4j:1.2.x 依赖(防止类冲突)。
  2. 添加桥接包:
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>log4j-over-slf4j</artifactId>
        <version>1.7.36</version> <!-- 版本与SLF4J一致 -->
    </dependency>
  3. 确保 slf4j-apilogback-classic 在 classpath 中。

原理: 桥接包内有一个 org.apache.log4j.Logger 类(完全替换了原Log4j的同名类),其内部实现调用了 SLF4J 的 API,第三方库以为自己在用Log4j,实际日志输出到了Logback。


项目用 SLF4J + Logback,但代码或依赖用了 JCL (Apache Commons Logging)

问题:Spring Framework 旧版本默认使用 JCL 输出日志,如果不桥接,Spring的日志无法打印,或者输出到 System.out 而不是Logback。

解决方案:使用 jcl-over-slf4j 桥接包(JCL → SLF4J)。

操作步骤

  1. 排除项目中传递过来的 commons-logging:commons-logging 依赖。
  2. 添加桥接包:
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>1.7.36</version>
    </dependency>
  3. 若使用 Maven,在 Spring 依赖中排除原生JCL:
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <exclusions>
            <exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

验证: Spring 的日志条目会出现在 Logback 配置的 appender 中,且格式统一。


项目用 SLF4J + Logback,但代码直接调用了 java.util.logging (JUL)

问题:JDK自带的 JUL 可能是某个库(如某些金融SDK)的日志实现,JUL 默认配置只输出 WARNING 级别到控制台,且格式与 Logback 不一致。

解决方案:使用 jul-to-slf4j 桥接包(JUL → SLF4J)。

操作步骤

  1. 添加桥接包:

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jul-to-slf4j</artifactId>
        <version>1.7.36</version>
    </dependency>
  2. 在应用启动时(main方法或ServletContextListener),安装桥接器:

    import org.slf4j.bridge.SLF4JBridgeHandler;
    public class Main {
        public static void main(String[] args) {
            // 移除JUL默认的根处理器,避免重复输出
            SLF4JBridgeHandler.removeHandlersForRootLogger();
            // 安装桥接处理器
            SLF4JBridgeHandler.install();
            // 启动你的应用...
        }
    }

注意:如果不禁用JUL默认处理器,日志会重复输出(一条来自JUL,一条来自Logback桥接)。


反向桥接(项目用 Log4j2,但代码硬编码了 SLF4J)

问题:你的团队实际使用 Log4j2 作为实现(因为异步性能好),但内部框架直接依赖了 SLF4J 接口。

解决方案:使用 log4j-slf4j-impl 适配层(SLF4J → Log4j2)。

操作步骤

  1. 添加 Log4j2 和适配器,不要添加 Logback:
    <!-- 核心:Log4j2 API + Core -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>2.20.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.20.0</version>
    </dependency>
    <!-- 桥接:让 SLF4J 调用 Log4j2 -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j-impl</artifactId>
        <version>2.20.0</version>
    </dependency>
  2. 配置 log4j2.xml 文件,而不是 logback.xml

最全综合排除法(检查“多余”的桥接包)

致命错误:同时引入 log4j-over-slf4jslf4j-log4j12,会导致无限递归(栈溢出异常)。

排查建议:使用 Maven 插件检查依赖树。

mvn dependency:tree -Dincludes=org.slf4j,ch.qos.logback,log4j

检查点

  • 是否出现两个 slf4j 绑定(Binding)?
    • 如果出现 slf4j-log4j12logback-classic,只能留一个。
  • 是否出现两个 log4j 实现?
    • 如果出现 log4j 原始包和 log4j-over-slf4j,必须移除原始包。

总结对照表

项目目标实现 场景中调用的API 需要添加的桥接包 需要移除的包
Logback Log4j 1.x log4j-over-slf4j 原始 log4j:log4j
Logback JCL jcl-over-slf4j commons-logging
Logback JUL jul-to-slf4j + 代码安装 无需移除(需禁用JUL默认处理器)
Log4j2 SLF4J log4j-slf4j-impl logback-classic
Log4j2 JCL log4j-jcl commons-logging
Log4j2 Log4j 1.x log4j-1.2-api 原始 log4j:log4j

核心口诀

  • 想让谁输出,就把谁放在classpath最深处(作为实现)。
  • 桥接包的本质是 “假装自己是那个库的API,但内部转发给SLF4J”
  • 确保 classpath 中只能有一个日志实现绑定到 SLF4J。

抱歉,评论功能暂时关闭!