Log4j日志体系结构

摘要

咱们在写日志的时候首先要获取logger,在每个使用log4j的项目都有不少个地方要获取logger,这些logger是真实的被实例化的Logger对象,他们有可能被分散在无数不一样的类中,日志体系结构讲的是这些logger对象是如何组织的,他们之间又有什么样的关系。java

 

体系结构

咱们举个具体的实例来看看,假设个人项目包结构以下:app

项目结构分布式

说明一下:com.flu.jdk包下面有两个类分别是LogTest1和LogTest2,而后在包com.flu包下面有一个LogTest3类,很显然,com.flu.jdk包是com.flu包的子包。假设咱们在这三个类中分别经过LogManager.getLogger(xxx.class)获取三个logger实例,他们分别是logger一、logger2和logger3,咱们将要讨论这三个logger的关系。性能

值得注意的是log4j的日志体系中,有一个特殊的日志对象叫作root(根),他是始终存在的,假设咱们首先获取logger实例,log4j将构造下面这样一个图形(我不能把它叫作树):this

当只有logger1的时候spa

当咱们获取logger2实例的时候,这个图将变成:debug

当加入logger2日志实例时结构图设计

当咱们获取logger3实例的时候,这个图又变了一个样,以下:日志

当加入logger3日志实例以后code

仅仅才三个日志实例,图就搞的略复杂,可想log4j应用中,将会有无数的日志实例按照这个规律组成纷繁复杂的有向图结构,虽然看起来杂乱,可是又规律。那么问题来了,这样的结构有什么用呢?下一节咱们将会看到这种结构对于日志配置继承的影响。

 

配置继承

log4j日志级别定义

在往下面看以前咱们先来看看log4j对日志级别的定义:

public final static int OFF_INT = Integer.MAX_VALUE;
public final static int FATAL_INT = 50000;
public final static int ERROR_INT = 40000;
public final static int WARN_INT  = 30000;
public final static int INFO_INT  = 20000;
public final static int DEBUG_INT = 10000;
  //public final static int FINE_INT = DEBUG_INT;
public final static int ALL_INT = Integer.MIN_VALUE;

很显然,log4j的日志级别有下面的关系:

OFF > FATAL > ERROR > WARN > INFO > DEBUG > ALL

log4j在写日志的时候只有当当前写日志的级别大于等于当前日志实例的配置级别的时候,日志写操做才生效,好比当前日志实例的配置级别为INFO,那么log.info会写成功,而log.debug则不会写。

 

日志写源码剖析

咱们来看看一句简单的log.info("this is log message")的背后,先来看看一段源代码:

public void info(Object message) {
  if(repository.isDisabled(Level.INFO_INT))
    return;
  if(Level.INFO.isGreaterOrEqual(this.getEffectiveLevel()))
    forcedLog(FQCN, Level.INFO, message, null);
}
public boolean isDisabled(int level) {
  return thresholdInt > level;
}

首先看看当前写的日志级别是否被禁止的,默认的状况下thresholdInt为ALL,所以INFO的级别显然比ALL大,所以下面会继续看看INFO的级别是否大于等于当前日志实例生效的级别,this.getEffectiveLevel()获取的实例是什么呢?咱们继续看看代码:

public Level getEffectiveLevel() {
   for(Category c = this; c != null; c=c.parent) {
     if(c.level != null)
		return c.level;
   }
   return null; // If reached will cause an NullPointerException.
 }

当前日志生效的级别逻辑为首先看看当前日志实例是否有配置级别,若是没有,那么就继续找当前日志实例的parent节点,按照上一节中所表述的,若是当前日志的日志级别没有配置,当找到root的日志级别,并根据root的日志级别来判定是否继续进行日志写。这里体现了日志级别的继承关系,其实不单单是日志级别,日志其余相关的配置也会基于这种继承的特性,好比appender组件等。

 

项目应用

了解Log4j的日志体系结构以及日志级别配置的继承特性以后,咱们如今应该比较清楚项目中应该如何配置了。以Log4j.xml配置文件为例子,知足基本需求咱们只须要配置root这个日志实例的日志级别便可,以下:

<root>
    <level value="INFO" />
    <appender-ref ref="CONSOLE" />
</root>

上面配置了root日志实例的日志级别为INFO,若是获取按照必定规范(当前类的权限定名做为日志实例名),那么咱们能够保证全部的日志实例将继承root所配置的日志级别。

 

配置隔离

上面的配置略粗糙,假如咱们想为不一样的模块给予不一样的配置怎么办呢?最多见的是业务日志与中间件日志,好比咱们的业务业务包名为com.dianping.biz,而咱们的rpc组件的包名字为com.dianping.pigeon,则咱们可使用下面方法给予不一样的模块不一样的配置:

<!--业务日志配置-->
<category name="com.dianping.biz">
    <level value="INFO" />
    <appender-ref ref="CONSOLE" />
</category>

<!--pigeon组件日志配置-->
<category name="com.dianping.pigeon">
    <level value="DEBUG" />
    <appender-ref ref="CONSOLE" />
</category>

经过上面的配置,咱们能够指定com.dianping.biz包下面全部类获取的logger都继承name为com.dianping.biz的日志配置,而com.dianping.pigeon包下面的全部类获取的logger都继承name为com.dianping.pigeon的日志篇日志。不过一般设计良好的中间件都定制了日志配置以确保中间件日志与业务日志隔离。

 

总结

昨天有个同事对log4j进行了一些分享,会上听的意犹未尽所以课下忍不住扒一扒log4j的内裤,日志做为java应用的一项重要内容,其不单单包括日志如何写、以什么格式写、以及日志写到哪里的问题,还包括性能、扩展性、分布式、日志实时分析等方面问题,本文在介绍log4j日志体系的基础之上稍微聊一下项目应用于配置隔离相关内容,若是读者有兴趣能够深刻研究,一定收货满满。

相关文章
相关标签/搜索