logback官方文件中文翻譯第七章:Filters

weixin_34337265發表於2019-01-20

第七章:Filters

在之前的章節中介紹的方法列印以及基本選擇規則是 logback-classic 的核心。在這章中,將介紹其它的過濾方法。

logback 過濾器基於三元邏輯,允許它們組裝或者連結在一起組成一個任意複雜的過濾策略。它們在很大程度上受到 Linux iptables 的啟發。

在 logback-classic 中

在 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() 方法就可以了。

如下所示的 SampleFilter 就是一個簡單的例子。如果日誌事件包含字元 "sample", decide 方法返回 ACCEPT。對於其他的日誌事件,則返回 NEUTRAL。

下面是關於將 SampleFilter 新增到 ConsoleAppender 上的配置示例:

Example: SampleFilterConfig.xml

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

在 logback 配置框架 Joran 的幫助下,為過濾器指定屬性或者子元件也變得更加的簡單。在過濾器類中新增相應的 set 方法,通過 <filter> 元素巢狀一個以屬性命名的 xml 元素中指定屬性的值。

通常情況下,過濾器的邏輯由兩個正交的部分組成,match/mismatch 的檢驗以及基於 match/mismatch 的返回值。例如,對於給定的檢驗,訊息等於 "foobar",一個過濾器在 match 的情況下返回 ACCEPT,在 mismatch 的情況下返回 NEUTRAL。另一個過濾可能在 match 的情況下返回 NEUTRAL,在 mismatch 的情況下返回 DENY。

注意這種正交,logback 附帶了一個 AbstractMatcherFilter 類,提供了一個有用的骨架用來指定在 match 與 mismatch 情況下的返回值,這兩個屬性名分別叫做 OnMatchOnMismatch。logback 中大部分的 regular 過濾器都源於 AbstractMatcherFilter

LevelFilter

LevelFilter 基於級別來過濾日誌事件。如果事件的級別與配置的級別相等,過濾器會根據配置的 onMatchonMismatch 屬性,接受或者拒絕事件。如下是一個簡單的示例:

Example: levelFilterConfig.xml

<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

ThresholdFilter 基於給定的臨界值來過濾事件。如果事件的級別等於或高於給定的臨界值,當呼叫 decide() 時,ThresholdFilter 將會返回 NEUTRAL。但是事件的級別低於臨界值將會被拒絕。下面是一個簡單的例子:

Example: thresholdFilterConfig.xml

<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

EvaluatorFilter 是一個通用的過濾器,它封裝了一個 EventEvaluator。顧名思義,EventEvaluator 根據給定的標準來評估給定的事件是否符合標準。在 match 和 mismatch 的情況下,EvaluatorFilter 將會返回 onMatchonMismatch 指定的值。

注意 EventEvaluator 是一個抽象類。你可以通過繼承 EventEvaluator 來實現自己事件評估邏輯。

GEventEvaluator

GEventEvaluatorEventEvaluator 具體的實現,它採用 Groovy 表示式作為評估的標準。我們把 Groovy 表示式稱為 "Groovy 評估表示式"。Groogy 評估表示式是目前為止進行事件過濾最靈活的方式。GEventEvaluator 需要 Groovy 執行環境。參考相關部分在類路徑下新增 Groovy 執行環境。

評估表示式在解析配置檔案期間被動態編譯。作為使用者,不需要考慮實際的情況。但是,你需要確保你的 Groovy 表示式是有效的。

評估表示式作用於當前的日誌事件。logback 會自動將 ILoggingEvent 型別的日誌事件作為變數插入,引用到 'event' 或者它的簡稱 'e'。TRACE, DEBUG, INFO, WARN 以及 ERROR 也能夠被匯入到表示式的範圍中。所以,"event.level == DEBUG" 與 "e.level == DEBUG" 是等價的。只有噹噹前日誌事件的級別為 DEBUG 時,Groovy 表示式才會返回 true。對於其它的級別比較操作,應該通過 toInt() 操作將 level 欄位轉變為整型。

下面是一個比較複雜的例子:

<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;  <!-- 在 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|msbbot|Yahoo/ 正規表示式。因為 MDC 的對映可能為 null,所以我們使用 Groovy 的安全解引用操作符,也就是 ?. 操作符。這個相等的邏輯在 Java 中的表示式更長。

