Log4j 2 (5) Layout与Filter

Appender使用布局将LogEvent格式化为可以让日志的输出更具有可读性。 在Log4j 1.x和Logback Layout中,都是把日志事件转成String。 在Log4j 2中,Layout返回一个字节数组。 这样可以让Layout的结果输出形式更加多样化。 但是,这也意味着我们需要为大多数Layout配置一个字符集,以确保字节数组能够正确输出。

如果你仔细阅读官方文档,你会发现,log4j比logback的输出更加丰富,可以支持CSV,JSON,XML等等,这些对于在web项目中可能也不常用,我也不做记录,如果将来真的需要,直接阅读官方文档以及示例。这里我们关注我们用的最频繁的Pattern Layout。

Pattern Layout

这个类型的layout最灵活也是最常用

参数列表:

Parameter NameTypeDescription
charsetString输出字符串为字节数组时使用的编码方式,如果不指定使用平台默认编码
patternString指定如何转换LogEvent为字符串的模式
patternSelectorPatternSelector根据LogEvent选择一种Pattern, pattern和patternSelector参数互斥
replaceRegexReplacement允许替换部分结果字符串。 如果已配置,则replace元素必须指定要匹配的正则表达式和替换。
alwaysWriteExceptionsboolean如果你不再pattern中指定异常输出,则使用默认异常格式,附加在Pattern尾部

Patterns规则

%项说明
c{precision}/logger{precision}输出logger的名称,可选的指定一个精度:当精度说明符为整数值时,它将减小记录器名称的大小。 如果数字为正,则布局将打印相应数量的最右边记录器名称组件。 如果为负,则布局将删除相应数量的最左边的记录器名称组件。
如果精度包含任何非整数字符,则布局会根据模式缩写名称。 如果精度整数小于1,则布局仍将完整打印最右边的标记。 默认情况下,布局将完整打印记录器名称。
C{precision}/class{precision}输出发起日志记录请求的那个类的全限定名称。生成调用方的类名称(位置信息)成本昂贵,并且可能会影响性能。
d{pattern}/date{pattern}输出记录事件的日期。 日期转换说明符后可以跟一组大括号,每个大括号包含每个SimpleDateFormat的日期和时间模式字符串。
highlight{pattern}{style}添加ANSI颜色高亮。【Linux才可以显示颜色】默认针对各种级别的高亮颜色为:FATALERRORWARNINFODEBUGTRACEpattern为需要高亮的其它Pattern字段style取值可以是Default或者Logback。后者亮度较高
K{key}/map{key}/MAP{key}用于输出MapMessage中的条目
l/location输出调用者的位置信息,包括方法名和行号,对性能有影响,谨慎使用
L/line输出调用发起处的行号,对性能有影响,谨慎使用
M/method输出调用发起处的方法名,对性能有影响,谨慎使用
marker输出Marker的完整名称,如果由父Marker也输出
n输出平台相关的换行符
N/nano输出System.nanoTime()
pid{[defaultValue]}/processId{[defaultValue]}输出PID
p/level{level=label, level=label, ...}输出日志的级别,示例:%level{WARN=Warning, DEBUG=Debug, ERROR=Error, TRACE=Trace, INFO=Info} %level{WARN=W, DEBUG=D, ERROR=E, TRACE=T, INFO=I}
r输出自JVM启动依赖流逝的毫秒数
sn/sequenceNumber输出此日志事件的序列号
T/tid/threadId输出当前线程ID
t/tn/thread/threadName输出当前线程名称
tp/threadPriority**输出当前线程优先级
X{key[,key2...]}<br/>mdc{key[,key2...]}<br/>MDC{key[,key2...]}输出与生成日志事件的线程关联的线程上下文映射(也称为映射诊断上下文或MDC)
x NDC输出与生成日志事件的线程关联的线程上下文堆栈(也称为嵌套诊断上下文或NDC)

当然还有一些不常用的不列举出来,具体看官方的表格。基本上与logback的encoder差不多的语法

logger pattern示例

Conversion PatternLogger NameResult
%c{1}org.apache.commons.FooFoo
%c{2}org.apache.commons.Foocommons.Foo
%c{10}org.apache.commons.Fooorg.apache.commons.Foo
%c{-1}org.apache.commons.Fooapache.commons.Foo
%c{-2}org.apache.commons.Foocommons.Foo
%c{-10}org.apache.commons.Fooorg.apache.commons.Foo
%c{1.}org.apache.commons.Fooo.a.c.Foo
%c{1.1.~.~}org.apache.commons.test.Fooo.a.~.~.Foo
%c{.}org.apache.commons.test.Foo....Foo

date pattern示例

