log4j 2.x 架构(源码)

目录

1.概述

1.1.组件概览

1.2.灵活的配置

1.2.1.插件发现机制

1.2.2.插件装配机制

1.2.3.配置文件基本元素与对象的映射关系

事件级别

2.属性占位符

2.1.概述

2.2.Interpolator插值器

扫描二维码关注公众号,回复: 4620159 查看本文章

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



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>

事件级别

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中,环境变量信息(键值对)被封装为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.1.1写日志逻辑

//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<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);
    }


//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<StringBuilder> 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);
        }
    }

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继承关系递归调用父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根节点为止.

3.4构建LoggerConfig树

//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<String, LoggerConfig> 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);
                }
            }
        }
    }


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字符串等等.

类体系如下:

layout类体系

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继承体系如下:

manager继承体系

7.Filter

过滤器的核心职责就是对LogEvent日志事件进行匹配,匹配结果分为匹配和不匹配,结果值有3种:接受,拒绝,中立.可由用户自定义匹配和不匹配的行为结果.

所有实现了Filterable接口的组件都可以引用一个过滤器进行事件过滤,包含LoggerConfigAppenderControl等.

框架实现的过滤器如下:

序号 工厂方法(类名.方法名) 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> 判断日志时间是否在指定的时间区间内
   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);
        }
}

 AbstractFilterBuilder

用于根据配置文件中设置创建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 事件的执行时间较长,输出 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<LogDelay> 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<Builder>
{
        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());
        }
}

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
 

猜你喜欢

转载自blog.csdn.net/demon7552003/article/details/84721737