logback_3-Filters


Filtersappender搭配形成组合拳,能够灵活的定制对 LoggingEvent 输出/忽略的逻辑.

在日常开发中,我们需要打日志时会 logger.info() 一下, 假如需要根据业务数据来决定打/不打, 那么需要在业务代码中 增加判断 if else /elseif. 可仔细想想, 这场景似乎可以抽成统一处理逻辑: 业务代码尽管 logging.info() 好了, 打/不打的逻辑在 Filter中统一做.

Filters就是用来控制 logger.info()之后, 打/不打 的.

(嗯,to log or not to log, that is a question!)

Logback的Filters 是典型的 责任链设计模式, 其在设计过程中也参考了 linux iptables .

一. In logback-classic

logback-classic中有两个大类 filters , 即 regular filter , turbo filters.
翻译过来就是, 普通过滤器, “涡轮增压"过滤器. 后面我们会说到, turbo filters是怎么"涡轮增压” (增强)的.

1.1 Regular filters

Regular logback-classic filters应该继承 Filter这个抽象类,并实现 abstract FilterReply decide() 方法. 这个方法的意图一目了然, 就是处理后返回结论:

  • 打, ACCEPT
  • 不打,DENY
  • 当前fitler 不好判断,让下一个Filter决定, NEUTRAL
public abstract class Filter<E> extends ContextAwareBase implements LifeCycle {
    private String name;
    boolean start = false;
    public void stop() {
        this.start = false;
    }
    /**
     * If the decision is <code>{@link FilterReply#DENY}</code>, then the event will be
     * dropped. If the decision is <code>{@link FilterReply#NEUTRAL}</code>, then the next
     * filter, if any, will be invoked. If the decision is
     * <code>{@link FilterReply#ACCEPT}</code> then the event will be logged without
     * consulting with other filters in the chain.
     * 
     * @param event
     *                The event to decide upon.
     */
    public abstract FilterReply decide(E event);
   //setter getter
}

FiltersAppender搭配起来, 并形成一个"chain". 每个 Filter都能根据多种条件,比如 LoggingEvent具体内容, MDC内容, 时间 等来判断 打/不打.

1.2 Implementing your own Filter

怎么自己整一个Filter呢?很简单,看例子:
步骤一: 写代码

public class SampleFilter extends Filter<ILoggingEvent> {

// 注意这里是很生硬的字符串匹配,对于日志量大的应用, 可能要评估下对性能影响
  @Override
  public FilterReply decide(ILoggingEvent event) {    
    if (event.getMessage().contains("sample")) {
      return FilterReply.ACCEPT;
    } else {
      return FilterReply.NEUTRAL;
    }
  }
}

步骤二: 加配置

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

这里还要另外提一下, 假如在SampleFilter中有一个属性和 setter的话,那么借助logback的Joran框架,就可以通过在 xml中增加配置 set 这个属性. (在 <filter> </filter> 标签中增加).
这有点像spring.xml .

此外, logback-classic还提供了AbstractMatcherFilter这个抽象类方便实现Filter,看:

public abstract class AbstractMatcherFilter<E> extends Filter<E> {

    protected FilterReply onMatch = FilterReply.NEUTRAL;
	protected FilterReply onMismatch = FilterReply.NEUTRAL;
	
	//getter setter
}

下面再介绍几种 不实用的Filter (你没看过,就是不大实用)

1.3 LevelFilter

这个是根据 日志 Level来过滤,比如下面的. 但这个委实鸡肋, 如果我需要过滤掉这些日志,那么为啥我还要调 logger.info() 呢?

不过呢, 有一种场景可以使用: 我想把 某一个Level,比如 INFO 的日志单独输出到一个 info.log 中,那么我可以 定义一个 info-appender,然后<appender-ref> root logger .

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

1.4 ThresholdFilter

这个Filter可以指定一个 Level, 然后把级别更高的Level的日志打出来.
比如下面的配置不会打印 TRACE DEBUG.

和 上面说的LevelFilter类似, 这个Filter也略鸡肋.你有根据Level 打/不打 的需求,为啥不在 Logger 中配置算了?

但可以利用ThresholdFilter实现一个功能: 将error 日志统一输出到 error.log . 我们只要在 root logger 中 <appender-ref> error-appender (假如是叫这个名字的话)

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

1.5 EvaluatorFilter

EvaluatorFilterEventEvaluator的封装. 其实就是把EventEvaluator boolean evaluate()结果转化成 EvaluatorFilter FilterReply decide().

1.5.1 GEventEvaluator

GEventEvaluator: 支持Groovy脚本的 EventEvaluator. 不过一般不会用, 打个日志搞那么复杂作甚?

