Log4j的应用说明与原理

眉间皱痕 提交于 2020-02-27 12:56:38
日志能够反映出程序系统的运行状况以及详细的运行轨迹,很多情况下对系统排错就是依据日志来进行的,记录日志是调试程序的一种手段。这一篇要介绍的log4j就是一个功能强大且灵活的日志库,Log4j是纯java实现的不依赖于其他类库,并且是开源项目,因此log4j在java程序范畴中非常流行,开发者能够使用log4j灵活的处理程序中的日志打印。

核心组件:logger,appender和layout

Log4j中最核心的几个概念是logger,appender以及layout,这些组件在内部组合起来实现日志消息的打印。

Logger及其层级关系

Logger是log4j暴露给用户打印日志的核心组件,为了能够灵活的控制日志打印,log4j会给每个Logger进行分类,这样就可以实现哪些Logger的日志打印是生效的,哪些Logger的日志打印不生效。区分Logger是通过给它设置一个名字实现的,Logger的层次关系也通过其名字来体现,名字采用java包命名规范的形式,通过“.”分隔号来分割Logger的层次,例如“a.b”标记的logger就是“a.b.c”的父logger。

顶层的root logger

“root”是一个特殊的logger,root logger内建在log4j系统中,它处于所有logger的最上层,是所有其他logger的祖先。

Logger的日志级别

Logger有一个“level”属性,表示此logger的日志级别,Log4j内建了多种日志级别:TRACE,DEBUG,INFO,WARN,ERROR,FATAL,OFF等,这些日志级别是有优先级顺序的,后面也会说明优先级别的重要性,另外我们也可以扩展Level类实现自己的日志级别,不过也没必要,系统默认提供的日志级别已经够用了。
 
这里重点需要知道的是,logger的日志级别可以通过继承获得,如果此logger没有标记一个明确的日志级别,那么其日志级别继承自最近的祖先logger,要让所有logger默认都能获得一个日志级别,root logger必需有一个默认的日志级别。
 
下面使用几个例子来展示日志级别继承的规则:
 
示例1
logger
标记的日志级别
继承的日志级别
root
Proot
Proot
x
-
Proot
x.y
-
Proot
x.y.z
-
Proot
当所有logger都没有标记日志级别时,它们的日志级别都继承自root logger的日志级别。
 
示例2
logger
标记的日志级别
继承的日志级别
root
Proot
Proot
x
Px
Px
x.y
Pxy
Pxy
x.y.z
Pxyz
Pxyz
这个例子中,所有logger都表了一个日志级别,因此它们不会继承日志级别。
 
示例3
logger
标记的日志级别
继承的日志级别
root
Proot
Proot
x
Px
Px
x.y
-
Px
x.y.z
Pxyz
Pxyz
这个例子中,由于 x.y 没有设置日志级别,它会继承自最近的祖先 x 的日志级别,因此 x.y 的日志级别也是Px,
而 x 和 x.y 设置了日志级别,因此不会继承。
 
示例4
logger
标记的日志级别
继承的日志级别
root
Proot
Proot
x
Px
Px
x.y
-
Px
x.y.z
-
Px
这个示例中,x.y 和 x.y.z 都没有自己的日志级别,因此都继承了 x 的日志级别。

获取Logger对象

在程序中要打印日志,首先我们要有一个logger对象,Logger类提供了获取logger对象的静态方法,下面的代码展示获取一般logger和root logger的方法:
Logger logger = Logger.getLogger(“x.y”);
Logger logger = Logger.getLogger(A.class);

// 获取root logger
Logger root = Logger.getRootLogger(); 

什么情况下日志请求生效

打印日志的请求也会被赋予一个级别,Logger类提供了以下这些日志打印请求方法:

public void trace(Object message);
public void trace(Object message, Throwable t);
public void debug(Object message);
public void debug(Object message, Throwable t);
public void error(Object message);
public void error(Object message, Throwable t);
public void fatal(Object message);
public void fatal(Object message, Throwable t);
public void info(Object message);
public void info(Object message, Throwable t);
public void warn(Object message);
public void warn(Object message, Throwable t);

// 下面两个是更通用的打印方法
public void log(Priority priority, Object message);
public void log(Priority priority, Object message, Throwable t);
 
