初探log4j
log4j 1.x
log4j是目前较为常用的日志框架,该项目由apache进行维护。(其他常见的日志框架还有java.util.logging,Logback)。Log4j相比较于System.out.println 的一个最大优势,就在于他将日志进行了归类,即划分了不同的优先级。通过优先级的划分,log4j可以控制输出哪些日志,不输出哪些日志。
那么log4j是如何做到的呢?我想从结构相对简单的log4j 1.x说起。事实上,从2015年 8月5日起,apache已经宣称不再继续维护Log4j 1,而转而支持Log4j 2【1】。Log4j 2在Log4j 1的基础上进行了一系列的增强和扩展,但是基本思路并没有本质的改变,即都是通过继承的层级实现了对日志输出的控制。Log4j 1的结构相对简单,所以本文将从Log4j 1开始讲起,介绍Log4j的基本思路,然后扩展到Log4j 2的一些特性。
在介绍log4j 1.x的组件之前,让我们首先看看他是如何对日志划分优先级的。默认情况下,日志的优先级按照从低到高的顺序可以列为:
TRACE,DEBUG,INFO,WARN,ERROR 和FATAL。
这些优先级的含义可以从其名字粗略的推断出来,详细的说明可以查看文档【2】。
简单来说log4j 1.x主要包含三个组件:Loggers,Appenders 和Layout。
- Logger
Logger是Log4j的核心类,大多数和日志有关的操作都是通过Logger来进行。例如,要输出一条error级别的日志:
Logger logger = LogManager.getLogger();logger.error("trace level");
Logger有自己的名字,他的名字是区分大小写的,logger和Logger之间通过名字定义继承关系。
Named Hierarchy
A logger is said to be an ancestor of another logger if its name followed by a dot is a prefix of >the descendant logger name. A logger is said to be a parent of a child logger if there are no >ancestors between itself and the descendant logger.
举个简单的例子,一个命名为“com.foo”的logger是命名为“com.foo.Bar”的logger的父亲。在继承树的顶端是一个默认的根logger,由Log4j维护(类似java 类型树的根节点Object)。运行以下代码将创建一个logger,并将其加入以root logger为根的继承树
LogManager.getLogger()
如果不特殊指定,新创建的Logger从其祖先节点继承日志的优先级。
- Appender
Appender定义了日志输出的目标,包括console,file,GUI组件,远程套接服务器,JMS等等。为同一个Logger可以添加多个appender,并且一个logger将继承其所有祖先的appender,也就是说执行
logger.error("trace level");
将向所有添加到该logger及其祖先的appender输出一条日志。
- Layout
Layout决定了日志输出的格式。
可以使用Configuration 文件对log4j 进行配置:
# Set root logger level to DEBUG and its only appender to A1.
log4j.rootLogger=DEBUG, A1
# A1 is set to be a ConsoleAppender.
log4j.appender.A1=org.apache.log4j.ConsoleAppender
# A1 uses PatternLayout.
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
log4j 2
以上我们简单浏览了log4j 1 的一些基本特性,接下来我们看看在此基础上,log4j 2做了哪些改进。
首先来看一下log4j 2的架构图:
log_4_j_2.png
通过架构图,不难看出,log4j主要增加了一下几个组件:Filter,LoggerContext,LoggerConfig,StrSubsitutor和StrLookup【3】。下面逐一对其进行介绍。
- Filter:
Filter类似防火墙的filter【4】,对于每一个传入的日志请求,每个filter都可以返回三种结果:Accept,Deny或者Neutral。
- Accept:日志请求可以被处理,不管还有没有必要调用其他filter。
- Deny:日志请求将被直接返回。
- Neutral:日志请求将被传输给其他的filter。
Filter是对依据logLevel(日志优先级)自动过滤机制的补充。
- LoggerConfig
LoggerConfig:在log4j 2中,logger之间的层级关系是通过LoggerConfig实现的。LoggerConfig 有自己的名字,他的名字是区分大小写的,LoggerConfig和LoggerConfig之间通过名字定义继承关系。
Named Hierarchy
A LoggerConfig is said to be an ancestor of another LoggerConfig if its name followed by a >dot is a prefix of the descendant logger name. A LoggerConfig is said to be a parent of >a child LoggerConfig if there are no ancestors between itself and the descendant >LoggerConfig.
举个简单的例子,一个命名为“com.foo”的LoggerConfig是命名为“com.foo.Bar”的LoggerConfig的父亲。在继承树的顶端是一个默认的根LoggerConfig,由Log4j维护。
每个在configuration 中声明的Logger都有一个LoggerConfig,不同Logger之间可能会共享同一个LoggerConfig。当创建一个logger的时候,将为其关联一个LoggerConfig,具体关联到哪个,遵从以下规则:
- LoggerConfig的名字和logger相同
- LoggerConfig的名字和父包相同。
- 根LoggerConfig
每个LoggerConfig上关联着Filter和Appender,用于日志事件的实际分发。
-
LoggerContext:
log4j 2支持一些新的应用场景,例如可能会有多个应用共享同一个环境,而每个应用都希望拥有独立的日志环境,在这种情况下,需要为每个需要建立独立日志环境的应用配置各自的LoggerContext【4】。每个LoggerContext都有自己的Configuration,这个configuration包含所有的appender,context-wide filter,LoggerConfig等组件。 -
StrSubsitutor和StrLookup
这两个组件借鉴于Apache Commons Lang。他们提供了一种从System Properties, onfiguration 文件和ThreadContext Map内获取变量引用的机制 LogEvent.【6】
总体来说,log4j 2在保留了log4j 1的诸多特性的同时,配置更加灵活,支持更多的场景(包括多线程),自动重加载, 支持基于context data,markers,regular 表达式,和其他组件的filter,此外,极大的改善了性能。
Sample
最后,是一个简单的sample。
-
首先将log4j所需jar包导入工程:
config_log4j.png - 其次,在src/main/resoures下建立log4j配置文件
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<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="error">
<AppenderRef ref="Console" />
</Root>
</Loggers>
</Configuration>
- 添加代码:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Log4jHelloWorld {
public static void main(String...strings){
Logger logger = LogManager.getLogger();
logger.error("trace level");
}
}
- 运行:
13:39:52.521 [main] ERROR Log4jHelloWorld - trace level
如果没有配置文件,运行没有问题,但是log4j会抱怨找不到配置文件,而且日志只会在console中输出。
ERROR StatusLogger No Log4j 2 configuration file found. Using default configuration (logging only errors to the console), or user programmatically provided configurations. Set system property 'log4j2.debug' to show Log4j 2 internal initialization logging. See https://logging.apache.org/log4j/2.x/manual/configuration.html for instructions on how to configure Log4j 2
13:37:48.723 [main] ERROR Log4jHelloWorld - trace level
【1】log4j 1.x
【2】log4j log level
【3】architecture
【4】Iptables
【5】Logging Separation
【6】commons-lang