本文是一个系列, 欢迎关注
日志到底是何方神圣? 为什么要使用日志框架?
想必大家都有过使用 System.out 来进行输出调试, 开发开发环境下这样做当然很方便, 但是线上这样做就有麻烦了:
系统一直运行, 输出越来越多, 磁盘空间逐渐被写满
不同的业务想要把日志输出在不同的位置
有些场合为了更高性能, 尽量控制减少日志输出, 需要动态调整日志输出量
自动输出日志相关信息, 比如: 日期, 线程, 方法名称等等
显然 System.out 解决不了我们的问题, 但是我们遇到的问题一定会有前人遇到过, 日志也不例外, 其中就有一个大牛 Ceki http://ceki.blogspot.com/ , 整个 Java 的日志体系几乎都有 Ceki 参与或者受到了 Ceki 的深度影响. 当然 Java 日志体系的复杂度也有一部分原因是拜这位大牛所赐.
Java 日志的恩怨情仇
1996 年早期, 欧洲安全电子市场项目组决定编写它自己的程序跟踪 API(Tracing API). 经过不断的完善, 这个 API 终于成为一个十分受欢迎的 Java 日志软件包, 即 Log4j(由 Ceki 创建).
后来 Log4j 成为 Apache 基金会项目中的一员, Ceki 也加入 Apache 组织. 后来 Log4j 近乎成了 Java 社区的日志标准. 据说 Apache 基金会还曾经建议 Sun 引入 Log4j 到 Java 的标准库中, 但 Sun 拒绝了.
2002 年 Java1.4 发布, Sun 推出了自己的日志库 JUL(Java Util Logging), 其实现基本模仿了 Log4j 的实现. 在 JUL 出来以前, Log4j 就已经成为一项成熟的技术, 使得 Log4j 在选择上占据了一定的优势.
接着, Apache 推出了 Jakarta Commons Logging,JCL 只是定义了一套日志接口(其内部也提供一个 Simple Log 的简单实现), 支持运行时动态加载日志组件的实现, 也就是说, 在你应用代码里, 只需调用 Commons Logging 的接口, 底层实现可以是 Log4j, 也可以是 Java Util Logging.
后来 (2006 年),Ceki 不适应 Apache 的工作方式, 离开了 Apache. 然后先后创建了 Slf4j(日志门面接口, 类似于 Commons Logging) 和 Logback(Slf4j 的实现)两个项目, 并回瑞典创建了 QOS 公司, QOS 官网上是这样描述 Logback 的: The Generic,Reliable Fast&Flexible Logging Framework(一个通用, 可靠, 快速且灵活的日志框架).
Java 日志领域被划分为两大阵营: Commons Logging 阵营和 Slf4j 阵营.
Commons Logging 在 Apache 大树的笼罩下, 有很大的用户基数. 但有证据表明, 形式正在发生变化. 2013 年底有人分析了 GitHub 上 30000 个项目, 统计出了最流行的 100 个 Libraries, 可以看出 Slf4j 的发展趋势更好.
Apache 眼看有被 Logback 反超的势头, 于 2012-07 重写了 Log4j 1.x, 成立了新的项目 Log4j 2, Log4j 2 具有 Logback 的所有特性.
如今日志框架已经发展为: Slf4j 作为 API, 实现分为 logback 与 log4j(Commons Logging 因为效率和 API 设计等问题, 现在逐渐淡出舞台了)
让我们来瞻仰一下大神, 哈哈:
那么如何在混乱的 Java 日志体系中如何优雅的使用日志呢?
其实在 Ceki 设计的体系下, 日志如同 Java 的 JDBC,Servelt 等一样, 定义好标准后实现可以互相切换, 问题在于定标准的人各自为政搞出来好多标准, JCL,SLF4j 等等, 官方 (Sun 公司) 又晚又不给力, 发展到现在终于被 SLF4j 以一种巧妙的方式 (桥接, 绑定, 见下文) 统一了, 标准使用方式如下图:
这个图截取自 slf4j 手册 http://www.slf4j.org/manual.html , 简化了多余部分, 很清晰的表示了使用方式:
应用引用 SLF4j-API(编码时使用 SLF4j 的接口 org.slf4j.Logger, 而非 logback 或 log4j 的实现)
logbak: slf4j 会自动查找 logback 实现(logback 默认实现了 slf4j)
log4j: 使用起来基本一致, 只不过多了适配器层, 引用了 slf4j-log4j12.jar, 官方称为绑定(concrete-bindings), 就是将 SLF4j-API 绑定到 log4j 最终输出日志
具体依赖如下
- logback
- <dependency>
- <groupId>ch.qos.logback</groupId>
- <artifactId>logback-classic</artifactId>
- <version>1.2.3</version>
- </dependency>
- log4j2
- <dependency>
- <groupId>org.apache.logging.log4j</groupId>
- <artifactId>log4j-slf4j-impl</artifactId>
- <version>2.12.1</version>
- </dependency>
- <dependency>
- <groupId>org.apache.logging.log4j</groupId>
- <artifactId>log4j-core</artifactId>
- <version>2.12.1</version>
- </dependency>
得益于 maven 的依赖传递机制, 我们不需要显示声明依赖 SLF4j-API.jar.
可以看到, log4j 多依赖了 log4j-slf4j-impl.jar, 其实就是上图所示的适配器层, 读者可能好奇, 为什么使用 log4j 会有适配器层? 其原因在于, slf4j 并不是官方规范, 所以没人遵守 (也就是自己的日志框架中没有原生实现 org.slf4j.Logger 接口, 如 log4j ), 而绑定层( log4j-slf4j-impl.jar) 的作用就是通过静态查找的方式将使用 log4j 作为实现(具体原理请关注后续文章), 这样就是实现了不依赖 log4j 而使用 log4j 输出日志(面向接口编程的最佳实践, Ceki 大神就是用这套思想将 slf4j 做成了 Java 日志的标准, 烂牌翻盘的典范).
上面这一段讲解了绑定 (concrete-bindings) 思想, 是本文的精髓, 读者一定要理解这里, 后面还有桥接思想与之类似, 请继续阅读.
小结
至此我们已经完成了日志的整合, 但是事情真的这么简单吗?
先梳理一下, 如此混乱的日志体系下 (slf4j,jul,jcl,logback,log4j) 会不会会产生什么问题? 答案是一定的, 各种第三方库使用了不同的日志框架, 如果我们依赖 Spring ,Spring(非 boot)的默认日志实现是 JCL, 又或者我们已有项目已经使用了 Log4j, 想使用 logback 的话, 难道要逐个类改代码吗(官方有迁移工具)? 我们能不能只用一种框架来处理 JUL(java.util.logging),JCL(Jakarta Commons Logging),Log4j1,Log4j2 呢?
答案是肯定的, Ceki 的 Slf4j 给出了解决方案, 就是上文所说的桥接( Bridging legacy), 简单来说就是劫持以上所以第三方日志输出并重定向至 SLF4j, 最终实现统一日志上层 API(编码) 与下层实现(输出日志位置, 格式统一). 我们来看一下图示
上图左侧就是前一张图的 logback 日志实现, 为了兼容其他日志, 我们需要引用右侧的桥接包: xxx-over/to-slf4j.jar ,xxx 对应日志框架, 使用 logback 的情况下, 除了上文的 logback 依赖, 还需要引入以下依赖才能保证所有日志都被桥接至 slf4j.
如何桥接?
logback 如下
- <dependency>
- <groupId>ch.qos.logback</groupId>
- <artifactId>logback-classic</artifactId>
- <version>1.2.3</version>
- </dependency>
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>jcl-over-slf4j</artifactId>
- </dependency>
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>jul-to-slf4j</artifactId>
- </dependency>
- <!-- log4j 桥接包, slf4j 官方实现, 另有 log4j 官方实现, 二选一即可 log4j-to-slf4j-->
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>log4j-over-slf4j</artifactId>
- </dependency>
log4j2 如下
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.12.1</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.12.1</version> </dependency> <!-- 以下是桥接包, 使用了 log4j 作为底层实现, 不能再桥接 log4j, 否则会出现无限递归的情况(具体原因请关注后续文章) --> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> </dependency><dependency> <groupId>org.slf4j</groupId> <artifactId>jul-to-slf4j</artifactId> </dependency>
SpringBoot 项目引用了一部分依赖, 所以使用起来略微有些不同:
logback 如下
<!-- logback 作为内置实现, 使用相对简单 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency><!-- 引入缺少的桥接包 --><dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> </dependency>
log4j2 如下
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <!-- 使用 log4j2 要排除 logback 依赖 --> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <!-- Spring 已经写好了一个 log4j2-starter 但缺少桥接包 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency> <!-- 引入缺少的桥接包 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> </dependency>
结束语
以上两种才是项目中的最佳使用方式, 其他笔者不推荐使用.
最后来看一下 slf4j 如何使用:
static final org.slf4j.Logger logger = LoggerFactory.getLogger(TestLog.class); logger.trace("A TRACE Message"); logger.debug("A DEBUG Message"); logger.info("An INFO Message"); logger.warn("A WARN Message"); logger.error("An ERROR Message");
这样使用我们就可以随意切换日志实现而无需改动代码, 操作起来也简单, 只需要按照上文切换依赖即可. 至于其他使用细节本文不在赘述, 关注后续文章(最佳实践, 配置文件, 原理, 扩展等).
如果觉得写的不错, 求关注, 求点赞, 求转发, 如果有问题或者文中有错误, 欢迎留言讨论.
扫描关注公众号, 第一时间获得更新
参考:
Java - 日志的江湖
http://www.slf4j.org/manual.html http://www.slf4j.org/manual.html http://www.slf4j.org/legacy.html http://www.slf4j.org/legacy.html https://www.baeldung.com/spring-boot-logging
来源: https://www.cnblogs.com/xuningfans/p/12151851.html