各种日志实现api介绍
记住,日志总体上来讲,分为两部分:1)给用户使用的api;2)实际执行写入日志工作的核心代码。
有的jar包,包含了上面两部分,即api和实现类就在一个包里面。
- 常用的api类有: slf4j,jcl (jakarta Commons Logging),jul (java.util.logging,jdk自带的),log4j,log4j2
- 实现类: log4j,log4j2,logback
可以看出,log4j和log4j2同时包含了供用户使用的api和实现类。而slf4j只包含有api,没有实现类,因此,单独使用slf4j是无法打印日志的。
除了上面两类之外,还有一类是起到绑定作用或者桥接作用的jar包。绑定是关联api和实现类,将api的操作委托给某个实际执行打印工作的实现类。桥接是将某个api a的操作转交给另外一个api b来做,这样,实际上是api b及其对应的实现类来完成日志的打印工作。
这四个类的关系如下图所示:
可以看出,桥接类主要是关联api类和实际执行打印工作的执行类,桥接类主要是将某个api的操作转交给其他api来做。
-
绑定类:
slf4j-jdk14:slf4j -> jul,将slf4j api的操作绑定到jdk日志框架上。slf4j-log4j12:slf4j -> log4j,将slf4j api的操作绑定到log4j 1.2版本上slf4j-jcl:slf4j -> jcl,将slf4j api的操作绑定到commons-logging(Jakarta Commons Logging)日志框架上。 -
桥接类:
log4j-over-slf4j:log4j -> slf4j,将log4j的日志打印操作桥接到slf4j api的操作上。注意,log4j-over-slf4j和slf4j-log4j12不能同时出现。因为,slf4j-log4j12会将slf4j的打印操作委托给log4j12(绑定),而log4j-over-slf4j有会将log4j的操作转交给slf4j来操作(桥接)。这样,就构成了一个死循环。
jul-to-slf4j:jul -> slf4j,将java.util.logging的日志桥接到slf4j api的操作上。即如果调用jul的api打印日志,则实际上间接调用slf4j的api来打印日志。此时,如果要打印日志,则必须同时含有slf4j -> 某个日志实现类 这个jar包。
jcl-over-slf4j:jcl -> slf4j,将jcl的日志打印桥接到slf4j api的操作上。注意,不能和slf4j-jdk14一起使用。
综合常见的日志打印api和执行类,可以得到如下的汇总图。

从图中可以看出各个jar包所处的位置。注意,图中只是表达了一种通常的使用方式。
1)如果选择log4j -> slf4j桥接类, slf4j -> log4j绑定类,则会出现死循环。
2)现在通常使用slf4j来作为打印日志的api,因此,图中将slf4j放在一个核心的位置。如果为了个人使用,其实也可以直接使用log4j,不需要使用其他日志包了。
log4j2配置文件的各个元素解释
log4j2最简单的配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%n%d{HH:mm:ss.SSS} [%t] %-5level %logger - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<logger name="test" additivity="false">
<appender-ref ref="Console"/>
</logger>
<Root level="warn">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
Configuration:该配置的根元素,代表一个配置。其有一个属性status,用于指定这个当前配置的日志级别,只有当输出的日志信息的日志级别高于或者等于该配置的日志级别时,才会打印日志。比如,如果配置status="warn",如果使用log.warn(msg)打印日志,则会打印日志。 如果使用log.info(msg),则不会。
Appenders:用于定义一系列appender,appender包括ConsoleAppender,FileAppender,SyslogAppender等。appender的作用是接收日志信息,按照PatternLayout指定的格式格式化信息,然后输出到存储设备。存储设备有控制台、文件或者其他远程设备等。
Console:定义了一个控制台appender。name属性是该控制台appender的名字,target是该控制台appender的目标输入,可以是SYSTEM_OUT和SYSTEM_ERR。
PatternLayout:定义了某个appender的日志格式形式。其属性pattern代表了某个格式。
%n:代表一个换行符%d{HH:mm:ss.SSS}:代表某个时间输出格式。这里是其中一种,更多的格式请参考log4j2 layouts。[%t]:代表线程的名字。%-5level:输出的信息的日志级别%logger:logger的名字。当使用private static Logger log = LoggerFactory.getLogger("myLogName");时,在LoggerFactory.getLogger(name)中指定的名字。%msg:打印的日志内容
Loggers: 用于定义一系列logger。logger的作用是捕获打印事件,并传给appender来处理。
Root:默认的根logger,是所有logger的父元素。当没有配置其他logger时,就会使用该根logger。比如,我们通过LoggerFactory.getLogger(UserServiceImpl.class)得到一个日志的实例,该实例的名字是该类的全路径名com.chris.com.chris.service.impl.UserServiceImpl。由于没有名字为com.chris.com.chris.service.impl.UserServiceImpl的logger,因此,就会取Root元素对应的配置。
logger:用于定义某个自定义的logger。name属性配置该logger的名字,appender-ref通过名字指向之前定义的某个appender。比如,当我们通过LoggerFactory.getLogger("test"),就可以得到获得name=”test”的logger。additivity="false"告诉该logger不需要调用父logger的appender。
logger和appender对应关系是多对多的,即一个logger可以使用多个appender,一个appender也可以被多个logger使用。
日志级别
日志级别是日志框架对于不同日志信息重要程度的区分。用户在打印日志时,可以通过设置某个属性,来决定某条日志信息的重要程度。日志框架根据配置的日志级别,从而决定是否打印该信息。比如,日志的配置为只要日志严重级别为”warn”及以上的才进行打印,那么,当用户使用log.info()时,是无法打印出日志的。如果想打印出日志,那么,用户可以有两种做法:1)使用log.warn()及以上级别的方法;2)改变日志配置为”info”及以下级别的配置。
log4j提供的日志级别从高到低为:
- OFF
- FATAL
- ERROR
- WARN
- INFO
- DEBUG
- TRACE
- ALL
log4j提供的日志级别如下图所示。

