logback filters使用详解(一)

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<ILoggingEvent> {

  @Override
  public FilterReply decide(ILoggingEvent event) {    
    if (event.getMessage().contains("sample")) {
      return FilterReply.ACCEPT;
    } else {
      return FilterReply.NEUTRAL;
    }
  }
}

下面的配置文件展示了将SampleFillter添加到ConsoleAppender上。

<configuration>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">

    <filter class="chapters.filters.SampleFilter" />

    <encoder>
      <pattern>
        %-4relative [%thread] %-5level %logger - %msg%n
      </pattern>
    </encoder>
  </appender>
        
  <root>
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

 通过Joran,logback配置框架,可以很简单地指定属性或子模块到过滤器。向过滤器中添加属性的setter方法后,可以通过<property>指定属性值,嵌套到<filter>里。

我们想要的过滤器常常包含match与mismatch种情况,并通过是否匹配返回结果。例如:日志中需要根据是否为"foobar",一个过滤器可能匹配时返回ACCEPT,不匹配时返回NEUTRAL;另一个过滤器可能匹配时返回NEUTRAL不匹配时返回DENY。

考虑到上面情况,logback提供了AbstractMatcherFilter类,通过OnMatch与OnMismatch属性,可以根据是否匹配指定合适响应值。logback中大部分正则过滤器都继承于AbstractMatcherFilter。

级别过滤器

级别过滤器(LevelFilter)是基于准确匹配日志级别。如果日志级别等于配置的级别,过滤器通过配置中的OnMatch与OnMismatch属性决定是接受还是拒绝事件。下面是一个配置文件的例子:

<configuration>
  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
      <level>INFO</level>
      <onMatch>ACCEPT</onMatch>
      <onMismatch>DENY</onMismatch>
    </filter>
    <encoder>
      <pattern>
        %-4relative [%thread] %-5level %logger{30} - %msg%n
      </pattern>
    </encoder>
  </appender>
  <root level="DEBUG">
    <appender-ref ref="CONSOLE" />
  </root>
</configuration>

 阀值过滤器

阀值过滤器(ThresholdFilter)过滤低于指定阀值的事件。当事件中的日志级别大于等于指定阀值时,过滤器的decide方法返回NEUTRAL。然而,拒绝日志级别小于阀值的事件。下面是配置文件的例子:

<configuration>
  <appender name="CONSOLE"
    class="ch.qos.logback.core.ConsoleAppender">
    <!-- deny all events with a level below INFO, that is TRACE and DEBUG -->
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
      <level>INFO</level>
    </filter>
    <encoder>
      <pattern>
        %-4relative [%thread] %-5level %logger{30} - %msg%n
      </pattern>
    </encoder>
  </appender>
  <root level="DEBUG">
    <appender-ref ref="CONSOLE" />
  </root>
</configuration>

 条件过滤器

条件过滤器(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()转换成整数。

给你一个更全面的例子:

<configuration>
    
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <filter class="ch.qos.logback.core.filter.EvaluatorFilter">      
      <evaluator class="ch.qos.logback.classic.boolex.GEventEvaluator"> 
        <expression>
           e.level.toInt() >= WARN.toInt() &amp;&amp;  <!-- Stands for && in XML -->
           !(e.mdc?.get("req.userAgent") =~ /Googlebot|msnbot|Yahoo/ )
        </expression>
      </evaluator>
      <OnMismatch>DENY</OnMismatch>
      <OnMatch>NEUTRAL</OnMatch>
    </filter>
    <encoder>
      <pattern>
        %-4relative [%thread] %-5level %logger - %msg%n
      </pattern>
    </encoder>
  </appender>

  <root level="DEBUG">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

 上面的过滤器会让日志级别高于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。例如:((String) mdc.get("k")).contains("val")

throwable java.lang.Throwable

如果无异常关联到事件中,则throwable变量为null。

不幸的是,"throwable"不支持serialization。因此,对于远程系统,它的值永远为null。对于本地独立表达式,使用下面的throwableProxy

throwableProxy IThrowableProxy 日志事件异常的代理。如果无异常关联到事件上,"throwableProxy"变量值为空。与throwable不同的是,throwableProxy值不为空,即使是经过序列化的远程系统上

给你一个具体例子:

<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>

上面配置中将EvaluatorFilter添加到ConsoleAppender。然后将JaninoEventEvaluator添加到EvaluatorFilter中。如果用户未指定<evaluator>,Logback默认使用JaninoEventEvaluator。 这是Joran很少发生的不明确指推断组件类型的一种情况。

由于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 0
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 0
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代码块。例如,下面是一个合法的表达式:

<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") &amp;&amp; <!-- & encoded as &amp; -->
        payee.contains("someSpecialValue") &amp;&amp;
        !message.contains("someSecret")) {
      return true;
    }

    return false;
  </expression>
</evaluator>

Matchers

有可能调用String类的matches()方法进行正则匹配,但,这会造成每一次调用filter的时候进行一次编译并生成一个类型对象。为了避免这个问题,你可以提前定义一个或多个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>

 如果你需要定义更多的matcher,你可以参考上面添加更多的<matcher>元素。

参考文档:https://logback.qos.ch/manual/filters.html

猜你喜欢

转载自dongmj.iteye.com/blog/2359559