目录
1.概述
1.1.组件概览
1.2.灵活的配置
1.2.1.插件发现机制
1.2.2.插件装配机制
1.2.3.配置文件基本元素与对象的映射关系
2.属性占位符
2.1.概述
2.2.Interpolator插值器
2.3.默认属性配置
3.Logger
3.1.配置示例
3.2.配置详解
3.3.Logger继承机制
4.Appender
4.1.概述
4.2.框架支持的Appender实现
4.3.常用Appender详解
4.3.1.ConsoleAppender
4.3.2.RollingFileAppender
5.Layout
5.1.概述
5.2.PatternLayout
5.2.1.模式字符串
6.Manager
7.Filter
1.概述
1.1.组件概览
在log4j2中,LogManager就是日志的门面,相当于slf4j-api中的LoggerFactory.
框架为每个类加载分配了一个单独的LoggerContext,用于管理所有创建出来的Logger实例.
ContextSelector则负责管理类加载器到对应的LoggerContext实例之间的映射关系.
log4j2中,有5个关键概念:
- LoggerConfig:日志配置,用于整合多个Appender,进行日志打印.
- Appender:追加器,用于操作Layout和Manager,往单一目的地进行日志打印.
- Layout:布局,用于把LogEvent日志事件序列化成字节序列,不同Layout实现具有不同的序列化方式.
- Manager:管理器,用于管理输出目的地,如:RollingFileManager用于管理文件滚动以及将字节序列写入到指定文件中.
- Filter:过滤器,用于对LogEvent日志事件加以过滤,LoggerConfig和Appender都可以配置过滤器,也就是说日志事件会经过一总一分两层过滤.
组件架构如下:
1.2.灵活的配置
1.2.1.插件发现机制
在log4j2中,一切皆插件,框架通过PluginRegistry
扫描并发现插件配置.
PluginRegistry
支持两种扫描方式
- 一种是使用指定的ClassLoader读取classpath下所有的
META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat
文件,产生PluginType
; - 另一种是扫描classpath下指定的packageName,内省带有
@Plugin
注解的类文件,产生PluginType
.
插件配置以PluginType
的形式保存在插件注册表中,PluginType
的作用类似于spring中BeanDefinition
,定义了如何创建插件实例.
插件类通过@PluginFactory
注解或者@PluginBuilderFactory
注解配置插件实例的实例化和属性注入方式.
1.2.2.插件装配机制
log4j2知道如何实例化插件后,我们就可以通过编写配置文件(如:log4j2.xml),进行插件的实例化和属性注入了.
Configuration
全局配置对象负责保存所有解析到的配置.
通过ConfigurationFactory.getConfiguration()
可以使用不同的工厂生产不同的配置对象,不同的Configuration
实现可以解析不同格式的配置,如:xml,yaml,json等.
以xml文件为例,文件中每个元素都会最终对应一个插件实例,元素名称实际就是PluginType中的name,实例的属性可以从子元素对应的实例获取,也可以从自身元素的属性配置获取.
因此,xml中dom树的元素嵌套关系,也就是log4j组件实例的引用嵌套关系.
xml,yaml,json格式文件都可以描述这种嵌套关系,因此log4j2中定义了与文件格式无关的数据结构,Node来抽象配置.
AbstractConfiguration.setup()
负责提取配置,形成Node树.
AbstractConfiguration.doConfigure()
负责根据Node树,进行插件实例化和属性注入.
1.2.3.配置文件基本元素与对象的映射关系
序号 | xml元素 | 工厂方法(类名.方法名) | 对象类型 |
---|---|---|---|
1 | <Properties> | PropertiesPlugin.configureSubstitutor() |
StrLookup |
2 | <Property> | Property.createProperty() |
Property |
3 | <Loggers> | LoggersPlugin.createLoggers() |
Loggers |
4 | <Logger> | LoggerConfig.createLogger() |
LoggerConfig |
5 | <Root> | RootLogger.createLogger() |
LoggerConfig |
6 | <AppenderRef> | AppenderRef.createAppenderRef() |
AppenderRef |
7 | <Filters> | CompositeFilter.createFilters() |
CompositeFilter |
8 | <Appenders> | AppendersPlugin.createAppenders() |
ConcurrentMap<String, Appender> |
2.属性占位符
2.1.概述
在log4j2中,环境变量信息(键值对)被封装为StrLookup对象,该对象作用类似于spring框架中的PropertySource.
在配置文件中,基本上所有的值的配置都可以通过参数占位符引用环境变量信息,格式为:${prefix:key}.
2.2.Interpolator插值器
Interpolator内部以Map<String,StrLookup>的方式,封装了很多StrLookuo对象,key则对应参数占位符${prefix:key}中的prefix.
同时,Interpolator内部还保存着一个没有prefix的StrLookup实例,被称作默认查找器,它的键值对数据来自于log4j2.xml配置文件中的<Properties>元素的配置.
当参数占位符${prefix:key}带有prefix前缀时,Interpolator会从指定prefix对应的StrLookup实例中进行key查询,
当参数占位符${key}没有prefix时,Interpolator则会从默认查找器中进行查询.
Interpolator中默认支持的StrLookup查找方式如下(StrLookup查找器实现类均在org.apache.logging.log4j.core.lookup包下):
序号 | prefix | 插件类型 | 描述 |
---|---|---|---|
1(*) | sys | SystemPropertiesLookup | 从jvm属性中查找 |
2(*) | env | EnvironmentLookup | 从操作系统环境变量中获取value |
3 | marker | MarkerLookup | 判断以指定key为名称的Marker标签是否存在,存在则返回key,否则返回null |
4 | jvmrunargs | JmxRuntimeInputArgumentsLookup | 获取jmx的运行时输入参数 |
5 | bundle | ResourceBundleLookup | 通过ResourceBundle查找value,格式:${prefix:bundleName:bundleKey} |
6 | java | JavaLookup | 获取jvm进程信息,只指定固定的key值,包括:(1)version:jdk版本(2)runtime:运行环境信息(3)vm:虚拟机名称(4)os:操作系统信息(5)hw:硬件信息(6)locale:地区信息 |
7 | main | MainMapLookup | 暂未启用 |
8 | log4j | Log4jLookup | 只支持两个key:(1)configLocation:获取log4j2配置文件的绝对路径(2)configParentLocation:获取配置文件所在目录的绝对路径 |
9 | date | DateLookup | 以指定格式,获取当前系统时间或LogEvent的时间戳,通过key来指定日期格式字符串 |
10 | sd | StructuredDataLookup | 从LogEvent中引用的StructuredDataMessage中获取value |
11 | ctx | ContextMapLookup | 从ThreadContext中获取value |
12 | map | MapLookup | 暂未启用 |
13 | jndi | JndiLookup | 使用jndi(javax.naming)获取value |
2.3.默认属性配置
注意:Properties元素一定要配置在最前面,否则不生效.
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Properties>
<Property name="customKey_1">customValue_1</Property>
<Property name="customKey_2">customValue_2</Property>
</Properties>
</Configuration>
3.Logger
3.1.配置示例
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" dest="err" verbose="false">
<Appenders>
<Console name="console">
<PatternLayout pattern="%date{yyyy-MM-dd HH:mm:ss.SSS} %C.%M %message%n"/>
</Console>
</Appenders>
<Loggers>
<Root additivity="true" level="error" includeLocation="true" >
<AppenderRef ref="console" level="info">
<ThresholdFilter level="warn" onMatch="NEUTRAL" onMismatch="DENY"/>
</AppenderRef>
<Property name="customeKey">customeValue</Property>
<ThresholdFilter level="warn" onMatch="NEUTRAL" onMismatch="DENY"/>
</Root>
<Logger name="com.lixin" additivity="true" level="info" includeLocation="true">
<AppenderRef ref="console" level="info">
<ThresholdFilter level="warn" onMatch="NEUTRAL" onMismatch="DENY"/>
</AppenderRef>
<Property name="customeKey">customeValue</Property>
<ThresholdFilter level="warn" onMatch="NEUTRAL" onMismatch="DENY"/>
</Logger>
</Loggers>
</Configuration>
3.2.配置详解
- additivity:日志可加性,如果配置为true,则在日志打印时,会通过Logger继承关系递归调用父Logger引用的Appender进行日志打印.
注意:该属性默认为true.在递归打印日志时,会忽略父Logger的level配置 - level:用于控制允许打印的日志级别上线,在配置示例中,只有级别<=info的LogEvent才会被放行,级别优先级顺序为OFF<FATAL<ERROR<WARN<INFO<DEBUG<TRACE<ALL
注意:level属性的配置时可选的,在获取level时会通过Logger继承关系递归获取,RootLogger的级别默认为error,其他默认为null.也就是说,如果全都不配置level的话,则所有Logger级别都默认为error. - includeLocation:如果配置为true,则打印日志时可以附带日志点源码位置信息输出.同步日志上下文默认为true,异步默认为false.
- LoggerConfig元素下可以单独配置Property元素,添加属性键值对,这些属性会在每次打印日志时,被追加到LogEvent的contextData中
- LoggerConfig支持配置过滤器,在判断是否打印日志时,先过滤器判断过滤,然后再级别判断过滤.
- AppenderRef:顾名思义,就是配置当前Logger引用的Appender.同时,AppenderRef也支持配置level和Filter,进行更细粒度的日志过滤
- LoggerConfig等于总开关,AppenderRef则为各个子开关,两个开关都通过才能打印日志
3.3.Logger继承机制
log4j2框架会根据LoggerConfig的name建立对象之间的继承关系.这种继承机制与java的package很像,name以点进行名称空间分割,子名称空间继承父名称空间.
名称空间可以是全限定类名,也可以是报名.整个配置树的根节点就是RootLogger.
举例:假如我们的配置的Logger如下:
<Root/>
<Logger name="com"/>
<Logger name="com.lixin.DemoClass"/>
<Logger name="org"/>
<Logger name="org.springframework"/>
当通过LogManager.getLogger(name)获取Logger实例时,会根据name逐级递归直到找到匹配的LoggerConfig,或者递归到Root根节点为止.
4.Appender
4.1.概述
追加器,负责控制Layout进行LogEvent的序列化,以及控制Manager对序列化后的字节序列进行输出.
在log4j2.xml配置文件中,配置方式如下:
<Appenders>
<具体的Appender插件名称>
</具体的Appender插件名称>
</Appenders>
4.2.框架支持的Appender实现
序号 | 工厂方法 | xml元素 | 具体类 | 作用 |
---|---|---|---|---|
1 | NullAppender.createAppender | <Null> | NullAppender | |
2 | ConsoleAppender.newBuilder | <Console> | ConsoleAppender | 控制台输出 |
3 | FileAppender.newBuilder | <File> | FileAppender | 往一个固定文件,流式追加日志 |
5 | RollingFileAppender.newBuilder | <RollingFile> | RollingFileAppender | 日志文件可滚动,滚动策略可配置,可按时间,文件大小等方式.流式追加日志 |
6 | AsyncAppender.newBuilder | <Async> | AsyncAppender | 内部引用一组appender,通过异步线程+队列方式调用这些appender |
7 | RollingRandomAccessFileAppender.newBuilder | <RollingRandomAccessFile> | RollingRandomAccessFileAppender | 日志文件可滚动,使用RandomAccessFile追加日志 |
8 | RandomAccessFileAppender.newBuilder | <RandomAccessFile> | RandomAccessFileAppender | 往一个固定文件,使用RandomAccessFile追加日志 |
8 | OutputStreamAppender.newBuilder | <OutputStream> | OutputStreamAppender | |
9 | MemoryMappedFileAppender.newBuilder | <MemoryMappedFile> | MemoryMappedFileAppender | 比RandomAccessFile性能高 |
10 | JdbcAppender.newBuilder | <JDBC> | JdbcAppender | |
11 | JpaAppender.createAppender | <JPA> | JpaAppender | |
12 | JeroMqAppender.createAppender | <JeroMQ> | JeroMqAppender | |
13 | KafkaAppender.newBuilder | <Kafka> | KafkaAppender | |
14 | JmsAppender.newBuilder | <JMS> <JMSQueue> <JMSTopic> |
JmsAppender | |
15 | Rewrite.createAppender | <Rewrite> | RewriteAppender | |
16 | RoutingAppender.newBuilder | <Routing> | RoutingAppender | 路由追加器 可根据pattern模式字符串,路由到内部管理的其他Appender实例, 支持LogEvent事件重写和Appender定期清理 |
17 | CountingNoOpAppender.createAppender | <CountingNoOp> | CountingNoOpAppender | |
18 | FailoverAppender.createAppender | <Failover> | FailoverAppender | |
19 | ScriptAppenderSelector | <ScriptAppenderSelector> | ||
20 | SmtpAppender.createAppender | <SMTP> | SmtpAppender | |
21 | SocketAppender.newBuilder | <Socket> | SocketAppender | |
22 | SyslogAppender.newBuilder | <Syslog> | SyslogAppender | |
23 | WriterAppender.newBuilder | <Writer> | WriterAppender |
4.3.常用Appender详解
4.3.1.ConsoleAppender
控制台追加器,用于把日志输出到控制台,一般本地调试时使用.
配置示例如下:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" dest="err" verbose="false">
<Appenders>
<!-- follow和direct不能同时为true,如果follow为true则会跟随底层输出流的变化,direct为true则固定指向输出流 -->
<Console name="console" target="SYSTEM_OUT" follow="false" direct="true">
<PatternLayout pattern="%date{yyyy-MM-dd HH:mm:ss.SSS} %C.%M %message%n"/>
</Console>
</Appenders>
<Loggers>
<Root additivity="true" level="error" includeLocation="true" >
<AppenderRef ref="console" level="info"/>
</Root>
</Loggers>
</Configuration>
4.3.2.RollingFileAppender
文件滚动追加器,用于向本地磁盘文件中追加日志,同时可以通过触发策略(TriggeringPolicy)和滚动策略(RolloverStrategy)控制日志文件的分片,避免日志文件过大.
线上环境常用.
常用的触发策略包含两种:
- TimeBasedTriggeringPolicy:基于时间周期性触发滚动,一般按天滚动
- SizeBasedTriggeringPolicy:基于文件大小触发滚动,可以控制单个日志文件的大小上限
滚动策略的实现包含两种:
-
DefaultRolloverStrategy:默认滚动策略
该策略内部维护一个最小索引和最大索引,每次滚动时,会删除历史文件,之后剩余文件全部进行一轮重命名,最后创建新的不带有索引后缀的文件进行日志追加
-
DirectWriteRolloverStrategy:直接写滚动策略
该策略内部会维护一个一直自增的文件索引,每次滚动时直接创建新的带有索引后缀的文件进行日志追加,同步清理历史的文件.
配置示例如下:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" dest="err" verbose="false">
<Properties>
<Property name="logDir">/Users/lixin46/workspace/demo/logdemo/logs</Property>
<Property name="pattern">%date{yyyy-MM-dd HH:mm:ss.SSS} %C.%M %message%n</Property>
</Properties>
<Appenders>
<Console name="console" >
<PatternLayout pattern="${pattern}"/>
</Console>
<!-- 使用DirectWriteRolloverStrategy策略时,不需要配置fileName -->
<RollingFile name="fileAppender" fileName="${logDir}/request.log" filePattern="${logDir}/request.log-%d-%i">
<PatternLayout pattern="${pattern}"/>
<!-- 所有策略中,只要任意策略满足就会触发滚动 -->
<Policies>
<!-- 滚动时间周期,只有数量,单位取决于filePattern中%d的配置 -->
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="10b"/>
</Policies>
<!-- 限制最多保留5个文件,索引自增 -->
<!--<DirectWriteRolloverStrategy maxFiles="5"/>-->
<!-- 限制最多保留5个文件,索引从2到6 -->
<DefaultRolloverStrategy fileIndex="max" min="2" max="6"/>
</RollingFile>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="console" level="info"/>
<AppenderRef ref="fileAppender" level="info" />
</Root>
</Loggers>
</Configuration>
5.Layout
5.1.概述
布局对象,职责是把指定的LogEvent转换成可序列化对象(如:String),或者直接序列化成字节数组.
log4j2支持很多的序列化格式,如:普通模式字符串,JSON字符串,yaml字符串,XML格式字符串,HTML字符串等等.
类体系如下:
5.2.PatternLayout
模式布局是我们最常使用的,它通过PatternProcessor模式解析器,对模式字符串进行解析,得到一个List<PatternConverter>转换器列表和List<FormattingInfo>格式信息列表.
在PatternLayout序列化时,会遍历每个PatternConverter,从LogEvent中取不同的值进行序列化输出.
5.2.1.模式字符串
模式字符串由3部分组成,格式为:%(格式信息)(转换器名称){选项1}{选项2}...
- 格式信息
数据结构如下:
public final class FormattingInfo {
// 默认配置,右对齐,长度不限,左侧截断
private static final FormattingInfo DEFAULT = new FormattingInfo(false, 0, Integer.MAX_VALUE, true);
// 字段最小长度
private final int minLength;
// 字段最大长度
private final int maxLength;
// 是否左对齐,默认为false,长度过短时,左侧填充空白
private final boolean leftAlign;
// 是否左侧截断,默认为true,长度过长时,删除左侧内容
private final boolean leftTruncate;
}
模式字符串的格式为:
%-(minLength).-(maxLength)(转换器名称){选项字符串}
minLength代表字段的最小长度限制,当字段内容长度小于最小限制时,会进行空格填充.
minLength前面的-负责控制对齐方式,默认为右对齐(左边空格填充),如果加上-,则会切换为左对齐方式(右边空格填充)
maxLength代表字段的最大长度限制,当字段内容长度大于最大限制时,会进行内容阶段
maxLength前面的-负责控制阶段方向,默认为左侧阶段,如果加上-,则会切换为右侧阶段
minLength和maxLength之间用点分隔.
格式信息中所有属性都是可选的,不配置,则使用默认值
- 转换器名称
log4j2会通过PluginManager
收集所有类别为Converter的插件,同时分析插件类上的@ConverterKeys
注解,获取转换器名称,并建立名称到插件实例的映射关系.
PatternParser识别到转换器名称的时候,会查找映射.
框架支持的所有转换器如下:
序号 | 名称 | 类型 | 描述 |
---|---|---|---|
1 | d date |
DatePatternConverter | 日志的时间戳 |
2 | p level |
LevelPatternConverter | 日志级别 |
3 | m msg message |
MessagePatternConverter | 日志中的消息内容 |
4 | C class |
ClassNamePatternConverter | 日志打印点所在类的类名 注意:需要给LoggerincludeLocation="true"属性开启位置 |
5 | M method |
MethodLocationPatternConverter | 日志打印点所在方法的方法名 注意:需要给LoggerincludeLocation="true"属性开启位置 |
6 | c logger |
LoggerPatternConverter | Logger实例的名称 |
7 | n | LineSeparatorPatternConverter | 专门追加换行符 |
8 | properties | Log4j1MdcPatternConverter | |
9 | ndc | Log4j1NdcPatternConverter | |
10 | enc encode |
EncodingPatternConverter | |
11 | equalsIgnoreCase | EqualsIgnoreCaseReplacementConverter | |
12 | equals | EqualsReplacementConverter | |
13 | xEx xThroable xException |
ExtendedThrowablePatternConverter | |
14 | F file |
FileLocationPatternConverter | 注意:需要给LoggerincludeLocation="true"属性开启位置 |
15 | l location |
FullLocationPatternConverter | 相当于%C.%M(%F:%L) 注意:需要给LoggerincludeLocation="true"属性开启位置 |
16 | highlight | HighlightConverter | |
17 | L line |
LineLocationPatternConverter | 日志打印点的代码行数 注意:需要给LoggerincludeLocation="true"属性开启位置 |
18 | K map MAP |
MapPatternConverter | |
19 | marker | MarkerPatternConverter | 打印完整标记,格式如:标记名[父标记名[祖父标记名]],一个标记可以有多个父标记 |
20 | markerSimpleName | MarkerSimpleNamePatternConverter | 只打印标记的名称 |
21 | maxLength maxLen |
MaxLengthConverter | |
22 | X mdc MDC |
MdcPatternConverter |
LogEvent.getContextData() 映射诊断上下文 |
23 | N nano |
NanoTimePatternConverter | |
24 | x NDC |
NdcPatternConverter |
LogEvent.getContextStack() 嵌套诊断上下文 |
25 | replace | RegexReplacementConverter | |
26 | r relative |
RelativeTimePatternConverter | |
27 | rEx rThrowable rException |
RootThrowablePatternConverter | |
28 | style | StyleConverter | |
29 | T tid threadId |
ThreadIdPatternConverter | 线程id |
30 | t tn thread threadName |
ThreadNamePatternConverter | 线程名称 |
31 | tp threadPriority |
ThreadPriorityPatternConverter | 线程优先级 |
32 | ex throwable Exception |
ThrowablePatternConverter | 异常 |
33 | u uuid |
UuidPatternConverter | 生成一个uuid,随日志一起打印,用于唯一标识一条日志 |
34 | notEmpty varsNotEmpty variablesNotEmpty |
VariablesNotEmptyReplacementConverter |
- 选项字符串
有时我们需要对特定的转换器进行特殊的配置,如:给DatePatternConverter配置时间格式,这个时候需要通过选项字符串配置.
PatternParser会提取模式字符串中的所有选项,保存在一个List<String>中,每个{}包裹的内容作为一个选项.
当创建转换器时,框架会自动扫描转换器类中声明的静态工厂方法newInstance,同时支持两种可选的形参,一种是Configuration,另一种String[]则会注入选项列表.
选项列表的识别由不同的转换器各自定义.
最后,以一个实际的例子解释配置:
日志会输出时间,类名,方法名,消息以及一个换行符.
同时,我们给DatePatternConverter指定了了时间格式,并且限制全限定类名最小长度为5,右截断,最大为10,左对齐.
<PatternLayout pattern="%date{yyyy-MM-dd HH:mm:ss.SSS} %-5.-10C.%M %message%n"/>
6.Manager
管理器的职责主要是控制目标输出流,以及把保存在ByteBuffer字节缓冲区中的日志序列化结果,输出到目标流中.
如:RollingFileManager需要在每次追加日志之前,进行滚动检查,如果触发滚动还会创建新的文件输出流.
manager继承体系如下:
7.Filter
过滤器的核心职责就是对LogEvent
日志事件进行匹配,匹配结果分为匹配和不匹配,结果值有3种:接受,拒绝,中立.可由用户自定义匹配和不匹配的行为结果.
所有实现了Filterable
接口的组件都可以引用一个过滤器进行事件过滤,包含LoggerConfig
和AppenderControl
等.
框架实现的过滤器如下:
序号 | 工厂方法(类名.方法名) | xml元素 | 作用 |
---|---|---|---|
1 | BurstFilter.newBuilder | <BurstFilter> | |
2 | DynamicThresholdFilter.createFilter | <DynamicThresholdFilter> | |
3 | LevelRangeFilter.createFilter | <LevelRangeFilter> | |
4 | MapFilter.createFilter | <MapFilter> | |
5 | MarkerFilter.createFilter | <MarkerFilter> | |
6 | RegexFilter.createFilter | <RegexFilter> | |
7 | ScriptFilter.createFilter | <ScriptFilter> | |
8 | StructuredDataFilter.createFilter | <StructuredDataFilter> | |
9 | ThreadContextMapFilter.createFilter | <ThreadContextMapFilter><ContextMapFilter> | |
10 | ThresholdFilter.createFilter | <ThresholdFilter> | 根据LogEvent的级别进行过滤,如果LogEvent.level<=ThresholdFilter.level,则返回匹配的结果,否则返回不匹配的结果.如:过滤器为info,日志为error,则error<=info返回匹配结果 |
11 | TimeFilter.createFilter | <TimeFilter> | 判断日志时间是否在指定的时间区间内 |