不过这种利用脚本拓展功能的思路还是挺好的.

1.5.2 JaninoEventEvaluator

Janino是轻量的极快的Java complier. 我们在项目中使用JaninoEventEvaluator那么我们还要另外引入 Janino的依赖.

坦白说,这个虽然很强,但是一般不会用.

二. TurboFilters

TurboFilterRegular Filter的思想是一致的,工作方式也大同小异,但是,二者有二个较大不同点.

  • TurboFilter是绑定到 LoggingContxt维度的.所以, 不但在使用 appender 的时候会调用, 每处理一个 logging request 也会调用.
  • TurboFilter在创建LoggingEvent对象前被调用. TurboFilter不需要初始化 LoggingEvent. 因此,TurboFilter是用于高性能的 LoggingEvent filtering,i.e., 在LoggingEvent创建前就可以filter.

2.1 Implementing your own TurboFilter

我们整一个自己的 TurboFilter.

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;
    }
  }
  //setter getter
  @Override
  public void start() {
    if (marker != null && marker.trim().length() > 0) {
      markerToAccept = MarkerFactory.getMarker(marker);
      super.start(); 
    }
  }
}

TurboFilter传入接收包含 特定marker 的LoggingEvent (方法FilterReply decide()).如果 marker没找着, 那么就交给下一个Filter.

为了增强灵活性,可以在配置文件中指定marker, 看下面将上面我们自定义的TurboFilter加入配置.

examples/src/main/resources/chapters/filters/sampleTurboFilterConfig.xml) View as .groovy
<configuration>
  <turboFilter class="chapters.filters.SampleTurboFilter">
    <Marker>sample</Marker>
  </turboFilter>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>
        %-4relative [%thread] %-5level %logger - %msg%n
      </pattern>
    </encoder>
  </appender>

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

Logback-classic已经内置了 MDCFilter,DynamicThresholdFilter,MarkerFilter.

(注意: MDCFilter, DynamicThresholdFilter,MarkerFilter are all sub-classes of TurboFilter)

2.2 MDCFilter + MarkerFilter 共用

我们来看一个 两个TurboFilter共用的demo:
代码:

 for (int i = 0; i < 10; i++) {
    if (i == 3) {
         MDC.put("username", "sebastien");
         logger.debug("logging statement {}", i);
         MDC.remove("username");
     } else if (i == 6) {
         /** 通过这种方法, 在LoggingEvent中打了一个 marker 标记;然后在后面处理时再将 marker 拿出来处理
          * 你还别说, 这个貌似还是有点可用场景的.*/
         Marker billing = MarkerFactory.getMarker("billing");
         logger.error(billing, "billing statement {}", i);
     } else {
         logger.info("logging statement {}", i);
     }
 }

配置:

<configuration>
  <turboFilter class="ch.qos.logback.classic.turbo.MDCFilter">
    <MDCKey>username</MDCKey>
    <Value>sebastien</Value>
    <OnMatch>ACCEPT</OnMatch>
  </turboFilter>    
  
  <turboFilter class="ch.qos.logback.classic.turbo.MarkerFilter">
    <Marker>billing</Marker>
    <OnMatch>DENY</OnMatch>
  </turboFilter>
  
  <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%date [%thread] %-5level %logger - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="INFO">
    <appender-ref ref="console" />
  </root>  
</configuration>
  • 先看 第一个 MDCFilter,当有一个logger.debug()调用,且存在 mdc key=username,value=sebastien 时,那么这个LoggingEvent 将会被打印出来, 即使当前 root level =INFO
  • 再看第二个MarkerFilter,当有一个 logger.error()调用,且存在marker= billing时, 那么即使当前root level=INFO < ERROR ,依然不会打印出来.

这个例子旨在说明: TurboFilterLoggingEvent的 打/不打 有着比Regular Filter更高的优先级.

2.3 DuplicateMessageFilter

DuplicateMessageFilter能够对多条内容相同的消息去重. 但是这个Filter十分鸡肋, 实践过程中,即使是相同的日志也是有参考价值的,说明不小心掉进入了死循环. 一般不推荐将 message filter 搞那么复杂… 甚至于最好就不用使用 filter .

三. In logback-access

前面我们讲了 logback-classic的feature,下面我们讲 logback-access的, 其实二者大同小异. 但有一点不同的是, 后者处理AccessEvent, 而前者处理LoggingEvent.

3.1 CountingFilter

我们可以定制一个Filter, 对LoggingEvent计数,并通过JMX暴露出去.

3.2 EvaluatorFilter

EvaluatorFilter是对EventEvaluator封装 ,玩法和 Logback-classicEventFilter如出一辙.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值