如果你好奇 user agent 識別符號作為值怎樣被插入到 key 為 "req.userAgent " 的 MDC 中,那麼就會涉及到 logback 為了這個目的附帶了一個名為 MDCInsertingServletFilter 的 servlet 過濾器。它將會在接下來的章節中描述。

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。<br />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。

下面是具體的例子。

Example: basicEventEvaluator.xml

<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 的 evaluator 之後被注入到 EvaluatorFilter 中。<evaluator 在缺少 class 屬性的情況下,Joran 會指定 evaluator 的預設型別為 JaninoEventEvaluator。這是少數幾個需要 Joran 預設指定型別的元件。

expression 元素對應剛才討論過的評估表示式。表示式 return message.contains("billing"); 返回一個布林值。message 變數會被 JaninoEventEvaluator 自動匯出。

由於 OnMismatch 屬性的值為 NEUTRAL 以及 OnMatch 屬性的值為 DENY,所以評估過濾器會丟掉訊息包含 "billing" 的日誌事件。

FilterEvents 發出十條日誌請求,編號為 0 到 9。首先在沒有過濾器的情況下執行 FilterEvents

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 中配置的過濾器恰好可以滿足這個需求。

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

    return false;
  </expression>
</evaluator>

Matchers

雖然可以通過呼叫 String 類的 matches() 方法來進行模式匹配,但是每次呼叫 filter 都需要耗費時間重新編譯一個新的 Pattern 物件。為了消除這種影響,你可以預先定義一個或者多個 Matcher 物件。一旦定義了一個 matcher,就可以在評估表示式中重複使用了。

通過一個簡單的例子來說明這一點:

Example: evaluatorWithMatcher.xml

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

通過 evaluatorWithMatcher.xml 執行:

java chapters.filters.FilterEvents src/main/java/chapters/filters/evaluatorWithMatcher.xml

將會得到:

260  [main] INFO  chapters.filters.FilterEvents - logging statement 0
264  [main] INFO  chapters.filters.FilterEvents - logging statement 2
264  [main] INFO  chapters.filters.FilterEvents - logging statement 4
266  [main] ERROR chapters.filters.FilterEvents - billing statement 6
266  [main] INFO  chapters.filters.FilterEvents - logging statement 8

如果你想定義其它的 matcher,可以繼續增加 <matcher> 元素。

TurboFilters

TurboFilter 物件都繼承 TurboFilter 抽象類。對於 regular 過濾器,它們使用三元邏輯來返回對日誌事件的評估。

總之,它們跟之前提到的過濾工作原理差不多。主要的不同點在於 FilterTurboFilter 物件。

TurboFilter 物件被繫結剛在 logger 上下文中。因此,在使用給定的 appender 以及每次發出的日誌請求都會呼叫 TurboFilter 物件。因此,turbo 過濾器可以為日誌事件提供高效能的過濾,即使是在事件被建立之前。

實現自己的 TurboFilter

想要建立自己的 TurboFilter 元件,只需要繼承 TurboFilter 這個抽象類就可以了。跟之前的一樣,想要實現定製的過濾器物件,開發自定義的 TurboFilter,只需要實現 decide() 方法就可以了。下一個例子,我們會建立一個稍微複雜一點的過濾器:

Example: SampleTurboFilter.java

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(); 
    }
  }
}

TurboFilter 接受一個指定的 marker,如果 marker 沒有被找到,那麼過濾器會將日誌事件傳遞給過濾器鏈中的下一個過濾器。

為了更加靈活,允許在配置檔案指定 marker 用於檢測,因此可以使用 get 和 set 方法。我們還可以通過實現 start() 方法來檢查在配置過程中,指定的選項是否滿足。

下面的配置充分利用了我們新建立的 TurboFilter

Example: sampleTurboFilterConfig.xml

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

loback-classic 附帶了幾個 TurboFilter 類可以開箱即用。MDCFilter 用來檢查給定的值在 MDC 中是否存在。DynamicThresholdFilter 根據 MDC key/level 相關的閥值來進行過濾。MarkerFilter 用來檢查日誌請求中指定的 marker 是否存在。

下面的例子使用了 MDCFilterMarkerFilter

Example: turboFilters.xml

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

執行以下命令:

java chapters.filters.FilterEvents src/main/java/chapters/filters/turboFilters.xml

