java log 体系介绍及日志包冲突的解决

2017-11-21 17:36:07   最后更新: 2017-11-21 17:36:07   访问数量:95




刚刚接触 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,他有以下优点:

  1. 更快的实现:Logback的内核重写了,在一些关键执行路径上性能提升10倍以上。而且logback不仅性能提升了,初始化内存加载也更小了。
  2. 非常充分的测试:Logback经过了几年,数不清小时的测试。Logback的测试完全不同级别的。
  3. Logback-classic非常自然实现了SLF4j:Logback-classic实现了SLF4j。在使用SLF4j中,你都感觉不到logback-classic。而且因为logback-classic非常自然地实现了slf4j , 所 以切换到log4j或者其他,非常容易,只需要提供成另一个jar包就OK,根本不需要去动那些通过SLF4JAPI实现的代码。
  4. 非常充分的文档 官方网站有两百多页的文档。
  5. 自动重新加载配置文件,当配置文件修改了,Logback-classic能自动重新加载配置文件。扫描过程快且安全,它并不需要另外创建一个扫描线程。这个技术充分保证了应用程序能跑得很欢在JEE环境里面。
  6. Lilith是log事件的观察者,和log4j的chainsaw类似。而lilith还能处理大数量的log数据 。
  7. 谨慎的模式和非常友好的恢复,在谨慎模式下,多个FileAppender实例跑在多个JVM下,能 够安全地写道同一个日志文件。RollingFileAppender会有些限制。Logback的FileAppender和它的子类包括 RollingFileAppender能够非常友好地从I/O异常中恢复。
  8. 配置文件可以处理不同的情况,开发人员经常需要判断不同的Logback配置文件在不同的环境下(开发,测试,生产)。而这些配置文件仅仅只有一些很小的不同,可以通过,和来实现,这样一个配置文件就可以适应多个环境。
  9. Filters(过滤器)有些时候,需要诊断一个问题,需要打出日志。在log4j,只有降低日志级别,不过这样会打出大量的日志,会影响应用性能。在Logback,你可以继续 保持那个日志级别而除掉某种特殊情况,如alice这个用户登录,她的日志将打在DEBUG级别而其他用户可以继续打在WARN级别。要实现这个功能只需加4行XML配置。可以参考MDCFIlter 。
  10. SiftingAppender(一个非常多功能的Appender):它可以用来分割日志文件根据任何一个给定的运行参数。如,SiftingAppender能够区别日志事件跟进用户的Session,然后每个用户会有一个日志文件。
  11. 自动压缩已经打出来的log:RollingFileAppender在产生新文件的时候,会自动压缩已经打出来的日志文件。压缩是个异步过程,所以甚至对于大的日志文件,在压缩过程中应用不会受任何影响。
  12. 堆栈树带有包版本:Logback在打出堆栈树日志时,会带上包的数据。
  13. 自动去除旧的日志文件:通过设置TimeBasedRollingPolicy或者SizeAndTimeBasedFNATP的maxHistory属性,你可以控制已经产生日志文件的最大数量。如果设置maxHistory 12,那那些log文件超过12个月的都会被自动移除。

 

上面列出了这么多的 java 日志解决方案及日志门面,在我们错综复杂的项目及依赖中,通常各自都依赖着不同的日志体系,包之间的冲突是常常发生的

那么如何来解决包冲突呢?我们的建议是剔除所有的依赖,只保留一个统一的日志门面入口 slf4j-api

 

java log 体系概览

 

 

java 日志体系
层级groupIdartifactId提供方作用备注
桥接层org.slf4jlog4j-over-slf4jSLF4J把log4j1的日志转向slf4j  
 org.slf4jjul-to-slf4jSLF4J把java.util.logging日志转向slf4j因为双亲委派加载问题,需要其它包辅助
 org.slf4jjcl-over-slf4jSLF4J把apache的common-logging日志转向slf4j  
 org.slf4jlogback-over-slf4jN/A把logback的日志转向slf4j这个包不存在,因为作者认为不需要
 org.apache.logging.log4jlog4j-to-slf4jAPACHE LOG4J2用于把log4j2的日志转向slf4j  
入口层org.slf4jslf4j-apiSLF4J主日志入口API强烈建议业务代码,仅依赖这一层API
适配器层org.slf4jslf4j-log4j12SLF4J把slf4j的日志输出到 log4j1 的实现上  
 org.slf4jslf4j-jdk14SLF4J把slf4j的日志输出到java.util.logging的实现上  
 org.slf4jslf4j-jclSLF4J把slf4j的日志输出到apache的common-logging实现上  
 org.slf4jslf4j-logbackSLF4J把slf4j的日志输出到logback上这个包不存在,因为logback原生实现了slf4j,所以不需要适配器
 org.apache.logging.log4jlog4j-slf4j-implAPACHE LOG4J2把slf4j的日志输出到log4j2的实现上 
微型实现org.slf4jslf4j-nopSLF4J把slf4j的日志吞掉(无任何输出)  
 org.slf4jslf4j-simpleSLF4J把slf4j的INFO以上级别日志,输出到System.err中一般用于调试或小型项目  
实现层log4jlog4jAPACHE LOG4J1 APACHE亲儿子,上古日志实现  
 N/AN/AJAVAJAVA亲儿子,发育不良java.util.logging集成在java4+,所以无需引入新包
 commons-loggingcommons-loggingAPACHE COMMONS-LOGGING APACHE亲儿子,上古日志API 
 ch.qos.logbacklogback-classic,logback-coreLOGBACKlog4j之父的改良版日志实现,比其兄弟log4j厉害很多 
 org.apache.logging.log4jlog4j-apiAPACHE LOG4J2 APACHE亲儿子,开源社区智慧结晶  

 

去除冲突的原则

  1. 遗留层的包必须全部去除——这样才能保证遗留日志都会经过桥接包进入slf4j-api
  2. 桥接层与适配器层的同一列的包不能共存——否则会死循环导致StackOverFlow
  3. 适配器层(包括logback-classic也算适配器层)最多只能选1个包存在——否则slf4j的日志有可能输出到非预料位置

 

最为推荐的做法是:业务代码仅依赖slf4j-api.jar,不依赖其它任何日志包,打包给别人引用时,除了slf-api.jar,其余日志包都声明为“provided”

 






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


京ICP备15018585号