PatternExample
%d{DEFAULT}2012-11-02 14:34:02,123
%d{DEFAULT_MICROS}2012-11-02 14:34:02,123456
%d{DEFAULT_NANOS}2012-11-02 14:34:02,123456789
%d{ISO8601}2012-11-02T14:34:02,781
%d{ISO8601_BASIC}20121102T143402,781
%d{ISO8601_OFFSET_DATE_TIME_HH}2012-11-02'T'14:34:02,781-07
%d{ISO8601_OFFSET_DATE_TIME_HHMM}2012-11-02'T'14:34:02,781-0700
%d{ISO8601_OFFSET_DATE_TIME_HHCMM}2012-11-02'T'14:34:02,781-07:00
%d{ABSOLUTE}14:34:02,781
%d{ABSOLUTE_MICROS}14:34:02,123456
%d{ABSOLUTE_NANOS}14:34:02,123456789
%d{DATE}02 Nov 2012 14:34:02,781
%d{COMPACT}20121102143402781
%d{UNIX}1351866842
%d{UNIX_MILLIS}1351866842781
PatternExample
%d{HH:mm:ss,SSS}14:34:02,123
%d{HH:mm:ss,nnnn} to %d{HH:mm:ss,nnnnnnnnn}14:34:02,1234 to 14:34:02,123456789
%d{dd MMM yyyy HH:mm:ss,SSS}02 Nov 2012 14:34:02,123
%d{dd MMM yyyy HH:mm:ss,nnnn} to %d{dd MMM yyyy HH:mm:ss,nnnnnnnnn}02 Nov 2012 14:34:02,1234 to 02 Nov 2012 14:34:02,123456789
%d{HH:mm:ss}{GMT+0}18:34:02

我们来尝试解读一个pattern吧!

%d{yyyy/MM/dd HH:mm:ss.SSS} %-5p [%t] [%X{requestId} - %C{0}] %m%n

打印的格式大致是日期+左对齐5格的日志等级+ 被[]括起来的线程名+MDC定义的key为requestId+没有包前缀的类名(指的是logger的名)+日志信息。

Filter

Log4j 2的过滤器其实和logback也是差不多的,我们直接引用logback中关于过滤器的描述:

logback 过滤器基于三元逻辑(ternary logic ),允许它们有序地组装或者链接在一起组成一个任意复杂的过滤策略。那么你可能就会问了:哎呀什么是三元逻辑呢?跟Java的三元运算符概念差不多吗?答案当然是跟三元运算符不一样了,它更偏向是二元逻辑,只有真假两种。我们来看一下维基百科上对三元逻辑的解释吧:

有三种状态来表示真、假和一个表示不确定的第三值;;这相对于基础的二元逻辑(比如布尔逻辑,它只提供真假两种状态)

我们现在知道了原来三元逻辑就是有三种状态啊,既然是基于三元逻辑的过滤器应该也有三个类似的状态值吧?是的,没错!过滤器的decide(ILoggingEvent event)方法会被调用,且返回值只能是FilterReply中定义的ACCEPTDENYNEUTRAL的三个枚举值的其中一个。

如果返回DENY,那么日志事件立即被抛弃,不再经过剩余过滤器;

如果返回NEUTRAL,那么有序列表里的下一个过滤器会接着处理记录事件;

如果返回ACCEPT,那么日志事件被立即处理,不再经过剩余过滤器。

在log4j中也是使用这三个状态代表过滤状态。可以在以下四个位置之一中配置过滤器::

  • 在配置文件里直接配置Context-wide Filters。这个过滤器是全局范围的过滤器,即如果被这个过滤器拒绝的日志将不会到下一个过滤器进行处理,相反如果被这个过滤器接受的将直接执行,不会进入到其他过滤器甚至不会受到日志等级的影响。他被配置在AppenderLogger中;
  • Logger 配置Filters。这个类型的过滤器执行顺序是排在Context-wide Filters和日志等级的过滤之后。这些过滤器拒绝的事件都将被丢弃,并且该事件不会传递给父Logger,即使additivity 设置为true
  • Appender中配置Filters 。用于确定特定的Appender是否应处理日志事件的格式化和输出;
  • Appender Reference 中配置的Filters。用于确定Logger是否应将事件路由到Appender

举个例子,表示下以上四个位置的过滤器:

