log4j 2.x 架构(源码)
目錄
1.概述
1.1.組件概覽
1.2.靈活的配置
1.2.1.插件發(fā)現(xiàn)機制
1.2.2.插件裝配機制
1.2.3.配置文件基本元素與對象的映射關(guān)系
事件級別
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構(gòu)建LoggerConfig樹
4.Appender
4.1.概述
4.2.框架支持的Appender實現(xiàn)
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
1.概述
1.1.組件概覽
在log4j2中,LogManager就是日志的門面,相當于slf4j-api中的LoggerFactory.
框架為每個類加載分配了一個單獨的LoggerContext,用于管理所有創(chuàng)建出來的Logger實例.
ContextSelector則負責管理類加載器到對應的LoggerContext實例之間的映射關(guān)系.
log4j2中,有5個關(guān)鍵概念:
- LoggerConfig:日志配置,用于整合多個Appender,進行日志打印.
- Appender:追加器,用于操作Layout和Manager,往單一目的地進行日志打印.
- Layout:布局,用于把LogEvent日志事件序列化成字節(jié)序列,不同Layout實現(xiàn)具有不同的序列化方式.
- Manager:管理器,用于管理輸出目的地,如:RollingFileManager用于管理文件滾動以及將字節(jié)序列寫入到指定文件中.
- Filter:過濾器,用于對LogEvent日志事件加以過濾,LoggerConfig和Appender都可以配置過濾器,也就是說日志事件會經(jīng)過一總一分兩層過濾.
組件架構(gòu)如下:
?
組件架構(gòu)
1.2.靈活的配置
1.2.1.插件發(fā)現(xiàn)機制
在log4j2中,一切皆插件,框架通過PluginRegistry掃描并發(fā)現(xiàn)插件配置.
PluginRegistry支持兩種掃描方式
- 一種是使用指定的ClassLoader讀取classpath下所有的META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat文件,產(chǎn)生PluginType;
- 另一種是掃描classpath下指定的packageName,內(nèi)省帶有@Plugin注解的類文件,產(chǎn)生PluginType.
插件配置以PluginType的形式保存在插件注冊表中,PluginType的作用類似于spring中BeanDefinition,定義了如何創(chuàng)建插件實例.
插件類通過@PluginFactory注解或者@PluginBuilderFactory注解配置插件實例的實例化和屬性注入方式.
1.2.2.插件裝配機制
log4j2知道如何實例化插件后,我們就可以通過編寫配置文件(如:log4j2.xml),進行插件的實例化和屬性注入了.
Configuration全局配置對象負責保存所有解析到的配置.
通過ConfigurationFactory.getConfiguration()可以使用不同的工廠生產(chǎn)不同的配置對象,不同的Configuration實現(xiàn)可以解析不同格式的配置,如:xml,yaml,json等.
以xml文件為例,文件中每個元素都會最終對應一個插件實例,元素名稱實際就是PluginType中的name,實例的屬性可以從子元素對應的實例獲取,也可以從自身元素的屬性配置獲取.
因此,xml中dom樹的元素嵌套關(guān)系,也就是log4j組件實例的引用嵌套關(guān)系.
xml,yaml,json格式文件都可以描述這種嵌套關(guān)系,因此log4j2中定義了與文件格式無關(guān)的數(shù)據(jù)結(jié)構(gòu),Node來抽象配置.
AbstractConfiguration.setup()負責提取配置,形成Node樹.
AbstractConfiguration.doConfigure()負責根據(jù)Node樹,進行插件實例化和屬性注入.
1.2.3.配置文件基本元素與對象的映射關(guān)系
| 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> |
?
事件級別
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;}}2.屬性占位符
2.1.概述
在log4j2中,環(huán)境變量信息(鍵值對)被封裝為StrLookup對象,該對象作用類似于spring框架中的PropertySource.
在配置文件中,基本上所有的值的配置都可以通過參數(shù)占位符引用環(huán)境變量信息,格式為:${prefix:key}.
2.2.Interpolator插值器
Interpolator內(nèi)部以Map<String,StrLookup>的方式,封裝了很多StrLookuo對象,key則對應參數(shù)占位符${prefix:key}中的prefix.
同時,Interpolator內(nèi)部還保存著一個沒有prefix的StrLookup實例,被稱作默認查找器,它的鍵值對數(shù)據(jù)來自于log4j2.xml配置文件中的<Properties>元素的配置.
當參數(shù)占位符${prefix:key}帶有prefix前綴時,Interpolator會從指定prefix對應的StrLookup實例中進行key查詢,
當參數(shù)占位符${key}沒有prefix時,Interpolator則會從默認查找器中進行查詢.
Interpolator中默認支持的StrLookup查找方式如下(StrLookup查找器實現(xiàn)類均在org.apache.logging.log4j.core.lookup包下):
| 1(*) | sys | SystemPropertiesLookup | 從jvm屬性中查找 |
| 2(*) | env | EnvironmentLookup | 從操作系統(tǒng)環(huán)境變量中獲取value |
| 3 | marker | MarkerLookup | 判斷以指定key為名稱的Marker標簽是否存在,存在則返回key,否則返回null |
| 4 | jvmrunargs | JmxRuntimeInputArgumentsLookup | 獲取jmx的運行時輸入?yún)?shù) |
| 5 | bundle | ResourceBundleLookup | 通過ResourceBundle查找value,格式:${prefix:bundleName:bundleKey} |
| 6 | java | JavaLookup | 獲取jvm進程信息,只指定固定的key值,包括:(1)version:jdk版本(2)runtime:運行環(huán)境信息(3)vm:虛擬機名稱(4)os:操作系統(tǒng)信息(5)hw:硬件信息(6)locale:地區(qū)信息 |
| 7 | main | MainMapLookup | 暫未啟用 |
| 8 | log4j | Log4jLookup | 只支持兩個key:(1)configLocation:獲取log4j2配置文件的絕對路徑(2)configParentLocation:獲取配置文件所在目錄的絕對路徑 |
| 9 | date | DateLookup | 以指定格式,獲取當前系統(tǒng)時間或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.1.1寫日志邏輯
//AbstractLogger public void debug(final Marker marker, final MessageSupplier msgSupplier, final Throwable t) {logIfEnabled(FQCN, Level.DEBUG, marker, msgSupplier, t);}//AbstractLoggerpublic 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);}} //具體 Loggerpublic boolean isEnabled(final Level level, final Marker marker, final String message, final Object... params) {return privateConfig.filter(level, marker, message, params);} //privateConfigboolean 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();}//AbstractLoggerprotected 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);}//Loggerpublic 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);}//具體 DefaultReliabilityStrategypublic void log(final Supplier<LoggerConfig> 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);}//LoggerConfigprivate void processLogEvent(final LogEvent event, LoggerConfigPredicate predicate) {event.setIncludeLocation(isIncludeLocation());if (predicate.allow(this)) {callAppenders(event);}logParent(event, predicate);} //LoggerConfigprotected void callAppenders(final LogEvent event) {final AppenderControl[] controls = appenders.get();//noinspection ForLoopReplaceableByForEachfor (int i = 0; i < controls.length; i++) {controls[i].callAppender(event);}}//AppenderControlpublic void callAppender(final LogEvent event) {if (shouldSkip(event)) {return;}callAppenderPreventRecursion(event);}//AppenderControlprivate void callAppenderPreventRecursion(final LogEvent event) {try {recursive.set(this);callAppender0(event);} finally {recursive.set(null);}}//AppenderControlprivate void callAppender0(final LogEvent event) {ensureAppenderStarted();if (!isFilteredByAppender(event)) {tryCallAppender(event);}}//AppenderControlprivate void tryCallAppender(final LogEvent event) {try {appender.append(event);} catch (final RuntimeException ex) {handleAppenderError(ex);} catch (final Exception ex) {handleAppenderError(new AppenderLoggingException(ex));}}//Appendervoid append(LogEvent event);//AbstractOutputStreamAppender@Overridepublic 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();}}//PatternLayoutpublic 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<StringBuilder> encoder = getStringBuilderEncoder();encoder.encode(text, destination);trimToMaxSize(text);}//PatternLayoutprivate 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格式化數(shù)據(jù)final byte[] bytes = getLayout().toByteArray(event);if (bytes != null && bytes.length > 0) {manager.write(bytes, this.immediateFlush || event.isEndOfBatch());}}//OutputStreamManagerprotected 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 directlyflush();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);}}3.1.2 Additive
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);}}3.2.配置詳解
- additivity:日志可加性,如果配置為true,則在日志打印時,會通過Logger繼承關(guān)系遞歸調(diào)用父Logger引用的Appender進行日志打印.
注意:該屬性默認為true.在遞歸打印日志時,會忽略父Logger的level配置 - level:用于控制允許打印的日志級別上線,在配置示例中,只有級別<=info的LogEvent才會被放行,級別優(yōu)先級順序為OFF<FATAL<ERROR<WARN<INFO<DEBUG<TRACE<ALL
注意:level屬性的配置時可選的,在獲取level時會通過Logger繼承關(guān)系遞歸獲取,RootLogger的級別默認為error,其他默認為null.也就是說,如果全都不配置level的話,則所有Logger級別都默認為error. - includeLocation:如果配置為true,則打印日志時可以附帶日志點源碼位置信息輸出.同步日志上下文默認為true,異步默認為false.
- LoggerConfig元素下可以單獨配置Property元素,添加屬性鍵值對,這些屬性會在每次打印日志時,被追加到LogEvent的contextData中
- LoggerConfig支持配置過濾器,在判斷是否打印日志時,先過濾器判斷過濾,然后再級別判斷過濾.
- AppenderRef:顧名思義,就是配置當前Logger引用的Appender.同時,AppenderRef也支持配置level和Filter,進行更細粒度的日志過濾
- LoggerConfig等于總開關(guān),AppenderRef則為各個子開關(guān),兩個開關(guān)都通過才能打印日志
3.3.Logger繼承機制
log4j2框架會根據(jù)LoggerConfig的name建立對象之間的繼承關(guān)系.這種繼承機制與java的package很像,name以點進行名稱空間分割,子名稱空間繼承父名稱空間.
名稱空間可以是全限定類名,也可以是報名.整個配置樹的根節(jié)點就是RootLogger.
舉例:假如我們的配置的Logger如下:
配置樹
當通過LogManager.getLogger(name)獲取Logger實例時,會根據(jù)name逐級遞歸直到找到匹配的LoggerConfig,或者遞歸到Root根節(jié)點為止.
3.4構(gòu)建LoggerConfig樹
//AbstractConfiguration,在初始化配置時設(shè)置層次。//通過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 {//不是自己的配置,則新創(chuàng)建一個配置,設(shè)置parentfinal 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<String, LoggerConfig> entry : loggerConfigs.entrySet()) {final LoggerConfig logger = entry.getValue();String key = entry.getKey();if (!key.isEmpty()) {//根據(jù)點號(.)按層次分別設(shè)置parentfinal 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);}}}}4.Appender
4.1.概述
追加器,負責控制Layout進行LogEvent的序列化,以及控制Manager對序列化后的字節(jié)序列進行輸出.
在log4j2.xml配置文件中,配置方式如下:
<Appenders><具體的Appender插件名稱></具體的Appender插件名稱> </Appenders>4.2.框架支持的Appender實現(xiàn)
| 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 | 內(nèi)部引用一組appender,通過異步線程+隊列方式調(diào)用這些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 | 路由追加器 可根據(jù)pattern模式字符串,路由到內(nèi)部管理的其他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
控制臺追加器,用于把日志輸出到控制臺,一般本地調(diào)試時使用.
配置示例如下:
4.3.2.RollingFileAppender
文件滾動追加器,用于向本地磁盤文件中追加日志,同時可以通過觸發(fā)策略(TriggeringPolicy)和滾動策略(RolloverStrategy)控制日志文件的分片,避免日志文件過大.
線上環(huán)境常用.
常用的觸發(fā)策略包含兩種:
- TimeBasedTriggeringPolicy:基于時間周期性觸發(fā)滾動,一般按天滾動
- SizeBasedTriggeringPolicy:基于文件大小觸發(fā)滾動,可以控制單個日志文件的大小上限
滾動策略的實現(xiàn)包含兩種:
-
DefaultRolloverStrategy:默認滾動策略
?
該策略內(nèi)部維護一個最小索引和最大索引,每次滾動時,會刪除歷史文件,之后剩余文件全部進行一輪重命名,最后創(chuàng)建新的不帶有索引后綴的文件進行日志追加默認策略
-
DirectWriteRolloverStrategy:直接寫滾動策略
?
該策略內(nèi)部會維護一個一直自增的文件索引,每次滾動時直接創(chuàng)建新的帶有索引后綴的文件進行日志追加,同步清理歷史的文件.直接寫策略
配置示例如下:
<?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}"/><!-- 所有策略中,只要任意策略滿足就會觸發(fā)滾動 --><Policies><!-- 滾動時間周期,只有數(shù)量,單位取決于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轉(zhuǎn)換成可序列化對象(如:String),或者直接序列化成字節(jié)數(shù)組.
log4j2支持很多的序列化格式,如:普通模式字符串,JSON字符串,yaml字符串,XML格式字符串,HTML字符串等等.
類體系如下:
?
layout類體系
5.2.PatternLayout
模式布局是我們最常使用的,它通過PatternProcessor模式解析器,對模式字符串進行解析,得到一個List<PatternConverter>轉(zhuǎn)換器列表和List<FormattingInfo>格式信息列表.
在PatternLayout序列化時,會遍歷每個PatternConverter,從LogEvent中取不同的值進行序列化輸出.
5.2.1.模式字符串
模式字符串由3部分組成,格式為:%(格式信息)(轉(zhuǎn)換器名稱){選項1}{選項2}...
- 格式信息
數(shù)據(jù)結(jié)構(gòu)如下:
模式字符串的格式為:
%-(minLength).-(maxLength)(轉(zhuǎn)換器名稱){選項字符串}
minLength代表字段的最小長度限制,當字段內(nèi)容長度小于最小限制時,會進行空格填充.
minLength前面的-負責控制對齊方式,默認為右對齊(左邊空格填充),如果加上-,則會切換為左對齊方式(右邊空格填充)
maxLength代表字段的最大長度限制,當字段內(nèi)容長度大于最大限制時,會進行內(nèi)容階段
maxLength前面的-負責控制階段方向,默認為左側(cè)階段,如果加上-,則會切換為右側(cè)階段
minLength和maxLength之間用點分隔.
格式信息中所有屬性都是可選的,不配置,則使用默認值
- 轉(zhuǎn)換器名稱
log4j2會通過PluginManager收集所有類別為Converter的插件,同時分析插件類上的@ConverterKeys注解,獲取轉(zhuǎn)換器名稱,并建立名稱到插件實例的映射關(guān)系.
PatternParser識別到轉(zhuǎn)換器名稱的時候,會查找映射.
框架支持的所有轉(zhuǎn)換器如下:
| 1 | d date | DatePatternConverter | 日志的時間戳 |
| 2 | p level | LevelPatternConverter | 日志級別 |
| 3 | m msg message | MessagePatternConverter | 日志中的消息內(nèi)容 |
| 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 | 日志打印點的代碼行數(shù) 注意:需要給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 | 線程優(yōu)先級 |
| 32 | ex throwable Exception | ThrowablePatternConverter | 異常 |
| 33 | u uuid | UuidPatternConverter | 生成一個uuid,隨日志一起打印,用于唯一標識一條日志 |
| 34 | notEmpty varsNotEmpty variablesNotEmpty | VariablesNotEmptyReplacementConverter | ? |
- 選項字符串
有時我們需要對特定的轉(zhuǎn)換器進行特殊的配置,如:給DatePatternConverter配置時間格式,這個時候需要通過選項字符串配置.
PatternParser會提取模式字符串中的所有選項,保存在一個List<String>中,每個{}包裹的內(nèi)容作為一個選項.
當創(chuàng)建轉(zhuǎn)換器時,框架會自動掃描轉(zhuǎn)換器類中聲明的靜態(tài)工廠方法newInstance,同時支持兩種可選的形參,一種是Configuration,另一種String[]則會注入選項列表.
選項列表的識別由不同的轉(zhuǎn)換器各自定義.
最后,以一個實際的例子解釋配置:
日志會輸出時間,類名,方法名,消息以及一個換行符.
同時,我們給DatePatternConverter指定了了時間格式,并且限制全限定類名最小長度為5,右截斷,最大為10,左對齊.
6.Manager
管理器的職責主要是控制目標輸出流,以及把保存在ByteBuffer字節(jié)緩沖區(qū)中的日志序列化結(jié)果,輸出到目標流中.
如:RollingFileManager需要在每次追加日志之前,進行滾動檢查,如果觸發(fā)滾動還會創(chuàng)建新的文件輸出流.
manager繼承體系如下:
?
manager繼承體系
7.Filter
過濾器的核心職責就是對LogEvent日志事件進行匹配,匹配結(jié)果分為匹配和不匹配,結(jié)果值有3種:接受,拒絕,中立.可由用戶自定義匹配和不匹配的行為結(jié)果.
所有實現(xiàn)了Filterable接口的組件都可以引用一個過濾器進行事件過濾,包含LoggerConfig和AppenderControl等.
框架實現(xiàn)的過濾器如下:
| 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> | 根據(jù)LogEvent的級別進行過濾,如果LogEvent.level<=ThresholdFilter.level,則返回匹配的結(jié)果,否則返回不匹配的結(jié)果.如:過濾器為info,日志為error,則error<=info返回匹配結(jié)果 |
| 11 | TimeFilter.createFilter | <TimeFilter> | 判斷日志時間是否在指定的時間區(qū)間內(nèi) |
?AbstractFilterBuilder
用于根據(jù)配置文件中設(shè)置創(chuàng)建Filter
public static abstract class AbstractFilterBuilder<B extends AbstractFilterBuilder<B>> {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;}}?
BurstFilter
頻率控制過濾器
<BurstFilter level="INFO" rate="16" maxBurst="100"/>level :BurstFilter過濾的事件級別
rate :每秒允許的 log 事件的平均值
maxBurst:當BurstFilter過濾的事件超過 rate 值,排隊的 log 事件上限。超過此上限的 log ,將被丟棄。默認情況下 maxBurst = 100*rate
按以上配置,假定每個 log 事件的執(zhí)行時間較長,輸出 117 個 log 事件( INFO級別)到RollingFileAppenders,BurstFilter會過濾得到INFO級別的 log 事件,之后會發(fā)生: 16 個 log 事件在執(zhí)行, 100 個等待執(zhí)行, 1 個被丟棄。
DynamicThresholdFilter
可以過濾具有特定的屬性某一級別的日志
<DynamicThresholdFilter key="loginId" defaultThreshold="ERROR" onMatch="ACCEPT" onMismatch="NEUTRAL"><KeyValuePair key="User1" value="DEBUG"/> </DynamicThresholdFilter>如果用戶的登錄ID被捕獲在ThreadContext的Map中則可以啟用debug級的日志
private Result filter(final Level level, final ReadOnlyStringMap contextMap) { //<DynamicThresholdFilter key="loginId"final String value = contextMap.getValue(key);if (value != null) {Level ctxLevel = levelMap.get(value);if (ctxLevel == null) {ctxLevel = defaultThreshold;}return level.isMoreSpecificThan(ctxLevel) ? onMatch : onMismatch;}return Result.NEUTRAL;}MapFilter
MapFilter可以對Map中的信息進行過濾,進而記錄特定事件,比如登入、退出
<MapFilter onMatch="ACCEPT" onMismatch="NEUTRAL" operator="or"><KeyValuePair key="eventId" value="Login"/><KeyValuePair key="eventId" value="Logout"/> </MapFilter> protected boolean filter(final Map<String, String> 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<String>) map.getValueAt(i)).contains(toMatch);if ((!isAnd && match) || (isAnd && !match)) {break;}}return match;}LevelRangeFilter
private Result filter(final Level level) {return level.isInRange(this.minLevel, this.maxLevel) ? onMatch : onMismatch;}MarkerFilter
對LogEvent中的Marker 進行過濾
<MarkerFilter marker="FLOW" onMatch="ACCEPT" onMismatch="DENY"/> 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);RegexFilter
對格式化消息和非格式化消息進行正則匹配過濾
<RegexFilter regex=".* test .*" onMatch="ACCEPT" onMismatch="DENY"/> private Result filter(final String msg) {if (msg == null) {return onMismatch;}final Matcher m = pattern.matcher(msg);return m.matches() ? onMatch : onMismatch;}?ThresholdFilter
對level進行過濾
<ThresholdFilter level="TRACE" onMatch="ACCEPT" onMismatch="DENY"/> private Result filter(final Level testLevel) {return testLevel.isMoreSpecificThan(this.level) ? onMatch : onMismatch;}TimeFilter
基于時間段的日志過濾
<TimeFilter start="05:00:00" end="05:30:00" onMatch="ACCEPT" onMismatch="DENY"/> Result filter(final long currentTimeMillis) {if (currentTimeMillis >= midnightTomorrow || currentTimeMillis < midnightToday) {initMidnight(currentTimeMillis);}return currentTimeMillis >= midnightToday + start && currentTimeMillis <= midnightToday + end //? onMatch // within window: onMismatch;}ScriptFilter
<Scripts><ScriptFile name="filter.js" language="JavaScript" path="src/test/resources/scripts/filter.js" charset="UTF-8" /><ScriptFile name="filter.groovy" language="groovy" path="src/test/resources/scripts/filter.groovy" charset="UTF-8" /></Scripts><ScriptFilter onMatch="ACCEPT" onMisMatch="DENY"><ScriptRef ref="filter.groovy" /></ScriptFilter>?
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;}CompositeFilter
組合過濾器
<Filters><MarkerFilter marker="EVENT" onMatch="ACCEPT" onMismatch="NEUTRAL"/><DynamicThresholdFilter key="loginId" defaultThreshold="ERROR"onMatch="ACCEPT" onMismatch="NEUTRAL"><KeyValuePair key="User1" value="DEBUG"/></DynamicThresholdFilter></Filters>?
?
StructuredDataFilter
ThreadContextMapFilter
參考鏈接:https://www.jianshu.com/p/0c882ced0bf5
?
總結(jié)
以上是生活随笔為你收集整理的log4j 2.x 架构(源码)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: log4j 2.x 架构
- 下一篇: 反射-Class