注意:这里需要区分日志信息的日志级别a和配置的日志级别b。如果a高于等于b,那么,该日志信息就会打印;否则,就无法打印。
日志打印流程
我们来看一个实际执行打印日志的例子,然后通过debug的方式来看看日志是怎么打印的。 这里,我是采用了slf4j + log4j2的方式。引入日志包如下:
<dependency>
<groupId>org.apache.logging.log4j</groupId> <!-- log4j2来执行实际的打印工作 -->
<artifactId>log4j-core</artifactId>
<version>2.2</version>
</dependency>
<dependency> <!-- 打印日志的api类 -->
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.10</version>
</dependency>
<dependency> <!-- 桥接:告诉Slf4j使用Log4j2 -->
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.2</version>
</dependency>
log4j2的配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
打印日志的操作为:
@Override
public void loadUser(int userId) {
log.info("--------user id is {}--------", userId);
}
调用栈如下:
调用jar包的流程是:slf4j -> log4j-slf4j-impl -> log4j-api -> log4j-core。
在继续往下读之前,可以想一想,打印日志的本质,其实就是将某个msg输出到某个设备中,比如,控制台,或者文件,或者远程设备,底层肯定是通过java的OutputStream来输出msg信息的。而日志框架的工作,将这个操作进行了封装,包括下面的优点:
1)更加方便使用的api;
2)具有日志级别,通过配置文件来达到灵活配置的目的;
3)打印日志更加高效,可以做到同步打印和异步打印。
流程图如下:
可以看出,实际执行打印日志工作的操作几乎都在log4j里面,slf4j主要是api的门面类。最终打印的时候,是通过OutputStream来输出打印信息的。
常见问题及解决
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/Users/chris/.m2/repository/org/slf4j/slf4j-log4j12/1.6.1/slf4j-log4j12-1.6.1.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/Users/chris/.m2/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.2/log4j-slf4j-impl-2.2.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]
根据报错信息可以看出,是由于存在多个slf4j的绑定包:slf4j-log4j12和log4j-slf4j-impl。根据自己的需要,exclude其中一个jar包应该就可以了。
ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console.
ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console.
根据报错信息,是由于没有找到log4j2的配置文件。需要确定项目路径里面含有log4j2的配置文件。关于log4j2的配置文件,请参考Automatic Configuration
总结
本文主要是讲了各种类型的日志包以及它们的关系,log4j2的配置文件以及log4j规定的日志级别,从而让读者可以理解项目中的日志配置。