SLF4j、Commons-logging、jboss-logging

Log4j、logback、Log4j2

日志源于log,有航海日志的意思。记录海员记录每天的行程,生活及发生的事件

软件开发领域用来监控代码中变量变化,跟踪代码运行的轨迹

开发环境中担当调试器作用,向控制台或文件等输出;生产环境中记录程序的警告和错误

招之即来的System.out.println

最方便,受众最广,知名度最高

第一个HelloWorld的例子开始就接触它

public class Hello {
  public static void main(String args[])
  {
    System.out.println("Hello, world!");
  }
}
System.out.println("My name is " + name + ", I am " + age + " years old.");

优点显而易见

缺点

  • 只能输出到控制台
  • 大量的代码混在业务逻辑中,在生产环境中需要处理这些影响性能的代码
  • 字符串拼接容易带来性能损失(当然可以使用StringBuffer,但又稍显繁琐)

Java5 才有加入无比怀念C语言中的printf

System.out.printf("My name is %S, I am %d years old.", name, age);

可读性和效率都比上面的好很多

土制utils小工具

System.out.println最大问题是从开发环境向生产环境转换时带的性能问题(当然其它的问题也不少),部署时应该去除或注释这些代码

public class SysUtils {

  public static final void log(Object o) {
     System.out.println(String.valueOf(o));
  }
}

以后的调试代码全部调用上面的静态方法:

SysUtils.log("My name is " + name + ", I am " + age + " years old.");

部署的时候,只要把SysUtils.log里的System.out.println 注释掉就万事大吉了

然而一切似乎还是没有改变,功能太弱,和代码耦合性太强…于是日志(log)出现了

常见日志框架、实现

接口

  • Commons-logging
  • SLF4j
  • jboss-logging

实现

  • Log4j
  • logback
  • Log4j2


  • log4j

日志系统抽象封装成 Logger 、appender 、pattern 等

  • logback

分成三个模块:logback-core,logback-classic 和 logback-access

logback-core 是其它两个模块的基础模块

logback-classic log4j 的一个改良版本

logback-access 与 Servlet 容器集成提供通过 Http 来访问日记的功能


common-logging

用户可以自由选择第三方的日志组件作为具体实现(日志实现jar包放进classpath),如log4j.jar,jdk 自带的 logging等

通过动态查找的机制,程序运行时自动找出真正使用的日志库

内部有一个 Simple logger 实现,但是功能很弱,通常配合log4j 来使用

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class XXXService {
    private static final Log log = LogFactory.getLog(XXXService.class);
    public void doSomething(){
        log.info("begin dosomething....");
    }
}

slf4j

Simple Logging Facade for JAVA,类似于 Common-Logging

使用 SLF4J 时必须选择正确的 SLF4J 的 jar 包的集合(各种桥接包)

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class XXXService {
    private static final Logger logger = LoggerFactory.getLogger(XXXService.class);
    public void doSomething() {
        logger.info("begin dosomething...");
    }
}

slf4j vs common-logging

common-logging 通过动态查找机制,运行时自动找出真正使用的日志库 OSGI不同插件使用自己的 ClassLoader,这种机制保证了插件互相独立,然而却使 Common-Logging 无法工作

slf4j 编译时静态绑定真正的 Log 库, 可以在OSGI 中使用

支持参数化的 log 字符串,避免之前为了减少字符串拼接的性能损耗而写的 if(logger.isDebugEnable())

现在可直接写:logger.debug(“current user is: {}”, user)

拼装消息被推迟到了它能够确定是不是要显示这条消息的时候,但是获取参数的代价并没有幸免

slf4j 静态绑定原理

编译时会绑定 import org.slf4j.impl.StaticLoggerBinder; 该类里面实现对具体日志方案的绑定接入

基于 slf4j 的实现都有一个这个类,如果发现类路径下有多个 StaticLoggerBinder,会给出警告

 logger.info("my name is {}", "dd");
 logger.info("my name is " + "dd");

效率上,第一行比第二行更高,因为如果当前日志级别是 ERROR, 第一行不会进行字符串拼接 第二行,无论日志级别是什么,都会先进行字符串拼接

