Logback项目(版本为1.2.3)分为三个模块,官方GitHub地址为:Logback官方源码地址
<module>logback-core</module>
<module>logback-classic</module>
<module>logback-access</module>
另外还有两个模块,logback-site
和logback-examples
用于提供站点资源和模板案例,一般项目使用日志不会包含这两个模块。所以本系列在后续不再谈论这两个模块。logback-classic
提供slf4j日志接口的基本实现,logback-access
搭配Servlet容器提供了通过Http访问日志的功能。作为基础模块,二者均依赖于logback-core
。logback的三大基础抽象是Logger
、Appender
、Layout
。通过Logger的消息类型和打印级别决定是否需要打印日志。
<logger name="chapters.configuration.Foo" level="debug">
<appender-ref ref="FILE" />
</logger>
在上面配置当中,定义了一个logger节点,它的名称为chapters.configuration.Foo
,而级别为debug。
public class Foo {
static final Logger logger = LoggerFactory.getLogger(Foo.class);
public void doIt() {
logger.debug("Did it again!");
}
}
在上面这个doIt方法执行的时候,通过logger打印debug日志。首先在配置文件当中包含有对应的logger,其次级别也相同,所以会打印日志。而如果在xml中定义的level与java代码中的级别不同呢?这就要取决于二者级别的大小了。关于Logger有两点非常重要,第一个就是名称,第二个就是级别。
- 名称
Logger是基于名称(name)的,通过相同的名称(大小写敏感)在同一个系统(Logger上下文)当中,获取是同一个对象,也就是说名称是唯一的。在一般的项目当中,都会采用类的名称作为Logger对象的名称,但是这会带来一个非常麻烦的问题,就是定义Logger的级别问题,如果每个Logger都需要在配置文件中定义level,那将是一种灾难,且不说配置文件会巨大,而且修改难度非常大,所以,必须有一种方式可以简化配置。这也是名称的第二个关键,数据结构,所有的Logger在一起构成一棵树,而树的根就是所谓的root节点,在配置文件中
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
在代码中可以通过以下方式获取到root节点Logger对象
Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
所有的Logger根据名称挂到这个root节点下面。根据包名决定是否为父子节点。假如当前上下文当中存在名称为名称为chapters.introduction
的logger、名称为chapters.configuration.MyApp1
的logger1, 名称为chapters.configuration.MyApp2
的logger2, 则可以认为root节点作为树根,logger作为root的子节点,logger-logger1和logger2作为logger的根节点。如下图所示
之所以要将logger的节点都构造成一个树的结构,目的是为了方便进行日志级别的控制。比如以下配置logback.xml
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned by default the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<!-- Strictly speaking, the level attribute is not necessary since -->
<!-- the level of the root level is set to DEBUG by default. -->
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>
代码如下
package ch.qos.logback.test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LoggerNameTest {
private static final Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
private static final Logger logger = LoggerFactory.getLogger("chapters.introduction");
private static final Logger logger1 = LoggerFactory.getLogger("chapters.introduction.MyApp1");
private static final Logger logger2 = LoggerFactory.getLogger("chapters.introduction.MyApp2");
public static void main(String[] args) {
logger.debug("------------chapters.introduction---------------");
logger1.debug("------------chapters.introduction.MyApp1---------------");
logger2.debug("------------chapters.introduction.MyApp2---------------");
}
}
由于root的level为debug,所以这些子节点都集成了父节点的level,都为debug。最后输出如下
可见三个logger都打印了结果,也就是日志控制级别为debug级别
如果在配置文件中加入以下配置
<logger name="chapters.introduction" level="INFO" />
此时logger的级别变为了INFO级别(INFO>DEBUG),关于级别的问题后面详述,这里只需要知道如果logger控制输出级别为INFO,那么使用调用debug输出因为级别过小,所以不会输出结果。按照树形数据结构以及继承法则,logger1和logger2这里都继承logger的级别,以上的测试是没有输出结果。
从源码来分析一下
从上图也可以看出,其实我们上面介绍的树形结构严格来说是不对的,这里包含了一个名称为chapters的节点,chapters.introduction并不是直接作为root的节点。如果用户只在代码中定义了一个如下的logger
private static final Logger logger2 = LoggerFactory.getLogger("chapters.introduction.MyApp2");
那么logger组成的树形结构如下所示
名称作为logger的唯一标识,同时通过树形结构建立彼此之间的关联。对应Logger类的继承结构如下所示
注意到这里其实有两个Logger类,其实最下面的那个类是ch.qos.logback.classic.Logger
,上面的接口是org.slf4j.Logger
,前者是后者在logback-classic中的实现。Logger的name保存名称、而parent、childrenList则用于保证树形结构。
/**
* The name of this logger
*/
private String name;
/**
* The parent of this category. All categories have at least one ancestor
* which is the root category.
*/
transient private Logger parent;
/**
* The children of this logger. A logger may have zero or more children.
*/
transient private List<Logger> childrenList;
在parent属性注释当中,包含了几个重要的信息,首先这里出现了一个词category
,也就是类别的意思,任何的类别都有只是一个祖先,因为根类别是必定存在,而所有类别都是挂载在根类别上的。所以所谓的类别,其实就是分类,也可以说是分层,同时还有祖先和后代的称呼,当然这些在数据结构中树固有的称谓了。这里也不继续深究了。
- 级别
上面的Logger类除了name、parent、childrenList三个属性之外,还有另外两个属性
// The assigned levelInt of this logger. Can be null.
transient private Level level;
// The effective levelInt is the assigned levelInt and if null, a levelInt is
// inherited form a parent.
transient private int effectiveLevelInt;
这里所谓的level本身是logback-classic
中的ch.qos.logback.classic.Level
类,这个类中包含以下几个级别。 OFF, ERROR, WARN, INFO, DEBUG, TRACE and ALL
,所谓的大小最后也是映射到数字。如下所示
public static final int OFF_INT = Integer.MAX_VALUE;
public static final int ERROR_INT = 40000;
public static final int WARN_INT = 30000;
public static final int INFO_INT = 20000;
public static final int DEBUG_INT = 10000;
public static final int TRACE_INT = 5000;
public static final int ALL_INT = Integer.MIN_VALUE;
public static final Integer OFF_INTEGER = OFF_INT;
public static final Integer ERROR_INTEGER = ERROR_INT;
public static final Integer WARN_INTEGER = WARN_INT;
public static final Integer INFO_INTEGER = INFO_INT;
public static final Integer DEBUG_INTEGER = DEBUG_INT;
public static final Integer TRACE_INTEGER = TRACE_INT;
public static final Integer ALL_INTEGER = ALL_INT;
比如DEBUG对应数字10000,而INFO对应数字20000,这也是为啥说DEBUG级别要小于INFO级别。最后所有级别的排序最后为OFF > ERROR > WARN > INFO > DEBUG > TRACE > ALL
。另一个属性effectiveLevelInt,所谓的有效级别,就是如果当前logger定义了level属性,则为level属性的值,没有定义level,则继承自父亲,如果父亲没有,则延着树结构向上查找,直到root,而root在Logger上下文构造的时候创建的,它的级别默认为DEBUG。对应源码如下所示
public LoggerContext() {
super();
this.loggerCache = new ConcurrentHashMap<String, Logger>();
this.loggerContextRemoteView = new LoggerContextVO(this);
this.root = new Logger(Logger.ROOT_LOGGER_NAME, null, this);
this.root.setLevel(Level.DEBUG);
loggerCache.put(Logger.ROOT_LOGGER_NAME, root);
initEvaluatorMap();
size = 1;
this.frameworkPackages = new ArrayList<String>();
}
以下为创建子logger的时候直接将父logger的有效级别设置给子logger.
之所以Logback这类的日志框架要优于System.out或是System.err,一个很重要的原因就是通过级别可以有效控制日志是否真实进行打印。要知道,当代码编写完成之后,使用System.out或是System.err就必定会打印,而使用logger调用对应的方法,比如info时,是否打印取决于当前logger对象的等级,所以,logger的等级系统是优于System.out的关键。比如通过调用debug方法来打印日志时
然后将当前logger的有效等级与方法的等级进行比较
在这里,INFO大于DEBUG,不需要进行打印,方法直接返回。
以上详细介绍了Logger的五个关键属性以及对应的作用,首先name保证了logger的唯一性,配合parent和childrenList将Logger上下文中的所有Logger实例组成了一棵树形结构,同时默认必定存在一个root logger,这样每个Logger都必定存在一个祖先。其次通过level和effectiveLevelInt,用户可以方便通过配置来控制是否需要真实打印日志。然后再结合树形结构的继承来实现level的继承,这样同样设置一个logger的等级就达到控制它的后代(未设置等级)的等级。这样就不需要一个logger一个logger设置等级了。可以说,这里的数据结构(树)是精髓所在