目录
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.1.1写日志逻辑
3.1.2 Additive
3.2.配置详解
3.3.Logger继承机制
3.4构建LoggerConfig树
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
AbstractFilterBuilder
BurstFilter
DynamicThresholdFilter
MapFilter
LevelRangeFilter
MarkerFilter
RegexFilter
ThresholdFilter
TimeFilter
ScriptFilter
CompositeFilter
StructuredDataFilter
ThreadContextMapFilter
在log4j2中,LogManager就是日志的门面,相当于slf4j-api中的LoggerFactory.
框架为每个类加载分配了一个单独的LoggerContext,用于管理所有创建出来的Logger实例.
ContextSelector则负责管理类加载器到对应的LoggerContext实例之间的映射关系.
log4j2中,有5个关键概念:
组件架构如下:
组件架构
在log4j2中,一切皆插件,框架通过PluginRegistry
扫描并发现插件配置.
PluginRegistry
支持两种扫描方式
META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat
文件,产生PluginType
;@Plugin
注解的类文件,产生PluginType
.插件配置以PluginType
的形式保存在插件注册表中,PluginType
的作用类似于spring中BeanDefinition
,定义了如何创建插件实例.
插件类通过@PluginFactory
注解或者@PluginBuilderFactory
注解配置插件实例的实例化和属性注入方式.
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树,进行插件实例化和属性注入.
序号 | xml元素 | 工厂方法(类名.方法名) | 对象类型 |
---|---|---|---|
1 | PropertiesPlugin.configureSubstitutor() |
StrLookup | |
2 | Property.createProperty() |
Property | |
3 | LoggersPlugin.createLoggers() |
Loggers | |
4 | LoggerConfig.createLogger() |
LoggerConfig | |
5 | RootLogger.createLogger() |
LoggerConfig | |
6 | AppenderRef.createAppenderRef() |
AppenderRef | |
7 | CompositeFilter.createFilters() |
CompositeFilter | |
8 | AppendersPlugin.createAppenders() |
ConcurrentMap |
public enum StandardLevel {
OFF(0),
FATAL(100),
ERROR(200),
WARN(300),
INFO(400),
DEBUG(500),
TRACE(600),
ALL(Integer.MAX_VALUE);
}
public final class Level
{
public boolean isMoreSpecificThan(final Level level) {
return this.intLevel <= level.intLevel;
}
public boolean isInRange(final Level minLevel, final Level maxLevel) {
return this.intLevel >= minLevel.intLevel && this.intLevel <= maxLevel.intLevel;
}
}
在log4j2中,环境变量信息(键值对)被封装为StrLookup对象,该对象作用类似于spring框架中的PropertySource.
在配置文件中,基本上所有的值的配置都可以通过参数占位符引用环境变量信息,格式为:${prefix:key}.
Interpolator内部以Map
同时,Interpolator内部还保存着一个没有prefix的StrLookup实例,被称作默认查找器,它的键值对数据来自于log4j2.xml配置文件中的
当参数占位符${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 |
注意:Properties元素一定要配置在最前面,否则不生效.
customValue_1
customValue_2
customeValue
customeValue
//AbstractLogger
public void debug(final Marker marker, final MessageSupplier msgSupplier, final Throwable t) {
logIfEnabled(FQCN, Level.DEBUG, marker, msgSupplier, t);
}
//AbstractLogger
public void logIfEnabled(final String fqcn, final Level level, final Marker marker,
final MessageSupplier msgSupplier, final Throwable t) {
if (isEnabled(level, marker, msgSupplier, t)) {
logMessage(fqcn, level, marker, msgSupplier, t);
}
}
//具体 Logger
public boolean isEnabled(final Level level, final Marker marker, final String message, final Object... params) {
return privateConfig.filter(level, marker, message, params);
}
//privateConfig
boolean filter(final Level level, final Marker marker, final String msg, final Object... p1) {
final Filter filter = config.getFilter();
if (filter != null) {
final Filter.Result r = filter.filter(logger, level, marker, msg, p1);
if (r != Filter.Result.NEUTRAL) {
return r == Filter.Result.ACCEPT;
}
}
return level != null && intLevel >= level.intLevel();
}
//AbstractLogger
protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message,
final Throwable t) {
logMessage(fqcn, level, marker, messageFactory.newMessage(message), t);
}
//Logger
public void logMessage(final String fqcn, final Level level, final Marker marker, final Message message,
final Throwable t) {
final Message msg = message == null ? new SimpleMessage(Strings.EMPTY) : message;
final ReliabilityStrategy strategy = privateConfig.loggerConfig.getReliabilityStrategy();
strategy.log(this, getName(), fqcn, marker, level, msg, t);
}
//具体 DefaultReliabilityStrategy
public void log(final Supplier reconfigured, final String loggerName, final String fqcn, final Marker marker, final Level level,
final Message data, final Throwable t) {
loggerConfig.log(loggerName, fqcn, marker, level, data, t);
}
//LoggerConfig
private void processLogEvent(final LogEvent event, LoggerConfigPredicate predicate) {
event.setIncludeLocation(isIncludeLocation());
if (predicate.allow(this)) {
callAppenders(event);
}
logParent(event, predicate);
}
//LoggerConfig
protected void callAppenders(final LogEvent event) {
final AppenderControl[] controls = appenders.get();
//noinspection ForLoopReplaceableByForEach
for (int i = 0; i < controls.length; i++) {
controls[i].callAppender(event);
}
}
//AppenderControl
public void callAppender(final LogEvent event) {
if (shouldSkip(event)) {
return;
}
callAppenderPreventRecursion(event);
}
//AppenderControl
private void callAppenderPreventRecursion(final LogEvent event) {
try {
recursive.set(this);
callAppender0(event);
} finally {
recursive.set(null);
}
}
//AppenderControl
private void callAppender0(final LogEvent event) {
ensureAppenderStarted();
if (!isFilteredByAppender(event)) {
tryCallAppender(event);
}
}
//AppenderControl
private void tryCallAppender(final LogEvent event) {
try {
appender.append(event);
} catch (final RuntimeException ex) {
handleAppenderError(ex);
} catch (final Exception ex) {
handleAppenderError(new AppenderLoggingException(ex));
}
}
//Appender
void append(LogEvent event);
//AbstractOutputStreamAppender
@Override
public void append(final LogEvent event) {
try {
tryAppend(event);
} catch (final AppenderLoggingException ex) {
error("Unable to write to stream " + manager.getName() + " for appender " + getName() + ": " + ex);
throw ex;
}
}
private void tryAppend(final LogEvent event) {
if (Constants.ENABLE_DIRECT_ENCODERS) {
directEncodeEvent(event);
} else {
writeByteArrayToManager(event);
}
}
protected void directEncodeEvent(final LogEvent event) {
// getLayout().encode(event, manager);
getLayout().encode(event, manager);
if (this.immediateFlush || event.isEndOfBatch()) {
manager.flush();
}
}
//PatternLayout
public void encode(final LogEvent event, final ByteBufferDestination destination) {
if (!(eventSerializer instanceof Serializer2)) {
super.encode(event, destination);
return;
}
final StringBuilder text = toText((Serializer2) eventSerializer, event, getStringBuilder());
final Encoder encoder = getStringBuilderEncoder();
encoder.encode(text, destination);
trimToMaxSize(text);
}
//PatternLayout
private StringBuilder toText(final Serializer2 serializer, final LogEvent event,
final StringBuilder destination) {
return serializer.toSerializable(event, destination);
}
protected void writeByteArrayToManager(final LogEvent event) {
//getLayout().toByteArray(event),layout格式化数据
final byte[] bytes = getLayout().toByteArray(event);
if (bytes != null && bytes.length > 0) {
manager.write(bytes, this.immediateFlush || event.isEndOfBatch());
}
}
//OutputStreamManager
protected void write(final byte[] bytes, final boolean immediateFlush) {
write(bytes, 0, bytes.length, immediateFlush);
}
protected synchronized void write(final byte[] bytes, final int offset, final int length, final boolean immediateFlush) {
if (immediateFlush && byteBuffer.position() == 0) {
writeToDestination(bytes, offset, length);
flushDestination();
return;
}
if (length >= byteBuffer.capacity()) {
// if request length exceeds buffer capacity, flush the buffer and write the data directly
flush();
writeToDestination(bytes, offset, length);
} else {
if (length > byteBuffer.remaining()) {
flush();
}
byteBuffer.put(bytes, offset, length);
}
if (immediateFlush) {
flush();
}
}
protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) {
try {
getOutputStream().write(bytes, offset, length);
} catch (final IOException ex) {
throw new AppenderLoggingException("Error writing to stream " + getName(), ex);
}
}
private void processLogEvent(final LogEvent event, LoggerConfigPredicate predicate) {
event.setIncludeLocation(isIncludeLocation());
if (predicate.allow(this)) {
callAppenders(event);
}
logParent(event, predicate);
}
private void logParent(final LogEvent event, final LoggerConfigPredicate predicate) {
if (additive && parent != null) {
parent.log(event, predicate);
}
}
log4j2框架会根据LoggerConfig的name建立对象之间的继承关系.这种继承机制与java的package很像,name以点进行名称空间分割,子名称空间继承父名称空间.
名称空间可以是全限定类名,也可以是报名.整个配置树的根节点就是RootLogger.
举例:假如我们的配置的Logger如下:
配置树
当通过LogManager.getLogger(name)获取Logger实例时,会根据name逐级递归直到找到匹配的LoggerConfig,或者递归到Root根节点为止.
//AbstractConfiguration,在初始化配置时设置层次。
//通过key查找,如果未找到自己的,往上找祖先。
public LoggerConfig getLoggerConfig(final String loggerName) {
LoggerConfig loggerConfig = loggerConfigs.get(loggerName);
if (loggerConfig != null) {
return loggerConfig;
}
String substr = loggerName;
while ((substr = NameUtil.getSubName(substr)) != null) {
loggerConfig = loggerConfigs.get(substr);
if (loggerConfig != null) {
return loggerConfig;
}
}
return root;
}
public synchronized void addLoggerFilter(final org.apache.logging.log4j.core.Logger logger, final Filter filter) {
final String loggerName = logger.getName();
final LoggerConfig lc = getLoggerConfig(loggerName);
if (lc.getName().equals(loggerName)) {
//如果是自己的配置
lc.addFilter(filter);
} else {
//不是自己的配置,则新创建一个配置,设置parent
final LoggerConfig nlc = new LoggerConfig(loggerName, lc.getLevel(), lc.isAdditive());
nlc.addFilter(filter);
nlc.setParent(lc);
loggerConfigs.putIfAbsent(loggerName, nlc);
setParents();
logger.getContext().updateLoggers();
}
}
public synchronized void addLoggerAppender(final org.apache.logging.log4j.core.Logger logger,
final Appender appender) {
final String loggerName = logger.getName();
appenders.putIfAbsent(appender.getName(), appender);
final LoggerConfig lc = getLoggerConfig(loggerName);
if (lc.getName().equals(loggerName)) {
lc.addAppender(appender, null, null);
} else {
final LoggerConfig nlc = new LoggerConfig(loggerName, lc.getLevel(), lc.isAdditive());
nlc.addAppender(appender, null, null);
nlc.setParent(lc);
loggerConfigs.putIfAbsent(loggerName, nlc);
setParents();
logger.getContext().updateLoggers();
}
}
public synchronized void setLoggerAdditive(final org.apache.logging.log4j.core.Logger logger, final boolean additive) {
final String loggerName = logger.getName();
final LoggerConfig lc = getLoggerConfig(loggerName);
if (lc.getName().equals(loggerName)) {
lc.setAdditive(additive);
} else {
final LoggerConfig nlc = new LoggerConfig(loggerName, lc.getLevel(), additive);
nlc.setParent(lc);
loggerConfigs.putIfAbsent(loggerName, nlc);
setParents();
logger.getContext().updateLoggers();
}
}
private void setParents() {
for (final Map.Entry entry : loggerConfigs.entrySet()) {
final LoggerConfig logger = entry.getValue();
String key = entry.getKey();
if (!key.isEmpty()) {
//根据点号(.)按层次分别设置parent
final int i = key.lastIndexOf('.');
if (i > 0) {
key = key.substring(0, i);
LoggerConfig parent = getLoggerConfig(key);
if (parent == null) {
parent = root;
}
logger.setParent(parent);
} else {
logger.setParent(root);
}
}
}
}
追加器,负责控制Layout进行LogEvent的序列化,以及控制Manager对序列化后的字节序列进行输出.
在log4j2.xml配置文件中,配置方式如下:
<具体的Appender插件名称>
具体的Appender插件名称>
序号 | 工厂方法 | xml元素 | 具体类 | 作用 |
---|---|---|---|---|
1 | NullAppender.createAppender | NullAppender | ||
2 | ConsoleAppender.newBuilder | ConsoleAppender | 控制台输出 | |
3 | FileAppender.newBuilder | FileAppender | 往一个固定文件,流式追加日志 | |
5 | RollingFileAppender.newBuilder | RollingFileAppender | 日志文件可滚动,滚动策略可配置,可按时间,文件大小等方式.流式追加日志 | |
6 | AsyncAppender.newBuilder | AsyncAppender | 内部引用一组appender,通过异步线程+队列方式调用这些appender | |
7 | RollingRandomAccessFileAppender.newBuilder | RollingRandomAccessFileAppender | 日志文件可滚动,使用RandomAccessFile追加日志 | |
8 | RandomAccessFileAppender.newBuilder | RandomAccessFileAppender | 往一个固定文件,使用RandomAccessFile追加日志 | |
8 | OutputStreamAppender.newBuilder | OutputStreamAppender | ||
9 | MemoryMappedFileAppender.newBuilder | MemoryMappedFileAppender | 比RandomAccessFile性能高 | |
10 | JdbcAppender.newBuilder | JdbcAppender | ||
11 | JpaAppender.createAppender | JpaAppender | ||
12 | JeroMqAppender.createAppender | JeroMqAppender | ||
13 | KafkaAppender.newBuilder | KafkaAppender | ||
14 | JmsAppender.newBuilder | JmsAppender | ||
15 | Rewrite.createAppender | RewriteAppender | ||
16 | RoutingAppender.newBuilder | RoutingAppender | 路由追加器 可根据pattern模式字符串,路由到内部管理的其他Appender实例, 支持LogEvent事件重写和Appender定期清理 |
|
17 | CountingNoOpAppender.createAppender | CountingNoOpAppender | ||
18 | FailoverAppender.createAppender | FailoverAppender | ||
19 | ScriptAppenderSelector | |||
20 | SmtpAppender.createAppender | SmtpAppender | ||
21 | SocketAppender.newBuilder | SocketAppender | ||
22 | SyslogAppender.newBuilder | SyslogAppender | ||
23 | WriterAppender.newBuilder | WriterAppender |
控制台追加器,用于把日志输出到控制台,一般本地调试时使用.
配置示例如下:
文件滚动追加器,用于向本地磁盘文件中追加日志,同时可以通过触发策略(TriggeringPolicy)和滚动策略(RolloverStrategy)控制日志文件的分片,避免日志文件过大.
线上环境常用.
常用的触发策略包含两种:
滚动策略的实现包含两种:
DefaultRolloverStrategy:默认滚动策略
该策略内部维护一个最小索引和最大索引,每次滚动时,会删除历史文件,之后剩余文件全部进行一轮重命名,最后创建新的不带有索引后缀的文件进行日志追加
默认策略
DirectWriteRolloverStrategy:直接写滚动策略
该策略内部会维护一个一直自增的文件索引,每次滚动时直接创建新的带有索引后缀的文件进行日志追加,同步清理历史的文件.
直接写策略
配置示例如下:
/Users/lixin46/workspace/demo/logdemo/logs
%date{yyyy-MM-dd HH:mm:ss.SSS} %C.%M %message%n
布局对象,职责是把指定的LogEvent转换成可序列化对象(如:String),或者直接序列化成字节数组.
log4j2支持很多的序列化格式,如:普通模式字符串,JSON字符串,yaml字符串,XML格式字符串,HTML字符串等等.
类体系如下:
layout类体系
模式布局是我们最常使用的,它通过PatternProcessor模式解析器,对模式字符串进行解析,得到一个List
在PatternLayout序列化时,会遍历每个PatternConverter,从LogEvent中取不同的值进行序列化输出.
模式字符串由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
当创建转换器时,框架会自动扫描转换器类中声明的静态工厂方法newInstance,同时支持两种可选的形参,一种是Configuration,另一种String[]则会注入选项列表.
选项列表的识别由不同的转换器各自定义.
最后,以一个实际的例子解释配置:
日志会输出时间,类名,方法名,消息以及一个换行符.
同时,我们给DatePatternConverter指定了了时间格式,并且限制全限定类名最小长度为5,右截断,最大为10,左对齐.
管理器的职责主要是控制目标输出流,以及把保存在ByteBuffer字节缓冲区中的日志序列化结果,输出到目标流中.
如:RollingFileManager需要在每次追加日志之前,进行滚动检查,如果触发滚动还会创建新的文件输出流.
manager继承体系如下:
manager继承体系
过滤器的核心职责就是对LogEvent
日志事件进行匹配,匹配结果分为匹配和不匹配,结果值有3种:接受,拒绝,中立.可由用户自定义匹配和不匹配的行为结果.
所有实现了Filterable
接口的组件都可以引用一个过滤器进行事件过滤,包含LoggerConfig
和AppenderControl
等.
框架实现的过滤器如下:
序号 | 工厂方法(类名.方法名) | xml元素 | 作用 |
---|---|---|---|
1 | BurstFilter.newBuilder | ||
2 | DynamicThresholdFilter.createFilter | ||
3 | LevelRangeFilter.createFilter | ||
4 | MapFilter.createFilter | ||
5 | MarkerFilter.createFilter | ||
6 | RegexFilter.createFilter | ||
7 | ScriptFilter.createFilter | ||
8 | StructuredDataFilter.createFilter | ||
9 | ThreadContextMapFilter.createFilter | ||
10 | ThresholdFilter.createFilter | 根据LogEvent的级别进行过滤,如果LogEvent.level<=ThresholdFilter.level,则返回匹配的结果,否则返回不匹配的结果.如:过滤器为info,日志为error,则error<=info返回匹配结果 | |
11 | TimeFilter.createFilter | 判断日志时间是否在指定的时间区间内 |
enum Result {
/**
* The event will be processed without further filtering based on the log Level.
*/
ACCEPT,
/**
* No decision could be made, further filtering should occur.
*/
NEUTRAL,
/**
* The event should not be processed.
*/
DENY;
public static Result toResult(final String name) {
return toResult(name, null);
}
public static Result toResult(final String name, final Result defaultResult) {
return EnglishEnums.valueOf(Result.class, name, defaultResult);
}
}
用于根据配置文件中设置创建Filter
public static abstract class AbstractFilterBuilder> {
public static final String ATTR_ON_MISMATCH = "onMismatch";
public static final String ATTR_ON_MATCH = "onMatch";
@PluginBuilderAttribute(ATTR_ON_MATCH)
private Result onMatch = Result.NEUTRAL;
@PluginBuilderAttribute(ATTR_ON_MISMATCH)
private Result onMismatch = Result.DENY;
public Result getOnMatch() {
return onMatch;
}
public Result getOnMismatch() {
return onMismatch;
}
/**
* Sets the Result to return when the filter matches. Defaults to Result.NEUTRAL.
* @param onMatch the Result to return when the filter matches.
* @return this
*/
public B setOnMatch(final Result onMatch) {
this.onMatch = onMatch;
return asBuilder();
}
/**
* Sets the Result to return when the filter does not match. The default is Result.DENY.
* @param onMismatch the Result to return when the filter does not match.
* @return this
*/
public B setOnMismatch(final Result onMismatch) {
this.onMismatch = onMismatch;
return asBuilder();
}
@SuppressWarnings("unchecked")
public B asBuilder() {
return (B) this;
}
}
频率控制过滤器
level :BurstFilter过滤的事件级别
rate :每秒允许的 log 事件的平均值
maxBurst:当BurstFilter过滤的事件超过 rate 值,排队的 log 事件上限。超过此上限的 log ,将被丢弃。默认情况下 maxBurst = 100*rate
按以上配置,假定每个 log 事件的执行时间较长,输出 117 个 log 事件( INFO级别)到RollingFileAppenders,BurstFilter会过滤得到INFO级别的 log 事件,之后会发生: 16 个 log 事件在执行, 100 个等待执行, 1 个被丢弃。
private static final long NANOS_IN_SECONDS = 1000000000;
//默认
private static final int DEFAULT_RATE = 10;
private static final int DEFAULT_RATE_MULTIPLE = 100;
private static final int HASH_SHIFT = 32;
private BurstFilter(final Level level, final float rate, final long maxBurst, final Result onMatch,
final Result onMismatch) {
super(onMatch, onMismatch);
this.level = level;
this.burstInterval = (long) (NANOS_IN_SECONDS * (maxBurst / rate));
//构造maxBurst个令牌
for (int i = 0; i < maxBurst; ++i) {
available.add(createLogDelay(0));
}
}
private Result filter(final Level level) {
if (this.level.isMoreSpecificThan(level)) {
//DelayQueue history = new DelayQueue<>();
LogDelay delay = history.poll();
while (delay != null) {
available.add(delay);
delay = history.poll();
}
delay = available.poll();
if (delay != null) {
//获取到令牌,则表示match
delay.setDelay(burstInterval);
history.add(delay);
return onMatch;
}
return onMismatch;
}
return onMatch;
}
public static class Builder extends AbstractFilterBuilder
{
public BurstFilter build() {
if (this.rate <= 0) {
this.rate = DEFAULT_RATE;
}
if (this.maxBurst <= 0) {
//默认为100 * rate
this.maxBurst = (long) (this.rate * DEFAULT_RATE_MULTIPLE);
}
return new BurstFilter(this.level, this.rate, this.maxBurst, this.getOnMatch(), this.getOnMismatch());
}
}
可以过滤具有特定的属性某一级别的日志
如果用户的登录ID被捕获在ThreadContext的Map中则可以启用debug级的日志
private Result filter(final Level level, final ReadOnlyStringMap contextMap) {
//
MapFilter可以对Map中的信息进行过滤,进而记录特定事件,比如登入、退出
protected boolean filter(final Map data) {
boolean match = false;
for (int i = 0; i < map.size(); i++) {
//map.getKeyAt(i) key="eventId"
final String toMatch = data.get(map.getKeyAt(i));
//map.getValueAt(i) value="Login"
match = toMatch != null && ((List) map.getValueAt(i)).contains(toMatch);
if ((!isAnd && match) || (isAnd && !match)) {
break;
}
}
return match;
}
private Result filter(final Level level) {
return level.isInRange(this.minLevel, this.maxLevel) ? onMatch : onMismatch;
}
对LogEvent中的Marker 进行过滤
private Result filter(final Marker marker) {
return marker != null && marker.isInstanceOf(name) ? onMatch : onMismatch;
}
LogManager.getLogger((Class>)caller[0]).error(MarkerManager.getMarker("FATALMARKER"), caller[1] + ": " + value);
对格式化消息和非格式化消息进行正则匹配过滤
private Result filter(final String msg) {
if (msg == null) {
return onMismatch;
}
final Matcher m = pattern.matcher(msg);
return m.matches() ? onMatch : onMismatch;
}
对level进行过滤
private Result filter(final Level testLevel) {
return testLevel.isMoreSpecificThan(this.level) ? onMatch : onMismatch;
}
基于时间段的日志过滤
Result filter(final long currentTimeMillis) {
if (currentTimeMillis >= midnightTomorrow || currentTimeMillis < midnightToday) {
initMidnight(currentTimeMillis);
}
return currentTimeMillis >= midnightToday + start && currentTimeMillis <= midnightToday + end //
? onMatch // within window
: onMismatch;
}
public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg,
final Throwable t) {
final SimpleBindings bindings = new SimpleBindings();
bindings.put("logger", logger);
bindings.put("level", level);
bindings.put("marker", marker);
bindings.put("message", msg);
bindings.put("parameters", null);
bindings.put("throwable", t);
bindings.putAll(configuration.getProperties());
bindings.put("substitutor", configuration.getStrSubstitutor());
final Object object = configuration.getScriptManager().execute(script.getName(), bindings);
return object == null || !Boolean.TRUE.equals(object) ? onMismatch : onMatch;
}
组合过滤器
参考链接:https://www.jianshu.com/p/0c882ced0bf5