为了解决这个问题,commons-logging 等框架提供了下面的方式:

 if (log.isDebugEnabled()){
        log.debug("dddd"+"eee");
    }

而 slf4j 却不需要,因为它的日志级别不满足的时候不会进行字符串拼接

slf4j 与其他各种日志组件的桥接

应用代码中使用 slf4j 接口,接入具体实现的方法

1

应用代码中使用别的日志接口,转成 slf4j 的方法

2

slf4j桥接器

老程序组件当中用了 JCL,还有一些组件可能直接调用了 java.util.logging,需要一个桥接器(XXX-over-slf4j.jar)把它们的日志输出重定向到 SLF4J

桥接器就是一个假的日志实现工具,比如jcl-over-slf4j.jar,即使某个组件原本是通过 JCL 输出日志的,现在会被 jcl-over-slf4j “骗到”SLF4J 里,然后 SLF4J 又会根据绑定器把日志交给具体的日志实现工具

log to Apache Commons Logging>jcl-over-slf4j.jar — (redirect) —> SLF4j —> slf4j-log4j12-version.jar —> log4j.jar —> 输出日志

假如同时放置 log4j-over-slf4j.jar 和 slf4j-log4j12-version.jar 会发生什么情况?日志会被踢来踢去,最终进入死循环

slf4j 根据 classpath 里的Jar 来决定具体日志实现库

  • logback-classic
  • slf4j-jcl.jar(apache commons logging)
  • slf4j-logj12.jar(log4j 1.2.4)
  • slf4j-jdk14(java.util.logging)

旧代码的日志调用转到 slfj

  • jcl-over-slf4j.jar:apache commons logging
  • log4j-over-slf4j.jar:log4j
  • jul-to-slf4j:jdk logging,需要在程序开始时调用 SLF4JBridgeHandler.install() 来注册 listener 参考 JulOverSlf4jProcessor,可在 applicationContext.xml 中定义该 bean 来实现初始化

log4j2

  • Logback输出日志产生异常时,不会告诉被调用方,而 log4j 2可配置

  • 使用Disruptor 无锁异步,多线程程序中,吞吐量比 Log4j 1.x 和 logback 高 10 倍,而时间延迟更低

  • 插件机制,更灵活

扩展 appenders,Filters,Layouts,Lookups 和 Pattern Converters 更简单 比如直接 flume-appender

Log4j 2 尽可能使用 Java5 提供的对并发及锁支持的工具类

Lo4j 1.x 有死锁的 bug

Logback 中修复了 log4j 1.x 的很多 bug,但是,logback 中的有很多类采用同步机制(导致性能下降)

建议一般程序员查看的日志改成异步方式,一些运营日志改成同步

异步日志在程序的 classpath 需要加载 disruptor-3.0.0或者更高的版本


两种异步日志

  • 全异步模式

不需要修改修改原理的配置文件,Logger 仍然使用 <root> and <logger>

只需要在主程序代码开头,加一句系统属性的代码

System.setProperty("Log4jContextSelector","org.apache.logging.log4j.core.async.AsyncLoggerContextSelector");  
  • 异步和非异步混合输出模式

配置文件中 Logger 使用 or

<loggers>  
    <AsyncLogger name="AsyncLogger" level="trace" includeLocation="true">  
        <appender-ref ref="Console" />  
        <appender-ref ref="debugLog" />  
        <appender-ref ref="errorLog" />  
    </AsyncLogger>  
  
    <asyncRoot level="trace" includeLocation="true">  
        <appender-ref ref="Console" />  
    </asyncRoot>   
</loggers>  

好多开源框架使用的日志组件不尽相同

可能一个项目中,不同的版本,不同的框架共存,导致日志输出异常混乱

虽然也不至于对系统造成致命伤害,但是明显可以看出,架构不够精良,追求极致略有不足


update

阿里java规约已经强制 slf4j

上lombok,类上只要 @Slf4j

slf4j-api 1.8.x 静态绑定机制废弃( 不再搜索 StaticLoggerBinder 类),用 Java 6 引入的 java.util.ServiceLoader