在前面我们说过 basic selection rule,这一节我们来讲另一个附加的过滤方法。
Logback filters 可以通过串链方式组成一个复杂过滤规则,类似 linux 系统的 iptables 防火墙。
在Logback-classic中提供两个类型的 filters , 一种是 regular filters , 另一种是 turbo filter。
regular filters 是与appender 绑定的, 而turbo filter是与与logger context(logger 上下文)绑定的,区别就是,turbo filter过滤所有logging request ,而regular filter只过滤某个appender的logging request。
Regular filters
在 logback-classic中,filters 继承
Filter 抽象类,Filter 抽象类有一个 decide()抽象方法,接收一个 ILoggingEvent 对象参数,而在 logback-access 中 则是 AccessEvent 对象。该方法返回一个enum类型 FilterReply。
值可以是
- DENY:如果方法返回DENY(拒绝),则跳出过滤链,而该 logging event 也会被抛弃。
- NRUTRAL:如果返回NRUTRAL(中立),则继续过滤链中的下一个过滤器。
- ACCEPT:如果返回ACCEPT(通过),则跳出过滤链
public abstract FilterReply decide(E event);
下面我们实现一个简单的 Filter
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
<
ILoggingEvent
>
{
@Override
public
FilterReply decide
(
ILoggingEvent
event
)
{
if
(
event
.getMessage
().contains
(
"sample"
))
{
return
FilterReply
.ACCEPT
;
}
else
{
return
FilterReply
.NEUTRAL
;
}
}
}
然后配置使用自己的Filter
name
=
"STDOUT"
class
=
"ch.qos.logback.core.ConsoleAppender"
>
class="chapters.filters.SampleFilter" />
%-4relative [%thread] %-5level %logger - %msg%n
ref
=
"STDOUT"
/>
上面的例子中,我们的filter类继承了Filter抽象类,但实际上,我们都会继承
AbstractMatcherFilter ,AbstractMatcherFilter继承自Filter,并提供了两个属性,OnMatch 和 OnMismatch。
下面我们看一下 Logback-classic中封装的几个 regular Filter
LevelFilter 的过滤是基于 logging event 的 level ,如果等于配置的level,则过滤器通过,否则拒绝。下面是它的配置
name
=
"CONSOLE"
class
=
"ch.qos.logback.core.ConsoleAppender"
>
class="ch.qos.logback.classic.filter.LevelFilter">
INFO
ACCEPT
DENY
%-4relative [%thread] %-5level %logger{30} - %msg%n
level
=
"DEBUG"
>
ref
=
"CONSOLE"
/>
ThresholdFilter
ThresholdFilter 其实跟我们之前说的
basic selection rule 很像,也是基于日志等级门槛过滤的。当logging event的 level 大于等于配置等级,才能通过过滤器。
name
=
"CONSOLE"
class
=
"ch.qos.logback.core.ConsoleAppender"
>
class="ch.qos.logback.classic.filter.ThresholdFilter">
INFO
%-4relative [%thread] %-5level %logger{30} - %msg%n
level
=
"DEBUG"
>
ref
=
"CONSOLE"
/>
EvaluatorFilter
EvaluatorFilter 是一个通用的 filter,它包含了一个
EventEvaluator
对象。EventEvaluator对象负责过滤条件的判断,过滤的结果由 onMatch 和 onMismatch这两个属性决定。
注意: EventEvaluator 是一个抽象类。你也可以实现自己的 Evaluator
下面我们看几个常见的EventEvaluator
GEventEvaluator
接收 Groovy 语言的条件表达式作为判断条件。Groovy evaluation expression 是目前最灵活的表达式。GEventEvaluator需要Groovy运行环境。表达式会在运行时,在解释配置文件的时候被编译。
使用maven 添加groovy包到classpath
org.codehaus.groovy
groovy-all
2.4.0
在 evaluation expression 中,你可以通过 event 或者缩写 e ,访问 ILoggingEvent对象。例如你可以通过以下表达式判断 当前logging event的等级是否等于DEBUG :
event.level == DEBUG 或者
e.level == DEBUG ,如果你想通过大于等于,或小于等于这些操作,就需要将 level转换成int值,再判断。例如下面的例子:
name
=
"STDOUT"
class
=
"ch.qos.logback.core.ConsoleAppender"
>
class="ch.qos.logback.core.filter.EvaluatorFilter">
class="ch.qos.logback.classic.boolex.GEventEvaluator">
e.level.toInt() >= WARN.toInt() &&
!(e.mdc?.get("req.userAgent") =~ /Googlebot|msnbot|Yahoo/ )
DENY
NEUTRAL
%-4relative [%thread] %-5level %logger - %msg%n
level
=
"DEBUG"
>
ref
=
"STDOUT"
/>
上面的例子过滤掉 logging event 的level小于 warn 的,并且 mdc中 key为req.userAgent的值不是 Googlebot , msnbot, Yahoo 中的其中一个。
需要注意的是,因为mdc中可能不存在对应的键值对,所以可能为空,上面的例子就使用了安全引用符 "?.” 。
我们也注意到了,mdc中包含了HttpServletRequest对象,他是怎么实现的呢?就是通过一个servlet filter
MDCInsertingServletFilter
实现的。
JaninoEventEvaluator
JaninoEventEvaluator 与 GEventEvaluator 恰恰相反,接收的是一个 java 的判断表达式作为判断条件。
JaninoEventEvaluator 依赖于 Janino library
org.codehaus.janino
3.0.0
这两个Evaluator相比,GEventEvaluator使用会更加灵活,但
JaninoEventEvaluator判断速度会快得多。
Logback-classic会自动帮我们导出logging event的属性到evaluation 表达式中,所以我们可以直接使用以下属性判断:
Name |
Type |
Description |
event |
LoggingEvent |
原始的logging event 对象。你可以通过该对象获取以下属性。例如event.getMessage()相当于message |
message |
String |
logging request 的原始message。例如,当你编码 I.info(“hello {}”, name); 这时, message的值就是 “hello {}” |
formattedMessage |
String |
格式化后的message。例如:当你编码 I.info(“hello {}”, name); name=“Alice”,则message的值就是 “hello Alice” |
logger |
String |
logger的名称 |
loggerContext |
LoggerContextVO |
logging event 属于的 LoggerContext 对象 |
level |
int |
logging event 的等级,注意:与GEventEvaluator不同,这里可以直接使用 level > INFO 的方式判断 日志等级,而在GEventEvaluator中需要先转换成int值 |
timeStamp |
long |
logging event 产生的时间 |
marker |
Marker |
logging request 的 Marker标签。需要注意:marker可以为空,所以你需要自己判断Marker是否为空,避免空指针异常。 |
mdc |
Map |
Map类型,包含了该logging event 的MDC值,可以通过:mdc.get(“key”)的方式获取,在logback-classic 0.9.30版本后,mdc永远不为空,但是需要注意,获取的是object对象,按你的编码强制转换类型,例如:((String) mdc.get("k")).contains("val") |
throwable |
java.lang.Throwable |
logging event的异常信息,如果没有异常关联,则这个值为null。注意,throwable 不支持序列化,所以在远程日志服务器中,该值为Null, 所以需要使用throwableProxy |
throwableProxy |
IThrowableProxy |
logging event exception 的代理。如果没有异常,则throwableProxy为null,但它支持序列化。 |
下面看个例子:
name
=
"STDOUT"
class
=
"ch.qos.logback.core.ConsoleAppender"
>
class="ch.qos.logback.core.filter.EvaluatorFilter">
return message.contains("billing");
NEUTRAL
DENY
%-4relative [%thread] %-5level %logger - %msg%n
level
=
"INFO"
>
ref
=
"STDOUT"
/>
上面的例子中,我们使用了默认JaninoEventEvaluator,由于OnMatch 被设置成DENY,所以该过滤器会丢弃所有message包含”billing”的logging event。
当然,因为JaninoEventEvaluator的expression 接收的是一个java 代码块,只要求该代码块返回 boolean值就行。所以我们可以来个复杂的:
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;
如果你还需要用到正则表达式的话,你还能使用Matcher
我们不推荐通过调用String类的matches()方法来匹配规则,因为这样每次都会重新创建一个新Pattern对象,浪费资源。我们推荐matcher服用的方式。例如下面配置:
debug
=
"true"
>
name
=
"STDOUT"
class
=
"ch.qos.logback.core.ConsoleAppender"
>
class
=
"ch.qos.logback.core.filter.EvaluatorFilter"
>
odd
statement [13579]
odd.matches(formattedMessage)
NEUTRAL
DENY
%-4relative [%thread] %-5level %logger - %msg%n
level
=
"DEBUG"
>
ref
=
"STDOUT"
/>
现在我们来说。logback-classic中的第二类过滤器。
TurboFilters
TurboFilter 是一个抽象类,是所有TurboFilter的祖先。与regular filters 一样,他也是串链的逻辑。
其实,Trubo Filter 与 Regular Filter 非常相似,只是有两点主要的不同。
- TurboFilter对象是与 logging context绑定的。因此,它会处理所有的logging request,而不是单独的appender。过滤范围更广。
- 更重要的是,他们在LoggingEvent对象创建之前就已经调用了。因此TurboFilter 对象并不需要 logging event来过滤logging request。因此会有更好的性能表现。
实现自己的TurboFilter
package chapters
.filters
;
import org
.slf4j
.
Marker
;
import org
.slf4j
.
MarkerFactory
;
import ch
.qos
.logback
.classic
.
Level
;
import ch
.qos
.logback
.classic
.
Logger
;
import ch
.qos
.logback
.classic
.turbo
.
TurboFilter
;
import ch
.qos
.logback
.core
.spi
.
FilterReply
;
public
class
SampleTurboFilter
extends
TurboFilter
{
String marker
;
Marker markerToAccept
;
@Override
public
FilterReply decide
(
Marker marker
,
Logger logger
,
Level level
,
String format
,
Object
[]
params
,
Throwable t
)
{
if
(!isStarted
())
{
return
FilterReply
.NEUTRAL
;
}
if
((markerToAccept
.equals
(marker
)))
{
return
FilterReply
.ACCEPT
;
}
else
{
return
FilterReply
.NEUTRAL
;
}
}
public
String getMarker
()
{
return marker
;
}
public
void setMarker
(
String markerStr
)
{
this
.marker
= markerStr
;
}
@Override
public
void start
()
{
if
(marker
!=
null
&& marker
.trim
().length
()
>
0
)
{
markerToAccept
=
MarkerFactory
.getMarker
(marker
);
super
.start
();
}
}
}
配置使用自己的Turbo Filter
class="chapters.filters.SampleTurboFilter">
sample
name
=
"STDOUT"
class
=
"ch.qos.logback.core.ConsoleAppender"
>
%-4relative [%thread] %-5level %logger - %msg%n
ref
=
"STDOUT"
/>
Logback-classic 封装了几个常用的TurboFilter
MDCFilter : 通过MDC过滤
DynamicThresholdFilter :通过MDC 或 level 过滤
MarkerFilter :通过marker标签过滤
下面看个配置:
class="ch.qos.logback.classic.turbo.MDCFilter">
username
sebastien
ACCEPT
class="ch.qos.logback.classic.turbo.MarkerFilter">
billing
DENY
name="console"class="ch.qos.logback.core.ConsoleAppender">
%date [%thread] %-5level %logger - %msg%n
level="INFO">
ref="console"/>
DuplicateMessageFilter
DuplicateMessageFilter
是用来过滤重复日志的。需要注意的是,它的判断方式如下
用例子说明:
logger.debug("Hello "+name0);
logger.debug("Hello "+name1);
以上会被认为不重复
logger
.debug
(
"Hello {}."
, name0
);
logger
.debug
(
"Hello {}."
, name1
);
这样则会被认为是重复的。
我们可以通过设置
AllowedRepetitions的值来设置重复的阈值。默认值为5。
为了判断是否重复,
DuplicateMessageFilter
需要维持一个old message的引用在内部缓冲区中。这个缓冲区的大小由 CacheSise 属性决定,默认值是100。
看个例子
class="ch.qos.logback.classic.turbo.DuplicateMessageFilter">
150
name
=
"console"
class
=
"ch.qos.logback.core.ConsoleAppender"
>
%date [%thread] %-5level %logger - %msg%n
level
=
"INFO"
>
ref
=
"console"
/>
In logback-access
与其他组件一样,logback-access也提供了与logback-classis 差不多的功能。不过logback-classic的event的类型
AccessEvent
在
logback-access中,只提供了少数的Filter
CountingFilter
通过 CountingFilter , logback-Access 提供数据统计的功能。初始化后,CountingFilter 就会将自己注册成一个MBean,在JMX server 平台上。你可以通过这个MBean访问访问已经统计的数据,例如每分钟,每小时,每天,每星期,每月经过web-server的数据。
看下官网的配置
class
=
"ch.qos.logback.core.status.OnConsoleStatusListener"
/>
class="ch.qos.logback.access.filter.CountingFilter">
countingFilter
name
=
"STDOUT"
class
=
"ch.qos.logback.core.ConsoleAppender"
>
%h %l %u %t \"%r\" %s %b
ref
=
"STDOUT"
/>
你可以通过 jconsole 应用查看
EvaluatorFilter(上面已经说过,与logback-classic是同一个,若不指定EvaluatorFilter,则默认使用JaninoEventEvaluator,参考上面
)
logback-access 会自动导出当前AccessEvent对象的属性,你可以在expression中直接使用。详细属性,请查看
AccessEvent
class source code
下面看个例子:这个例子过滤 access event 的状态马不是404的日志
class
=
"ch.qos.logback.core.status.OnConsoleStatusListener"
/>
name
=
"STDOUT"
class
=
"ch.qos.logback.core.ConsoleAppender"
>
class="ch.qos.logback.core.filter.EvaluatorFilter">
event.getStatusCode() == 404
DENY
%h %l %u %t %r %s %b
ref
=
"STDOUT"
/>
再来一个:这个例子过滤掉状态码不是404并且,uri包含.css的日志
class
=
"ch.qos.logback.core.status.OnConsoleStatusListener"
/>
name
=
"STDOUT"
class
=
"ch.qos.logback.core.ConsoleAppender"
>
class
=
"ch.qos.logback.core.filter.EvaluatorFilter"
>
name
=
"Eval404"
>
(event.getStatusCode() == 404)
&&
!(event.getRequestURI().contains(".css"))
DENY
%h %l %u %t %r %s %b
ref
=
"STDOUT"
/>