这些方法的名字体现了日志请求的级别,Log4j规定: 只有日志请求的级别高于或者等于logger的级别时,日志请求才会生效。这意味着log4j中的日志级别是按照顺序排列的,默认的级别顺序是:ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL。
 
例如假设loggerA的日志级别是WARN,则“loggerA.info(“...”)”的日志打印则不会生效,因为请求级别INFO低于loggerA的日志级别WARN。

Appender,Layout

我们希望可以把日志打印到不同的地方,例如有时候把日志打印到控制台就可以了,另一些时候需要打印到文件中,有时为了性能可能又需要把日志先记录到一个异步队列中,在log4j中,日志具体输出的目的地就是用Appender来表示的,log4j提供了不同类型的appender可用,例如ConsoleAppender,FileAppender,SocketAppender,JdbcAppender,我们也可以实现自己需要的appender。
 
Appender是通过Logger的addAppender(appender)方法来关联的,一个logger可以关联多个appender对象。

appender additivity性质

“appender additivity”表示的意思就是: 生效的日志请求会被输出到此logger关联的所有appender中,也会被输出到所有上层logger关联的所有appender中,一直到root logger的所有appender。例如 “x.y” 关联一个SocketAppender,”x“ 关联一个FileAppender和一个ConsoleAppender,root关联一个ConsoleAppender,那么”x.y“请求的日志消息会同时被输出到SocketAppender,ConsoleAppender和FileAppender。
 
上述这个规则是log4j默认的。
 
我们可以通过设置Logger的“additivity”属性值为false来禁止这个规则,对于上述例子如果我们在“x.y”这个logger对象上设置为false,那么“x.y”请求的日志消息只会输出到SocketAppender中。
 
 
可以给appender对象关联一个layout对象,layout和appender各自的功能不同,layout是控制日志消息输出格式的,而appender是控制日志输出目的地的。同样的,layout也有多种实现,我们最常用的是PatternLayout,PatternPayout用来控制格式时功能强大,它采用模式占位符来描述最终日志的打印格式,类似C语言的printf函数,例如这个模式描述符“ %r [%t] %-5p %c - %m%n”控制的输出结果为:
 
         176 [main] INFO  org.foo.Bar - Located nearest gas station.
 
其中“%r”表示的是程序运行的时间,“%t”表示当前线程名字,“%-5p”表示日志请求级别,此字段占5个字符宽度并使用左对齐,“%c”表示logger的名字,“%m”表示日志消息内容。
 
Log4j还提供一个SimpleLayout,它控制简单的日志格式打印,其输出结果格式仅仅包含日志请求级别和日志消息内容:
 
        DEBUG - Hello world
 
SimpleLayout显然功能不如PatternLayout强大,但是它性能高,在只需要简单记录日志的情况下也不错。

Filter链

Appender中还维护了一个Filter链,它提供了addFilter(filter)和getFilter()方法,如名字所示,Filter作用于日志请求内容并起过滤作用。
 
Filter采用责任链的设计思想,其核心方法是decide(LogEvent ),filter链中的所有decide方法按顺序处理日志事件并决定日志事件是被丢弃还是保留,此方法返回一个枚举整数值,取直包括:
  • DENY
  • NUETRAL
  • ACCEPT
如果返回DENY,则表示不再需要咨询下一个filter了,直接丢弃日志事件。
 
如果返回NUETRAL,则表示还需要咨询下一个filter来决定如何处理日志事件,如果当前已经是最后一个filter,则日志会被输出到目的地。
 
如果返回ACCEPT,则表示不需要在咨询下一个filter了,日志会被输出到目的地。
 
Filter的实现细节在Appender的基本实现类AppenderSkeleton中,详细可以参考 AppenderSkeleton的代码。

threshold值

在AppenderSkeleton中还有一个比较重要的属性:threshold,所有具体的Appender都会通过继承获得这个属性,threshold的值表示此appender的日志级别,如果请求的日志事件的级别低于threshold表示的日志级别,则日志事件被此appender丢弃。
 
在配置文件中,通过“Threshold”属性来指定值,例如:log4j.appender.file.Threshold=WARN。

配置文件写法

大多数情况下我们一般使用两种类型的配置文件:properties文件格xml文件。
 
Log4j内部使用Configurator组件来配置系统,Configurator可以从流对象中加载配置信息,也可以从一个URL表示的资源处加载配置信息。PropertiesConfigurator支持解析properties配置文件,DOMConfigurator支持解析xml配置文件。
 
