Logback过滤器是基于Ternary Logic,允许过滤器可以组合或串连在一直形成更复杂的过滤策略。这个设计很大程度上受到Linux的iptables影响。
关于logback自带过滤器
logback提供了两种过滤器:正则过滤器与turbo过滤器。
正则过滤器
正则过滤器继承于抽象类Filter,Filter本质上只包含以ILoggingEvent为入参的一个方法decide()。
基于Ternary Logic,过滤器以排序列表的形式组合在一起。系统按照这个顺序调用每个过滤器的decide(ILoggingEvent event)方法。decide方法返回结果是枚举类FilterReply(DENY, NEUTRAL, ACCEPT其中一个)。返回值为DENY时,日志事件直接丢弃这条日志,不会再传递给剩下的过滤器。返回值为NEUTRAL时,则传递给下面的过滤器。返回值为ACCEPT时,日志事件立即处理这条日志,跳过调用其它过滤器。
在logback中,过滤器可以添加到Appender上。通过添加一个或多个过滤器到Appender,你可以通过任意条件,比如:日志内容、MDC内容、时间等等日志的任何部分,过滤日志事件。
实现你自己的过滤器
创建你自己的过滤器很简单,你只需要继承Filter抽象类并实现decide方法。
下面,提供了一个例子SampleFilter。过滤器只处理日志内容饱含"sample"关键字的日志。
package chapters.filters;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;
public class SampleFilter extends Filter {
@Override
public FilterReply decide(ILoggingEvent event) {
if (event.getMessage().contains("sample")) {
return FilterReply.ACCEPT;
} else {
return FilterReply.NEUTRAL;
}
}
}
下面的配置文件展示了将SampleFillter添加到ConsoleAppender上。
%-4relative [%thread] %-5level %logger - %msg%n
通过Joran,logback配置框架,可以很简单地指定属性或子模块到过滤器。向过滤器中添加属性的setter方法后,可以通过
我们想要的过滤器常常包含match与mismatch种情况,并通过是否匹配返回结果。例如:日志中需要根据是否为"foobar",一个过滤器可能匹配时返回ACCEPT,不匹配时返回NEUTRAL;另一个过滤器可能匹配时返回NEUTRAL不匹配时返回DENY。
考虑到上面情况,logback提供了AbstractMatcherFilter类,通过OnMatch与OnMismatch属性,可以根据是否匹配指定合适响应值。logback中大部分正则过滤器都继承于AbstractMatcherFilter。
级别过滤器
级别过滤器(LevelFilter)是基于准确匹配日志级别。如果日志级别等于配置的级别,过滤器通过配置中的OnMatch与OnMismatch属性决定是接受还是拒绝事件。下面是一个配置文件的例子:
INFO ACCEPT DENY %-4relative [%thread] %-5level %logger{30} - %msg%n
阀值过滤器
阀值过滤器(ThresholdFilter)过滤低于指定阀值的事件。当事件中的日志级别大于等于指定阀值时,过滤器的decide方法返回NEUTRAL。然而,拒绝日志级别小于阀值的事件。下面是配置文件的例子:
INFO %-4relative [%thread] %-5level %logger{30} - %msg%n
条件过滤器
条件过滤器(EvaluatorFilter)是一种封装了EventEvaluator的通过过滤器。像名字代表的,条件过滤器是根据判断事件是否符合指定条件来分别返回OnMatch与OnMismatch属性里的值。
注意EventEvaluator是一个抽象类,通过继承它,你可以实现你自己的条件逻辑。
Groovy事件判断类
Groovy条件过滤器(GEventEvaluator)是一种以任何Groovy语言的布尔表达式作为判断条件的EventEvaluator的具体实现。我们称这种Groovy布尔表达式为"groovy evaluation expression"。groovy判断表达式是现在过滤器中最灵活的。Groovy事件判断类需要Groovy的运行环境,请将参考指定文档,将Groovy的运行环境添加到你的运行环境中。
判断表达式是在解析配置文件时进行编译的。作为用户,你不用担心实际的内存泄漏。然而,你需要保证你的groovy语言表达式合法。
判断表达式管理当前日志事件。Logback自动添加一个ILoggingEvent的变量'event'(也可以是短名字'e')代表当前日志事件。TRACE、DEBUG、INFO、WARN和ERROR也引入到当前表达式范畴内。因此,"event.level == DEBUG" 与 "e.level == DEBUG"是等价的,都是当前日志事件的级别为DEBUG时,返回true。对于其它比较操作,level变量需要通过toInt()转换成整数。
给你一个更全面的例子:
e.level.toInt() >= WARN.toInt() &&
!(e.mdc?.get("req.userAgent") =~ /Googlebot|msnbot|Yahoo/ )
DENY
NEUTRAL
%-4relative [%thread] %-5level %logger - %msg%n
上面的过滤器会让日志级别高于WARN的事件继续传递,或者错误是由Google、MSN、Yahoo爬虫产生的。这样做是为了判断事件的MDC中"req.userAgent"值是否匹配"/Googlebot|msnbot|Yahoo/"正则式。注意由于MDC的map可以为null,我们也使用了Groovy的未引用安全操作符"?."。这个表达逻辑如果通过Java实现会很长。
如果你想知道用户代理值是如何以"req.userAgent"键值插入到MDC中的,logback通过一个servlet过滤器MDCInsertingServletFilter实现的,显然,这已经超出了这篇文章的范围。它将在以后的章节中讲到。
Java事件判断类
Logback带了另一个叫做JaninoEventEvaluator的EventEvaluator实现,将任何返回布尔值的Java代码块作为判断条件。我们将这个Java布尔表达式称作"判断表达式"。判断表达式在事件过滤中有很强的灵活性。JaninoEventEvaluator需要Janino jar。相关依赖请参考其它章节。
和JaninoEventEvaluator比较,由于是Groovy的优点,GEventEvaluator更便于使用,但,相同的表达式,JaninoEventEvaluator一般运行快很多。
判断表达式是在解决配置文件时编译的。作为用户,你不需要担心内存问题。但,你需要保证Java语言表达式返回一个布尔值,例如,结果为true或false
判断表达式是判断当前的日志事件。判断表达式可以获取到Logback自动导入的各种变量。导入变更中字母大小写敏感的变更展示如下:
Name | Type | Description |
event | LoggingEvent | 关联日志请求的原始日志事件。下面全部变量都可以在event中取得。例如:event.getMessage()与message变量返回一样的String。 |
message | String | 日志请求中原始信息。像某些日志/,当你写l.info("Hello {}", name),name关联的值为"Alice",然后message为"Hello {}"。 |
formattedMessage | String | 日志请求中的格式化消息。像某些日志/,当你写l.info("Hello {}", name),name关联值为"Allice",格式化消息为"Hello Alice"。 |
logger | String | 日志的名字 |
loggerContext | LoggerContextVO | 日志事件所属日志上下文的一个限制视图对象 |
level | int | 日志级别的int值。用于简化创建关于level的表达式。默认值DEBUG、INFO、WARN和ERROR也可以获得。因此,使用level>INFO是正确的 |
timeStamp | long | 日志事件创建时间。 |
marker | Marker | 日志请求中的Marker对象。注意这个对象可能为空。 |
mdc | Map | 包含日志事件创建时的全部MDC值的map对象。可能通过后面表达式获取值:mdc.get("myKey").从0.9.30,"mdc"变量永远不为空 由于Janino不支持泛型,java.util.Map不包含参数类型。mdc.get()返回的对象是Object而不是String。为了调用返回值的String方法,你需要强转成String。例如: |
throwable | java.lang.Throwable | 如果无异常关联到事件中,则throwable变量为null。 不幸的是,"throwable"不支持serialization。因此,对于远程系统,它的值永远为null。对于本地独立表达式,使用下面的throwableProxy |
throwableProxy | IThrowableProxy | 日志事件异常的代理。如果无异常关联到事件上,"throwableProxy"变量值为空。与throwable不同的是,throwableProxy值不为空,即使是经过序列化的远程系统上 |
给你一个具体例子:
return message.contains("billing"); NEUTRAL DENY %-4relative [%thread] %-5level %logger - %msg%n
上面配置中将EvaluatorFilter添加到ConsoleAppender。然后将JaninoEventEvaluator添加到EvaluatorFilter中。如果用户未指定
由于OnMismatch属性设置为NEUTRAL,OnMatch属性设置为DENY,这个条件过滤器将丢掉全部内容包含"billing"的事件。
FilterEvents应用程序发出10个日志请求,从0到9。首先,让我们跑不包含任何过滤器的FilterEvent:
java chapters.filters.FilterEvents src/main/java/chapters/filters/basicConfiguration.xml
全部请求会向下面展示的一样:
0 [main] INFO chapters.filters.FilterEvents - logging statement 1
0 [main] INFO chapters.filters.FilterEvents - logging statement 2
0 [main] DEBUG chapters.filters.FilterEvents - logging statement 3
0 [main] INFO chapters.filters.FilterEvents - logging statement 4
0 [main] INFO chapters.filters.FilterEvents - logging statement 5
0 [main] ERROR chapters.filters.FilterEvents - billing statement 6
0 [main] INFO chapters.filters.FilterEvents - logging statement 7
0 [main] INFO chapters.filters.FilterEvents - logging statement 8
0 [main] INFO chapters.filters.FilterEvents - logging statement 9
假如我们想处理"billing statement"。上面讲到的basicEventEvaluator.xml过滤包含"billing",这是我们想要的准确结果。
运行basicEventEvaluator.xml:
java chapters.filters.FilterEvents src/main/java/chapters/filters/basicEventEvaluator.xml
我们将得到:
0 [main] INFO chapters.filters.FilterEvents - logging statement 1
0 [main] INFO chapters.filters.FilterEvents - logging statement 2
0 [main] DEBUG chapters.filters.FilterEvents - logging statement 3
0 [main] INFO chapters.filters.FilterEvents - logging statement 4
0 [main] INFO chapters.filters.FilterEvents - logging statement 5
0 [main] INFO chapters.filters.FilterEvents - logging statement 7
0 [main] INFO chapters.filters.FilterEvents - logging statement 8
0 [main] INFO chapters.filters.FilterEvents - logging statement 9
判断表达式可以是java代码块。例如,下面是一个合法的表达式:
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;
Matchers
有可能调用String类的matches()方法进行正则匹配,但,这会造成每一次调用filter的时候进行一次编译并生成一个类型对象。为了避免这个问题,你可以提前定义一个或多个Matcher对象。定义完matcher后,你可以在判断表达式中通过名字重复使用对象。
下面例子讲了这种情况:
odd
statement [13579]
odd.matches(formattedMessage)
NEUTRAL
DENY
%-4relative [%thread] %-5level %logger - %msg%n
如果你需要定义更多的matcher,你可以参考上面添加更多的
参考文档:https://logback.qos.ch/manual/filters.html