刚刚接触 java 的同学通常会被 java 百花齐放的日志体系搞晕,错综复杂的日志框架包之间总是发生冲突,令人抓狂
本篇日志就从历史上各个版本的 java 日志框架出发,抽丝剥茧,为各位详细介绍他们,以及如何处理复杂的日志包冲突问题
java.util.logging 是 jdk1.4 发布的 java 日志包
他的优点是拥有比 log4j 更加详细的日志分级,存储在 java.util.logging.Level 枚举类型中:
- SEVERE
- WARNING
- INFO
- CONFIG
- FINE
- FINER
- FINEST
Logger 对象的创建
java.util.logging 的 Logger 对象的创建有两个 static 方法:
static Logger getLogger(String name)
static Logger getLogger(String name, String resourceBundleName)
参数 name 是 Logger 的名称,同一个名称 Logger 只会创建一个
简单实例
public class TestLogger {
public static void main(String[] args) {
Logger log = Logger.getLogger("lavasoft");
log.setLevel(Level.INFO);
Logger log1 = Logger.getLogger("lavasoft");
System.out.println(log==log1); //true
Logger log2 = Logger.getLogger("lavasoft.blog");
log2.setLevel(Level.WARNING);
log.info("aaa");
log2.info("bbb");
log2.fine("fine");
}
}
Logger 的 Handler
Logger 提供了 MemoryHandler、StreamHandler、ConsoleHandler、FileHandler、SocketHandler 等 handler 用来实现不同的日志输出行为
通过 Logger 对象的 addHandler 方法传入所需要的 Handler 对象即可
Formatter
默认的 Logger 是通过 XML 的格式输出日志的,如果希望改变日志输出的格式,那么就需要创建自己的 Formater 对象
public class TestLogger {
public static void main(String[] args) throws IOException {
Logger log = Logger.getLogger("lavasoft");
log.setLevel(Level.INFO);
Logger log1 = Logger.getLogger("lavasoft");
System.out.println(log == log1); //true
Logger log2 = Logger.getLogger("lavasoft.blog");
log2.setLevel(Level.WARNING);
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setLevel(Level.ALL);
log.addHandler(consoleHandler);
FileHandler fileHandler = new FileHandler("C:/testlog%g.log");
fileHandler.setLevel(Level.INFO);
fileHandler.setFormatter(new MyLogHander());
log.addHandler(fileHandler);
log.info("aaa");
log2.info("bbb");
log2.fine("fine");
}
}
class MyLogHander extends Formatter {
@Override
public String format(LogRecord record) {
return record.getLevel() + ":" + record.getMessage()+"\n";
}
}
log4j 是 apache 的一个开源项目,提供了强有力的 java 日志支持,甚至他也提供了其他语言包括 C、C++、.Net、PL/SQL 的接口,从而实现多语言并存的分布式环境日志打印
在此前的日志中,我们介绍了 log4j 的用法:
Log4j 在 spring 中的配置及应用
commons-logging 是 Apache commons类库中的一员,他作为一个日志门面,能够自动选择使用 log4j 还是 JDK logging,但是他不依赖Log4j,JDK Logging的API。如果项目的classpath中包含了log4j的类库,就会使用log4j,否则就使用JDK Logging
他实现了项目中不同模块的自由组合以及项目的迁移,虽然底层使用了不同的日志包,却不需要修改源码
SLF4J 可以说是目前应用最为广泛的日志门面了,它提供了一个日志抽象层,允许你在后台使用任意一个日志类库
log4j2 是目前 java 项目中应用最为广泛的 log 包了,log4j 1.x有了脱胎换骨的变化,其官网宣称的优势有多线程下10几倍于log4j 1.x和logback的高吞吐量、可配置的审计型日志、基于插件架构的各种灵活配置等
博主即将对 log4j2 的用法进行一篇详细的讲解,敬请期待
Logback是由log4j创始人设计的另一个开源日志组件,官方网站: http://logback.qos.ch。它当前分为下面下个模块:
- logback-core:其它两个模块的基础模块
- logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j API使你可以很方便地更换成其它日志系统如log4j或JDK14 Logging
- logback-access:访问模块与Servlet容器集成提供通过Http来访问日志的功能
相比于 log4j,他有以下优点:
- 更快的实现:Logback的内核重写了,在一些关键执行路径上性能提升10倍以上。而且logback不仅性能提升了,初始化内存加载也更小了。
- 非常充分的测试:Logback经过了几年,数不清小时的测试。Logback的测试完全不同级别的。
- Logback-classic非常自然实现了SLF4j:Logback-classic实现了SLF4j。在使用SLF4j中,你都感觉不到logback-classic。而且因为logback-classic非常自然地实现了slf4j , 所 以切换到log4j或者其他,非常容易,只需要提供成另一个jar包就OK,根本不需要去动那些通过SLF4JAPI实现的代码。
- 非常充分的文档 官方网站有两百多页的文档。
- 自动重新加载配置文件,当配置文件修改了,Logback-classic能自动重新加载配置文件。扫描过程快且安全,它并不需要另外创建一个扫描线程。这个技术充分保证了应用程序能跑得很欢在JEE环境里面。
- Lilith是log事件的观察者,和log4j的chainsaw类似。而lilith还能处理大数量的log数据 。
- 谨慎的模式和非常友好的恢复,在谨慎模式下,多个FileAppender实例跑在多个JVM下,能 够安全地写道同一个日志文件。RollingFileAppender会有些限制。Logback的FileAppender和它的子类包括 RollingFileAppender能够非常友好地从I/O异常中恢复。
- 配置文件可以处理不同的情况,开发人员经常需要判断不同的Logback配置文件在不同的环境下(开发,测试,生产)。而这些配置文件仅仅只有一些很小的不同,可以通过,和来实现,这样一个配置文件就可以适应多个环境。
- Filters(过滤器)有些时候,需要诊断一个问题,需要打出日志。在log4j,只有降低日志级别,不过这样会打出大量的日志,会影响应用性能。在Logback,你可以继续 保持那个日志级别而除掉某种特殊情况,如alice这个用户登录,她的日志将打在DEBUG级别而其他用户可以继续打在WARN级别。要实现这个功能只需加4行XML配置。可以参考MDCFIlter 。
- SiftingAppender(一个非常多功能的Appender):它可以用来分割日志文件根据任何一个给定的运行参数。如,SiftingAppender能够区别日志事件跟进用户的Session,然后每个用户会有一个日志文件。
- 自动压缩已经打出来的log:RollingFileAppender在产生新文件的时候,会自动压缩已经打出来的日志文件。压缩是个异步过程,所以甚至对于大的日志文件,在压缩过程中应用不会受任何影响。
- 堆栈树带有包版本:Logback在打出堆栈树日志时,会带上包的数据。
- 自动去除旧的日志文件:通过设置TimeBasedRollingPolicy或者SizeAndTimeBasedFNATP的maxHistory属性,你可以控制已经产生日志文件的最大数量。如果设置maxHistory 12,那那些log文件超过12个月的都会被自动移除。
上面列出了这么多的 java 日志解决方案及日志门面,在我们错综复杂的项目及依赖中,通常各自都依赖着不同的日志体系,包之间的冲突是常常发生的
那么如何来解决包冲突呢?我们的建议是剔除所有的依赖,只保留一个统一的日志门面入口 slf4j-api
java log 体系概览