在之前我們看到 FilterEvents 輸出了 10 條日誌請求,編號 0 到 9。除了第 3 條與第 6 條,所有的請求都是 INFO 級別的,與 root logger 的級別一致。第 3 條日誌請求是 DEBUG 級別的,在有效級別之下。但是,因為 MDC 的 key "username" 在第三條請求之前設定為 "sebastien",之後才被移除,所以 MDCFilter 接受這條請求 (僅僅只有這條請求)。第 6 條請求的級別為 ERROR,被標記為 "billing"。因此,它會被 MarkerFilter (配置檔案中第二個 turbo 過濾器) 拒絕。

因此,FilterEvents 通過 turboFilters.xml 輸出的資訊如下:

2018-08-20 23:19:28,807 [main] INFO  chapters.filters.FilterEvents - logging statement 0
2018-08-20 23:19:28,810 [main] INFO  chapters.filters.FilterEvents - logging statement 1
2018-08-20 23:19:28,810 [main] INFO  chapters.filters.FilterEvents - logging statement 2
2018-08-20 23:19:28,810 [main] DEBUG chapters.filters.FilterEvents - logging statement 3
2018-08-20 23:19:28,810 [main] INFO  chapters.filters.FilterEvents - logging statement 4
2018-08-20 23:19:28,810 [main] INFO  chapters.filters.FilterEvents - logging statement 5
2018-08-20 23:19:28,810 [main] INFO  chapters.filters.FilterEvents - logging statement 7
2018-08-20 23:19:28,811 [main] INFO  chapters.filters.FilterEvents - logging statement 8
2018-08-20 23:19:28,811 [main] INFO  chapters.filters.FilterEvents - logging statement 9

可以看到,第 3 條日誌請求,本來不應該被展示出來,因為我們僅僅只關注 INFO 級別的請求,但是它匹配了第一個 TurboFilter,所以被接受了。

第 6 條日誌請求,它是 ERROR 級別的日誌,應該被顯示。但是因為滿足第二個 TurboFilter,它的 OnMatch 設定為 DENY,所以第 6 條請求不會被展示。

DuplicateMessageFilter

DuplicateMessageFilter 可以拿出來單獨闡述。這個過濾器檢測重複的訊息,在重複了一定次數之後,丟棄掉重複的訊息。

這個過濾器使用字串是否相等來檢查是否重複。不會檢查非常相似,僅僅只差幾個字元的字串。例如:

logger.debug("Hello "+name0);
logger.debug("Hello "+name1);

如果 name0name1 有不同的值,那麼兩個 "Hello" 訊息會被認為不相關。根據使用者的需要,將會可能會支援相似字串的檢查,限制相似字串的重複,而不是完全相同的。

但是在引數化日誌請求中,只考慮原始訊息。例如,下面兩條日誌請求,原始訊息為 "Hello {}",它們被認為是想相等的,因此被認為是重複出現。

logger.debug("Hello {}.", name0);
logger.debug("Hello {}.", name1);

可以通過 AllowedRepetitions 屬性來指定允許重複的次數。如果這個屬性被設定為 1,那麼第二條以及後續的日誌訊息都會被丟棄掉。類似的,如果被設定為 2,那麼第三條及後續的日誌訊息會被丟棄掉。這個值預設設定為 5。

為了檢測重複,過濾器需要在內部的快取中保留對舊訊息的引用。通過 CacheSize 來控制快取的大小。預設情況下,這個值為 100。

Example: duplicateMessage.xml

<configuration>

  <turboFilter class="ch.qos.logback.classic.turbo.DuplicateMessageFilter"/>

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

FilterEvents 通過 duplicateMessage.xml 配置後輸出如下:

2018-08-21 09:09:22,036 [main] INFO  chapters.filters.FilterEvents - logging statement 0
2018-08-21 09:09:22,041 [main] INFO  chapters.filters.FilterEvents - logging statement 1
2018-08-21 09:09:22,041 [main] INFO  chapters.filters.FilterEvents - logging statement 2
2018-08-21 09:09:22,041 [main] INFO  chapters.filters.FilterEvents - logging statement 4
2018-08-21 09:09:22,041 [main] INFO  chapters.filters.FilterEvents - logging statement 5
2018-08-21 09:09:22,050 [main] ERROR chapters.filters.FilterEvents - billing statement 6

"logging statement 0" 是訊息 "logging statement {}"j 第一次出現。"logging statement 1" 是第一次重複。"logging statement 2" 是第二次重複。有趣的是,雖然 "logging statement 3" 的級別為 DEBUG,為第三次重複。但是根據方法列印以及基本選擇規則,它被丟棄了。這也說明了 turbo 過濾器會在其它過濾器之前呼叫,包括在基本選擇規則之前。因此 DuplicateMessageFilter 認為 "logging statement 3" 是第三次重複,而不會管它是否會在之後過濾器鏈的處理中被丟棄掉。"logging statement 4" 是第四次重複。"logging statement 5" 是第五次。因此預設的重複次數是 5,所以之後的語句都會被丟棄掉。(注:指的是 "logging statement {}")。

