日志能够反映出程序系统的运行状况以及详细的运行轨迹,很多情况下对系统排错就是依据日志来进行的,记录日志是调试程序的一种手段。这一篇要介绍的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中的初始化算法描述如下:
-
首先要检查jvm系统属性“log4j.defaultInitOverride”是否存在,如果不存在活着存在并且属性值为false,那么就会进行初始化配置,否则跳过初始化配置过程,不进行下面的过程了。
-
确定log4j初始化所需要的配置,首先检查jvm系统属性“log4j.configuration”,我们可以通过此属性指定log4j的配置文件,如果没有指定“log4j.configuration”属性,那么尝试使用“log4j.xml”作为配置文件,如果不存在“log4j.xml”,最后尝试使用“log4j.properties”作为配置。注意要把“log4j.xml”或者“log4j.properties”文件放在classpath下,因为最终log4j采用system classloader来加载这个文件。
-
log4j尝试把配置资源转换成一个URL对象,如果能够转换成一个URL对象,接下来就开始解析配置资源并配置log4j系统。
实际配置日志系统的工作是通过Configurator组件来完成的,如果配置资源是xml形式的,则采用DOMConfigurator,否则采用PropertyConfigurator,我们也可以通过“log4j.configuratorClass”系统属性来指定具体要使用的Configurator的类名。
来源:oschina
链接:https://my.oschina.net/u/3227308/blog/3176020