Java日志体系居然这么复杂?——架构篇
本文是一个系列,欢迎关注
日志到底是何方神圣?为什么要使用日志框架?
想必大家都有过使用System.out
来进行输出调试,开发开发环境下这样做当然很方便,但是线上这样做就有麻烦了:
- 系统一直运行,输出越来越多,磁盘空间逐渐被写满
- 不同的业务想要把日志输出在不同的位置
- 有些场合为了更高性能,尽量控制减少日志输出,需要动态调整日志输出量
- 自动输出日志相关信息,比如:日期、线程、方法名称等等
显然System.out
解决不了我们的问题,但是我们遇到的问题一定会有前人遇到过,日志也不例外,其中就有一个大牛 Ceki,整个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手册,简化了多余部分,很清晰的表示了使用方式:
应用引用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.htmlhttp://www.slf4j.org/legacy.htmlwww.baeldung.com/spring-boot…
转载请注明出处。
Java日志体系居然这么复杂?——架构篇的更多相关文章
- java 日志体系(四)log4j 源码分析
java 日志体系(四)log4j 源码分析 logback.log4j2.jul 都是在 log4j 的基础上扩展的,其实现的逻辑都差不多,下面以 log4j 为例剖析一下日志框架的基本组件. 一. ...
- java日志体系的思考(转)
Java 日志缓存机制的实现 Java 日志管理最佳实践 混乱的 Java 日志体系 log日志远程统一记录 浅谈后端日志系统 Java异常处理和接口约定 用SLF4j/Logback打印日志-1 用 ...
- 混乱的 Java 日志体系
混乱的 Java 日志体系 2016/09/10 | 分类: 基础技术 | 0 条评论 | 标签: LOG 分享到: 原文出处: xirong 一.困扰的疑惑 目前的日志框架有 jdk 自带的 log ...
- java 日志体系目录
java 日志体系目录 1.1 java 日志体系(一)log4j1.log4j2.logback.jul.jcl.slf4j 1.2 java 日志体系(二)jcl 和 slf4j 2.1 java ...
- java 日志体系(三)log4j从入门到详解
java 日志体系(三)log4j从入门到详解 一.Log4j 简介 在应用程序中添加日志记录总的来说基于三个目的: 监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析工作: 跟踪代 ...
- Java 日志体系(二)jcl 和 slf4j
Java 日志体系(二)jcl 和 slf4j <java 日志体系(一)统一日志>:https://www.cnblogs.com/binarylei/p/9828166.html &l ...
- Java 日志体系
Java 日志体系 <java 日志和 SLF4J 随想>:http://ifeve.com/java-slf4j-think/ 一.常用的日志组件 名称 jar 描述 log4j log ...
- 【原创】架构师必备,带你弄清混乱的JAVA日志体系!
引言 还在为弄不清commons-logging-xx.jar.log4j-xx.jar.sl4j-api-xx.jar等日志框架之间复杂的关系而感到烦恼吗? 还在为如何统一系统的日志输出而感到不知所 ...
- 架构师必备,带你弄清混乱的JAVA日志体系!
作者:孤独烟 出处:http://rjzheng.cnblogs.com/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任 ...
随机推荐
- 仿IOS效果-带弹簧动画的ListView
背景介绍 最近项目打算做一个界面,类似于dayone首页的界面效果,dayone 是一款付费应用,目前只有IOS端.作为一个资深懒惰的程序员,奉行的宗旨是绝对不重复造一个轮子.于是乎,去网上找一大堆开 ...
- es6新增语法之`${}`
这是es6中新增的字符串方法 可以配合反单引号完成拼接字符串的功能 1.反单引号怎么打出来?将输入法调整为英文输入法,单击键盘上数字键1左边的按键. 2.用法step1: 定义需要拼接进去的字符串变量 ...
- JS获取手机型号和系统
废话不多说,直接上源码 <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type&q ...
- supersockets单个 listener
在下面的配置中,你可以配置服务器的监听 ip/port: <superSocket> <servers> <server name="TelnetServer& ...
- NLP进阶之(七)膨胀卷积神经网络
NLP进阶之(七)膨胀卷积神经网络1. Dilated Convolutions 膨胀卷积神经网络1.2 动态理解1.2.2 转置卷积动画1.2.3 理解2. Dilated Convolutions ...
- ios9.3.3 h5的js代码全部失效
做微信公众号页面时,ios9.3.3 h5的js代码全部失效描述: 机型iphone6 plus,ios9.3.3js代码全部失效,刚开始还以为是ios和jq兼容问题, 后来发现是es6语法不能读,导 ...
- Editplus配置java编译运行环境
1.进入配置环境界面 首先,从菜单"工具(Tools)"->"配置用户工具..."进入用户工具设置. 在类别里展开"工具"树形菜单-& ...
- gSOAP calc服务端与客户端示例
1. Web服务定义描述头文件 typedef double xsd__double; int ns__add(xsd__double a, xsd__double b, xsd__double &a ...
- SAX解析xml (遍历DOM树各节点)
本文参考 http://yangjunfeng.iteye.com/blog/401377 1. books.xml <?xml version="1.0" encoding ...
- Vue中qs插件的使用
qs 是一个增加了一些安全性的查询字符串解析和序列化字符串的库. 在项目中使用命令行工具输入:npm install qs安装完成后在需要用到的组件中:import qs from ‘qs’具体使用中 ...