java 日志体系层级 | groupId | artifactId | 提供方 | 作用 | 备注 |
桥接层 | org.slf4j | log4j-over-slf4j | SLF4J | 把log4j1的日志转向slf4j | |
| org.slf4j | jul-to-slf4j | SLF4J | 把java.util.logging日志转向slf4j | 因为双亲委派加载问题,需要其它包辅助 |
| org.slf4j | jcl-over-slf4j | SLF4J | 把apache的common-logging日志转向slf4j | |
| org.slf4j | logback-over-slf4j | N/A | 把logback的日志转向slf4j | 这个包不存在,因为作者认为不需要 |
| org.apache.logging.log4j | log4j-to-slf4j | APACHE LOG4J2 | 用于把log4j2的日志转向slf4j | |
入口层 | org.slf4j | slf4j-api | SLF4J | 主日志入口API | 强烈建议业务代码,仅依赖这一层API |
适配器层 | org.slf4j | slf4j-log4j12 | SLF4J | 把slf4j的日志输出到 log4j1 的实现上 | |
| org.slf4j | slf4j-jdk14 | SLF4J | 把slf4j的日志输出到java.util.logging的实现上 | |
| org.slf4j | slf4j-jcl | SLF4J | 把slf4j的日志输出到apache的common-logging实现上 | |
| org.slf4j | slf4j-logback | SLF4J | 把slf4j的日志输出到logback上 | 这个包不存在,因为logback原生实现了slf4j,所以不需要适配器 |
| org.apache.logging.log4j | log4j-slf4j-impl | APACHE LOG4J2 | 把slf4j的日志输出到log4j2的实现上 | |
微型实现 | org.slf4j | slf4j-nop | SLF4J | 把slf4j的日志吞掉(无任何输出) | |
| org.slf4j | slf4j-simple | SLF4J | 把slf4j的INFO以上级别日志,输出到System.err中 | 一般用于调试或小型项目 |
实现层 | log4j | log4j | APACHE LOG4J1 | APACHE亲儿子,上古日志实现 | |
| N/A | N/A | JAVA | JAVA亲儿子,发育不良 | java.util.logging集成在java4+,所以无需引入新包 |
| commons-logging | commons-logging | APACHE COMMONS-LOGGING | APACHE亲儿子,上古日志API | |
| ch.qos.logback | logback-classic,logback-core | LOGBACK | log4j之父的改良版日志实现,比其兄弟log4j厉害很多 | |
| org.apache.logging.log4j | log4j-api | APACHE LOG4J2 | APACHE亲儿子,开源社区智慧结晶 | |
去除冲突的原则
- 遗留层的包必须全部去除——这样才能保证遗留日志都会经过桥接包进入slf4j-api
- 桥接层与适配器层的同一列的包不能共存——否则会死循环导致StackOverFlow
- 适配器层(包括logback-classic也算适配器层)最多只能选1个包存在——否则slf4j的日志有可能输出到非预料位置
最为推荐的做法是:业务代码仅依赖slf4j-api.jar,不依赖其它任何日志包,打包给别人引用时,除了slf-api.jar,其余日志包都声明为“provided”
欢迎关注微信公众号,以技术为主,涉及历史、人文等多领域的学习与感悟,每周三到七篇推文,只有全部原创,只有干货没有鸡汤

技术帖
技术分享
log
java
jdk
log4j
maven
log4j2
commons-logging
logback
slf4j
pom