<Configuration status="warn" name="MyApp" packages="">
     <Filters>
     <!-- 全局级别Filter-> Context-wide Filters -->
        <LevelRangeFilter minLevel="DEBUG" maxLevel="ERROR" onMatch="DENY"></LevelRangeFilter>
      </Filters>

  <Loggers>
     <!-- Logger级别的Filter -->
    <Root level="error">
      <MapFilter onMatch="ACCEPT" onMismatch="NEUTRAL" operator="or">
        <KeyValuePair key="eventId" value="Login"/>
        <KeyValuePair key="eventId" value="Logout"/>
      </MapFilter>
    </Root>

 <Appenders>
    <RollingFile name="RollingFile" fileName="logs/app.log"
                 filePattern="logs/app-%d{MM-dd-yyyy}.log.gz">
      <!-- Appender级别的Filter -->
      <BurstFilter level="INFO" rate="16" maxBurst="100"/>
      <PatternLayout>
        <pattern>%d %p %c{1.} [%t] %m%n</pattern>
      </PatternLayout>
      <TimeBasedTriggeringPolicy />
    </RollingFile>
  </Appenders>    

    <Logger name="TestJavaScriptFilter" level="trace" additivity="false">
       <!-- AppenderRef级别的Filter -->
      <AppenderRef ref="List">
        <ScriptFilter onMatch="ACCEPT" onMisMatch="DENY">
          <ScriptRef ref="filter.js" />
        </ScriptFilter>
      </AppenderRef>
    </Logger>
  </Loggers>

</Configuration>

BurstFilter

Parameter NameTypeDescription
levelString要过滤的消息级别。 如果超过了maxBurst,则等于或低于此级别的所有内容都会被滤除。 默认值为WARN,这意味着将记录任何高于警告的消息,无论突发大小如何。
ratefloat每秒允许的平均事件数。
maxBurstinteger在过滤事件超过平均速率之前可以发生的最大事件数。 默认值为速率的10倍。
onMatchString过滤器匹配时要采取的操作。可以是ACCEPT、DENY、NEUTRAL,默认是NEUTRAL。
onMismatchString当过滤器不匹配时要采取的操作。可以是ACCEPT、DENY、NEUTRAL,默认是DENY。

例如

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
    <Appenders>
        <RollingFile name="RollingFile" fileName="logs/app.log"
                     filePattern="logs/app-%d{MM-dd-yyyy}.log.gz">
            <BurstFilter level="WARN" rate="10" maxBurst="2"/>
            <PatternLayout>
                <pattern>%d %p %c{1.} [%t] %m%n</pattern>
            </PatternLayout>
            <TimeBasedTriggeringPolicy />
        </RollingFile>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="RollingFile"/>
        </Root>
    </Loggers>
</Configuration>

输出:

2020-04-25 22:08:14,071 INFO l.TestFilter [main] test info 0
2020-04-25 22:08:14,074 WARN l.TestFilter [main] test warn 0
2020-04-25 22:08:14,074 ERROR l.TestFilter [main] test error 0
2020-04-25 22:08:14,075 ERROR l.TestFilter [main] test error 2 0
2020-04-25 22:08:14,075 ERROR l.TestFilter [main] test error 1
2020-04-25 22:08:14,075 ERROR l.TestFilter [main] test error 2 1
2020-04-25 22:08:14,075 ERROR l.TestFilter [main] test error 2

假如你把maxBurst设置小于等于0,那么会被赋予默认值:rate*100。

CompositeFilter

复合过滤器提供了一种指定多个过滤器的方法。他以Filters元素加入到配置中,元素里面可以配置多个过滤器。该元素不支持添加参数。

例如:

 <Filters>
    <MarkerFilter marker="EVENT" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
    <DynamicThresholdFilter key="loginId" defaultThreshold="ERROR"
                            onMatch="ACCEPT" onMismatch="NEUTRAL">
      <KeyValuePair key="User1" value="DEBUG"/>
    </DynamicThresholdFilter>
  </Filters>

DynamicThresholdFilter

DynamicThresholdFilter允许基于特定属性按日志级别进行过滤。 例如,如果在ThreadContext Map中捕获了用户的loginId,则可以仅对该用户启用调试日志记录。 如果日志事件不包含指定的ThreadContext项,则将返回NEUTRAL

Parameter NameTypeDescription
keyString去ThreadContext Map中比较的key
defaultThresholdString需要被过滤的消息等级。当指定的key不在ThreadContext中时,使用该配置。
keyValuePairKeyValuePair[]可以定义多个KeyValuePair属性。通过该属性可以对指定用户设置日志级别。KeyValuePair的key为ThreadContext中获取的value值,KeyValuePair的value为日志的级别。
onMatchString过滤器匹配时要采取的操作。可以是ACCEPT、DENY、NEUTRAL,默认是NEUTRAL。
onMismatchString当过滤器不匹配时要采取的操作。可以是ACCEPT、DENY、NEUTRAL,默认是DENY。

