logback中的Filter
# 简介
logback 过滤器基于三元逻辑,允许它们组装或者链接在一起组成一个任意复杂的过滤策略。它们在很大程度上受到 Linux iptables 的启发。
在 logback-classic 中,有两种类型的过滤器,regular 过滤器以及 turbo 过滤器。
# Regular
reqular 过滤器继承自 Filter 这个抽象类。本质上它由一个单一的 decide() 方法组成,接收一个 ILoggingEvent 实例作为参数。
过滤器通过一个有序列表进行管理,并且基于三元逻辑。每个过滤器的 decide(ILoggingEvent event) 被依次调用。这个方法返回 FilterReply 枚举值中的一个, DENY, NEUTRAL 或者 ACCEPT。如果 decide() 方法返回 DENY,那么日志事件会被丢弃掉,并且不会考虑后续的过滤器。如果返回的值是 NEUTRAL,那么才会考虑后续的过滤器。如果没有其它的过滤器了,那么日志事件会被正常处理。如果返回值是 ACCEPT,那么会跳过剩下的过滤器而直接被处理。
在 logback-classic 中,过滤器可以被直接添加到 Appender 实例上。通过将一个或者多个过滤器添加到 appender 上,你可以通过任意标准来过滤日志事件。例如,日志消息的内容,MDC 的内容,时间,或者日志事件的其它部分。
# 自定义过滤器
创建一个自己的过滤器非常的简单。只需要继承 Filter 并且实现 decide() 方法就可以了。
自定一个过滤器,把日志内容包含“different”的不打印
/**
* @author blog.unclezs.com
* @date 2020/12/3 1:05 上午
*/
public class CustomRegularFilter extends Filter<ILoggingEvent> {
@Override
public FilterReply decide(ILoggingEvent event) {
return event.getMessage().contains("different")?FilterReply.DENY:FilterReply.ACCEPT;
}
}
/**
* @author blog.unclezs.com
* @since 2020/12/04 17:26
*/
public class FilterSample {
public static void main(String[] args) {
LoggerHelper.reconfigure("logback-filter.xml");
Logger logger = LoggerFactory.getLogger(FilterSample.class);
logger.info("different log");
logger.info("same log");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<configuration debug="false" scan="false" scanPeriod="1 second" packagingData="false">
<property name="pattern"
value="%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger) - %cyan(%msg%n)"/>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="com.unclezs.samples.log.slf4j.logback.filter.CustomRegularFilter"/>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
<!--在头部打印出pattern-->
<outputPatternAsHeader>true</outputPatternAsHeader>
</encoder>
<!--立即刷新到流-->
<immediateFlush>true</immediateFlush>
</appender>
<logger name="com.unclezs.samples.log.slf4j.logback" level="info" additivity="false">
<appender-ref ref="console"/>
</logger>
<root level="off">
</root>
</configuration>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# LevelFilter
LevelFilter 基于级别来过滤日志事件。如果事件的级别与配置的级别相等,过滤器会根据配置的 onMatch 与 onMismatch 属性,接受或者拒绝事件。如下是一个简单的示例:
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
2
3
4
5
# ThresholdFilter
ThresholdFilter 基于给定的临界值来过滤事件。如果事件的级别等于或高于给定的临界值,当调用 decide() 时,ThresholdFilter 将会返回 NEUTRAL。但是事件的级别低于临界值将会被拒绝。下面是一个简单的例子:
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
2
3
# EvaluatorFilter
EvaluatorFilter 是一个通用的过滤器,它封装了一个 EventEvaluator。顾名思义,EventEvaluator 根据给定的标准来评估给定的事件是否符合标准。在 match 和 mismatch 的情况下,EvaluatorFilter 将会返回 onMatch 或 onMismatch 指定的值。
注意 EventEvaluator 是一个抽象类。你可以通过继承 EventEvaluator 来实现自己事件评估逻辑。
GEventEvaluator是EventEvaluator一个实现类,通过Groogy脚本判断是否需要过滤。
# JaninoEventEvaluator
logback-classic 附带的另外一个 EventEvaluator 的具体实现名为 JaninoEventEvaluator,它接受任意返回布尔值的 Java 代码块作为评判标准。我们把这种 Java 布尔表达式称为 "评估表达式"。评估表达式在事件过滤中可以更加的灵活。JaninoEventEvaluator 需要 Janino 类库。请参见相关章节进行设置。跟 JaninoEventEvaluator 相比,GEventEvaluator 使用 Groovy 语言,使用起来非常方便。但是 JaninoEventEvaluator 将使用运行更快的等效表达式。
评估表达式在解析配置文件期间被动态编译。作为用户,不需要考虑实际的情况。但是,你需要确保你的 Java 表达式是有效的,保证它的评估结果为 true 或 false。
评估表达式对当前日志事件进行评估。logback-classic 自动导出日志事件的各种字段作为变量,为了可以从评估表达式访问。这些导出的变量是大小写敏感的,如下表所示:
名字 | 类型 | 描述 |
---|---|---|
event | LoggingEvent | 日志请求的原始日志事件。下面所有的变量都来自这个日志事件。例如,event.getMessage() 返回的字符串跟下面的 message 变量返回的字符串一样。 |
message | String | 日志请求的原始信息。例如,对于 logger I,当你写的是 I.info("Hello {}", name); 时,name 的值被指定为 "Alice",消息就为 "Hello {}"。 |
formattedMessage | String | 日志请求中格式化后的消息。例如,对于 logger I,当你写的是 I.info("Hello {}", name); 时,name 的值被指定为 "Alice",格式化后的消息就为 "Hello Alice"。 |
logger | String | logger 的名字 |
loggerContext | LoggerContextVO | 日志事件属于 logger 上下文中哪个受限的视图 (值对象) |
level | int | 事件级别对应的 int 值。用来创建包含级别的表达式。默认值是 DEBUG,INFO,WARN 以及 ERROR 也是有效的。所以 level > INFO 是有效的表达式。 |
timeStamp | long | 日志事件创建的时间 |
marker | Marker | 与日志请求相关的 Marker 对象。注意,marker 可能会为 null,因此你需要对这种情况进行检查,进而避免 NullPointerException。 |
mdc | Map | 创建日志事件时包含的所有的 MDC 值的一个映射。可以通过 mdc.get("myKey") 来获取 MDC 中对应的值。在 0.9.30 版本的 logback-classic,mdc 变量永远不会为 null。java.util.Map 类型是非参数化的,因为 Janino 不支持泛型。因此,mdc.get() 返回值的类型是 Object 而不是 String。但是可以将返回值强制转换为 String。例如, ((String) mdc.get("k")).contains("val")。 |
throwable | java.lang.Throwable | 如果日志事件没有相关的异常,那么变量 "throwable" 的值为 null。"throwable" 不可以被序列化。所以在远程服务器上,这个值永远为 null。想要使用与位置无关的表达式,可以使用下面的 throwableProxy。 |
throwableProxy | IThrowableProxy | 日志事件的异常代理。如果日志事件没有相关的异常,那么 throwableProxy 的值为 null。与 "throwable" 相反,即使在远程服务器上序列化之后,日志事件相关的异常也不会为 null。 |
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator> <!-- defaults to type ch.qos.logback.classic.boolex.JaninoEventEvaluator -->
<expression>return message.contains("billing");</expression>
</evaluator>
<OnMismatch>NEUTRAL</OnMismatch>
<OnMatch>DENY</OnMatch>
</filter>
<encoder>
<pattern>
%-4relative [%thread] %-5level %logger - %msg%n
</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
上面的配置将 EvaluatorFilter 添加到 ConsoleAppender。一个类型为 JaninoEventEvaluator 的 evaluator 之后被注入到 EvaluatorFilter 中。<evaluator 在缺少 class 属性的情况下,Joran 会指定 evaluator 的默认类型为 JaninoEventEvaluator。这是少数几个需要 Joran 默认指定类型的组件。
expression 元素对应刚才讨论过的评估表达式。表达式 return message.contains("billing"); 返回一个布尔值。message 变量会被 JaninoEventEvaluator 自动导出。
甚至可以是更复杂的表达式
<evaluator>
<expression>
if(logger.startsWith("org.apache.http"))
return true;
if(mdc == null || mdc.get("entity") == null)
return false;
String payee = (String) mdc.get("entity");
if(logger.equals("org.apache.http.wire") &&
payee.contains("someSpecialValue") &&
!message.contains("someSecret")) {
return true;
}
return false;
</expression>
</evaluator>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Matchers
虽然可以通过调用 String 类的 matches() 方法来进行模式匹配,但是每次调用 filter 都需要耗费时间重新编译一个新的 Pattern 对象。为了消除这种影响,你可以预先定义一个或者多个 Matcher 对象。一旦定义了一个 matcher,就可以在评估表达式中重复使用了。
<configuration debug="true">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator>
<matcher>
<Name>odd</Name>
<!-- filter out odd numbered statements -->
<regex>statement [13579]</regex>
</matcher>
<expression>odd.matches(formattedMessage)</expression>
</evaluator>
<OnMismatch>NEUTRAL</OnMismatch>
<OnMatch>DENY</OnMatch>
</filter>
<encoder>
<pattern>%-4relative [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# TurboFilters
TurboFilter 对象都继承 TurboFilter 抽象类。对于 regular 过滤器,它们使用三元逻辑来返回对日志事件的评估。
总之,它们跟之前提到的过滤工作原理差不多。主要的不同点在于 Filter 与 TurboFilter 对象。
TurboFilter 对象被绑定刚在 logger 上下文中。因此,在使用给定的 appender 以及每次发出的日志请求都会调用 TurboFilter 对象。因此,turbo 过滤器可以为日志事件提供高性能的过滤,即使是在事件被创建之前。
# 实现自己的 TurboFilter
想要创建自己的 TurboFilter 组件,只需要继承 TurboFilter 这个抽象类就可以了。跟之前的一样,想要实现定制的过滤器对象,开发自定义的 TurboFilter,只需要实现 decide() 方法就可以了。下一个例子,我们会创建一个稍微复杂一点的过滤器:
/**
* @author blog.unclezs.com
* @date 2020/12/4 10:32 下午
*/
public class CustomTurboFilter extends TurboFilter {
@Override
public FilterReply decide(Marker marker, Logger logger, Level level, String format, Object[] params, Throwable t) {
if (t == null) {
return FilterReply.DENY;
}
return FilterReply.ACCEPT;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
<configuration debug="false" scan="false" scanPeriod="1 second" packagingData="false">
<property name="pattern"
value="%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger) - %cyan(%msg%n)"/>
<turboFilter class="com.unclezs.samples.log.slf4j.logback.filter.CustomTurboFilter"/>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="com.unclezs.samples.log.slf4j.logback.filter.CustomRegularFilter"/>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
<!--在头部打印出pattern-->
<outputPatternAsHeader>true</outputPatternAsHeader>
</encoder>
<!--立即刷新到流-->
<immediateFlush>true</immediateFlush>
</appender>
<logger name="com.unclezs.samples.log.slf4j.logback" level="info" additivity="false">
<appender-ref ref="console"/>
</logger>
<root level="off">
</root>
</configuration>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 其他
loback-classic 附带了几个 TurboFilter 类可以开箱即用。MDCFilter 用来检查给定的值在 MDC 中是否存在。DynamicThresholdFilter 根据 MDC key/level 相关的阀值来进行过滤。MarkerFilter 用来检查日志请求中指定的 marker 是否存在。
- DuplicateMessageFilter
- MarkerFilter
- MDCFilter
- DynamicThresholdFilter