在 logback-access 中

logback-access 提供了 logback-classic 提供的大部分功能。特別地,Filter 物件同樣是有效的,並且以同樣的方式工作,就像 logback-classic 的副本一樣,但是有一個顯著的區別。logback-access 過濾器對 AccessEvent 例項起作用,而不是 LoggingEvent 例項。目前,logback-access 只提供了以下有限的過濾器。如果你想建議新增額外的過濾器,請通過 logback-dev 郵件列表進行聯絡。

CountingFilter

CountingFilter 類的幫助下,logback-access 可以提供對伺服器訪問資料的統計。在初始化的死後,CountingFilter 將自己作為一個 MBean 註冊到平臺的 JMX 服務上。你可以通過輪詢 MBean 來進行資料統計。例如,平均每分鐘,每小時,每天,每週,或者每月。其它的統計,例如周計,天計,小時計,月計或者總計也是可以獲取的。

下面的 logback-access.xml 配置檔案宣告瞭一個 CountingFilter

<configuration>
  <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />

  <filter class="ch.qos.logback.access.filter.CountingFilter">
    <name>countingFilter</name>
  </filter>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%h %l %u %t \"%r\" %s %b</pattern>
    </encoder>
  </appender>

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

你可以通過 jconsole 檢視有 CountingFilte 在你平臺的 JMX 服務上維護的各種統計資訊。

[圖片上傳失敗...(image-9bfd4b-1547968754978)]

EvaluatorFilter

EvaluatorFilter 是一個通用的過濾器,維護了一個 EventEvaluator。顧名思義,EventEvaluator 根據給定的標準判斷給定的日誌事件是否滿足,EvaluatorFilter 將會根據 match 與 mismatch 的情況,返回由 onMatchonMismatch 屬性指定的值。EvaluatorFilter 在之前的 logback-classic 中已經討論過了 (見上面)。現在大部分都是對之前討論的重複。

注意 EventEvaluator 是一個抽象類。你可以通過繼承 EventEvaluator 來實現你自己的評估邏輯。logback-access 附帶了一個名為 JaninoEventEvaluator 的具體實現。它可以接收任意的 Java 表示式作為評估標準。我們把這種 Java 程式碼塊稱為 "評估表示式"。評估表示式在事件過濾中有較大的靈活性。JaninoEventEvaluator 需要 Janino 類庫。請檢視相應的文件進行設定。

評估表示式在解析配置檔案的過程中被動態編譯。作為使用者,你不需要知道實際的細節。但是,你需要保證 Java 表示式返回一個布林值,能夠計算為 true 或者 false。

評估表示式可以對當前訪問的事件進行評估。logback-access 會自動匯出當前 AccessEvent 例項到變數 event 下。你可以通過 event 變數讀取 HTTP 請求中以及 HTTP 響應中的各種資料。檢視 AccessEvent 類的原始碼來檢視具體的列表。

下個配置檔案基於 HTTP 響應碼 404 (Not Found) 來進行過濾。每一個 404 的請求都會在控制檯列印出來。

Example: accessEventEvaluator.xml

<configuration>
  <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <filter class="ch.qos.logback.core.filter.EvaluatorFilter">
      <evaluator>
        <expression>event.getStatusCode() == 404</expression>
      </evaluator>
      <onMismatch>DENY</onMismatch>
    </filter>
   <encoder><pattern>%h %l %u %t %r %s %b</pattern></encoder>
  </appender>

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

下面的例子,列印 404 錯誤,但是排除了請求 CSS 檔案的請求。

Example: accessEventEvaluator2.xml

<configuration>
  <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <filter class="ch.qos.logback.core.filter.EvaluatorFilter">
      <evaluator name="Eval404">
        <expression>
         (event.getStatusCode() == 404)
           &amp;&amp;  <!-- & 符號需要被轉義 -->
         !(event.getRequestURI().contains(".css"))
        </expression>
      </evaluator>
      <onMismatch>DENY</onMismatch>
    </filter>

   <encoder><pattern>%h %l %u %t %r %s %b</pattern></encoder>
  </appender>

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

相關文章