这里的配置示例说明采用properties形式。
 
全局范围的配置
 
        log4j.threshold=[level]
 
repository范围的threshold属性定义全局的日志级别,指定此属性后所有logger的日志级别将被忽略,然后所有的日志请求级别只要低于此级别就会被丢弃,默认此threshold的日志级别是ALL,因此默认它不会影响任何日志请求。
 
 
        log4j.reset=true
 
配置上述属性后,log4j会在配置日志系统之前清理已经存在logger层级关系。
 
 
        log4j.debug=true
 
配置此属性后,log4j的加载配置日志系统的细节信息将会打印在控制台。
 
Logger配置
 
配置root logger:
        log4j.rootLogger=[level], appenderName, appenderName, …
 
level值是可选的,可以设置log4j内建的ALL,TRACE,DEBUG,INFO,WARN,ERROR,FATAL,OFF或者自定义级别,自定义级别的写法为“level/custom-level-classname”。
 
rootLogger的level值是可以省略的,如果省略的话则采用的是系统默认值。
 
配置其他logger:
 
        log4j.logger.loggerName=[level | INHERITED | NULL], appenderName, appenderName, …
 
INHERITED值指定此logger的日志级别应该从上层logger继承获得,NULL值和INHERITEd意思相同。
 
配置additivity:
        log4j.additivity.loggerName=false
 
Appender配置
 
配置appender:
        # appender名字可以包含“.”字符
        log4j.appender.appenderName=fully.qualified.name.of.appender.class
 
设置appender的属性:
        log4j.appender.appenderName.option1=value1
        log4j.appender.appenderName.option2=value2
 
设置appender的layout及属性:
        log4j.appender.appenderName.layout=fully.qualified.name.of.layout.class
        log4j.appender.appenderName.layout.option1=value1
         log4j.appender.appenderName.layout.option2=value2
 
设置appender的filter及属性:
        # ID是唯一的名字用来标识filter
        # 多个filter按照ID的字母排序添加到appender中
        log4j.appender.appenderName.filter.ID=fully.qualified.name.of.filter.class
        log4j.appender.appenderName.filter.ID.option1=value1
         log4j.appender.appenderName.filter.ID.option2=value2
 
设置appender的ErrorHandler:
        log4j.appender.appenderName.errorhandler=fully.qualified.name.of.errorhandler.class
        log4j.appender.appenderName.errorhandler.root-ref=[true|false]
        log4j.appender.appenderName.errorhandler.logger-ref=loggerName
        log4j.appender.appenderName.errorhandler.appender-ref=appenderName
         log4j.appender.appenderName.errorhandler.option1=value1
         log4j.appender.appenderName.errorhandler.option2=value2
 
 
Filter和ErrorHandler配置一般使用的少些,很多情况下直接配置logger和appender就可以了。

Log4j的初始化

Log4j的初始化相当直接,当我们调用任意一种“Logger.getLogger()”的重载形式时,Log4j就会自动初始化自己,因为在LogManager类中有一个静态初始化器,JVM加载LogManager时log4j就会获得初始化配置。
 
LogManager中的初始化算法描述如下:
  1. 首先要检查jvm系统属性“log4j.defaultInitOverride”是否存在,如果不存在活着存在并且属性值为false,那么就会进行初始化配置,否则跳过初始化配置过程,不进行下面的过程了。
  2. 确定log4j初始化所需要的配置,首先检查jvm系统属性“log4j.configuration”,我们可以通过此属性指定log4j的配置文件,如果没有指定“log4j.configuration”属性,那么尝试使用“log4j.xml”作为配置文件,如果不存在“log4j.xml”,最后尝试使用“log4j.properties”作为配置。注意要把“log4j.xml”或者“log4j.properties”文件放在classpath下,因为最终log4j采用system classloader来加载这个文件。
  3. log4j尝试把配置资源转换成一个URL对象,如果能够转换成一个URL对象,接下来就开始解析配置资源并配置log4j系统。
 
实际配置日志系统的工作是通过Configurator组件来完成的,如果配置资源是xml形式的,则采用DOMConfigurator,否则采用PropertyConfigurator,我们也可以通过“log4j.configuratorClass”系统属性来指定具体要使用的Configurator的类名。
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!