例如:

 public static void main(String[] args) throws InterruptedException {
        ThreadContext.put("debugMode", "false");
        log.info("Info should not show anywhere");
        log.debug("This shouldn't show anywhere");

        ThreadContext.put("debugMode", "true");
        log.debug("This should show in the log and console");
        log.info("This should also show in both");

        ThreadContext.put("debugMode", "false");
        log.info("This should not show anywhere");
        log.error("This error should show only in console.");


    }
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
    <Appenders>
        <Console name="console" target="SYSTEM_OUT">
            <filters>
                <DynamicThresholdFilter key="debugMode" defaultThreshold="ERROR" onMatch="ACCEPT" onMismatch="DENY">
                    <KeyValuePair key="true" value="DEBUG"/>
                    <KeyValuePair key="false" value="ERROR"/>
                </DynamicThresholdFilter>
            </filters>
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg\n%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="TRACE" additivity="false">
            <AppenderRef ref="console"/>
        </Root>
    </Loggers>
</Configuration>

输出:

22:54:41.416 [main] DEBUG [vsTenant] log4jstudy.TestFilter - This should show in the log and console

22:54:41.418 [main] INFO  [vsTenant] log4jstudy.TestFilter - This should also show in both

22:54:41.418 [main] ERROR [vsTenant] log4jstudy.TestFilter - This error should show only in console.

ThreadContextMapFilter (or ContextMapFilter)

ThreadContextMapFilterContextMapFilter允许对当前上下文中的数据元素进行过滤。默认情况下,这是ThreadContext映射。

Parameter NameTypeDescription
keyValuePairKeyValuePair[]一个或多个KeyValuePair元素,它们定义映射中的键和要匹配的值。 如果多次指定同一键,则该键的检查将自动为“或”,因为Map只能包含一个值。KeyValuePair的key为ThreadContext中包含的key,KeyValuePair的value为ThreadContext Map中key的值。
operatorString如果运算符为“or”,则任何键/值对的匹配都将被视为匹配,否则所有键/值对必须匹配。
onMatchString过滤器匹配时要采取的操作。可以是ACCEPT、DENY、NEUTRAL,默认是NEUTRAL。
onMismatchString当过滤器不匹配时要采取的操作。可以是ACCEPT、DENY、NEUTRAL,默认是DENY。

例如:

        ThreadContext.put("User1", "error name");
        log.info("Info should not show anywhere");
        log.debug("This shouldn't show anywhere");

        ThreadContext.put("User2", "jane");
        log.debug("This should show in the log and console");
        log.info("This should also show in both");

        ThreadContext.put("User1", "nicole");
        log.info("This should show anywhere");
        log.error("This error should show only in console.");
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">

    <ContextMapFilter onMatch="ACCEPT" onMismatch="NEUTRAL" operator="or">
        <KeyValuePair key="User1" value="nicole"/>
        <KeyValuePair key="User2" value="jane"/>
    </ContextMapFilter>

    <Appenders>
        <Console name="console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg\n%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="WARN" additivity="false">
            <AppenderRef ref="console"/>
        </Root>
    </Loggers>
</Configuration>

输出:

23:22:58.977 [main] DEBUG log4jstudy.TestFilter - This should show in the log and console

23:22:58.980 [main] INFO  log4jstudy.TestFilter - This should also show in both

23:22:58.980 [main] INFO  log4jstudy.TestFilter - This should show anywhere

23:22:58.980 [main] ERROR log4jstudy.TestFilter - This error should show only in console.

当然这个过滤器也可以被放置在logger中:

<Loggers>
    <Root level="error">
      <AppenderRef ref="RollingFile"/>
      <ContextMapFilter onMatch="ACCEPT" onMismatch="NEUTRAL" operator="or">
        <KeyValuePair key="foo" value="bar"/>
        <KeyValuePair key="User2" value="Liming"/>
      </ContextMapFilter>
    </Root>
  </Loggers>

ThresholdFilter

如果LogEvent中的级别与配置的级别相同或更高,则此过滤器返回onMatch结果,否则返回onMismatch值。例如,你的日志事件为info,但是该过滤器配置了error,则这个事件将会被过滤掉即返回onMismatch

Parameter NameTypeDescription
levelString要匹配的有效级别名称
onMatchString过滤器匹配时要采取的操作。可以是ACCEPT、DENY、NEUTRAL,默认是NEUTRAL。
onMismatchString当过滤器不匹配时要采取的操作。可以是ACCEPT、DENY、NEUTRAL,默认是DENY。

例如:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
    <Appenders>
        <Console name="console" target="SYSTEM_OUT">
            <ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg\n%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="INFO" additivity="false">
            <AppenderRef ref="console"/>
        </Root>
    </Loggers>
</Configuration>
log.error("error");

log.debug("hello debug");

输出

23:27:08.024 [main] ERROR log4jstudy.TestFilter - error

小结

过滤器不仅仅有上述这些还有诸如:TimeFilter,MapFilter ,MarkerFilter ,NoMarkerFilter,RegexFilter ,ScriptFilter ,StructuredDataFilter 这些。这里只是列举了可能常用的一些过滤器,如果有需要再翻阅官方文档进行配置。