logback官方文檔中文翻譯第六章:Layouts

第六章:Layouts

layout 是 logback 的組件,負(fù)責(zé)將日志事件轉(zhuǎn)換為字符串。Layout 接口中的 format() 方法接受一個(gè)表示日志事件的對(duì)象 (任何類型) 并返回一個(gè)字符串。Layout 接口的概要如下:

public interface Layout<E> extends ContextAware, LifeCycle {

  String doLayout(E event);
  String getFileHeader();
  String getPresentationHeader();
  String getFileFooter();
  String getPresentationFooter();
  String getContentType();
}

這個(gè)接口相對(duì)簡(jiǎn)單,但是它可以滿足大部分的格式化需求。

Logback-classic

logback-classic 僅僅用來(lái)處理 ch.qos.logback.classic.spi.ILoggingEvent 類型的日志事件。我們將在這個(gè)部分說(shuō)明這個(gè)事實(shí)。

定制 Layout

讓我們?yōu)?logback-classic 模塊實(shí)現(xiàn)一個(gè)簡(jiǎn)單但是實(shí)用的功能,打印應(yīng)用啟動(dòng)所耗費(fèi)的時(shí)間,日志事件的級(jí)別,被綜括號(hào)包裹的調(diào)用者線程,logger 名,破折號(hào)后面跟日志信息,以及新起一行。

類似下面的輸出:

10489 DEBUG [main] com.marsupial.Pouch - Hello world.

下面是一種可能的實(shí)現(xiàn):

Example: MySampleLayout.java

package chapters.layouts;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.LayoutBase;

public class MySampleLayout extends LayoutBase<ILoggingEvent> {

  public String doLayout(ILoggingEvent event) {
    StringBuffer sbuf = new StringBuffer(128);
    sbuf.append(event.getTimeStamp() - event.getLoggingContextVO.getBirthTime());
    sbuf.append(" ");
    sbuf.append(event.getLevel());
    sbuf.append(" [");
    sbuf.append(event.getThreadName());
    sbuf.append("] ");
    sbuf.append(event.getLoggerName();
    sbuf.append(" - ");
    sbuf.append(event.getFormattedMessage());
    sbuf.append(CoreConstants.LINE_SEP);
    return sbuf.toString();
  }
}

MySampleLayout 繼承自 LayoutBase。這個(gè)類管理所有 layout 實(shí)例的狀態(tài)信息,例如:layout 是否啟動(dòng)或者停止,頭部,尾部以及內(nèi)容類型數(shù)據(jù)。它讓開(kāi)發(fā)者通過(guò)自己 Layout 集中在日志具體的格式化上。LayoutBase 類是通用的。在它的類聲明上,MySampleLayout 繼承 LayoutBase<ILoggingEvent>。

在上面這個(gè)例子中,doLayout 方法忽略了日志事件中任何可能的異常。在實(shí)際應(yīng)用中,你可能需要打印異常信息。

配置自定義的 layout

配置自定義的 layout 跟其它的組件一樣的配置。根據(jù)之前提到的,FileAppender 及其子類期望一個(gè) encoder。為了去滿足這個(gè)需求,我們將一個(gè)包裹了我們自己定義的 MySampleLayoutLayoutWrappingEncoder 的實(shí)例傳遞給 FileAppender。下面是配置示例:

Example: sampleLayoutConfig.xml

<configuration>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
      <layout class="chapters.layouts.MySampleLayout" />
    </encoder>
  </appender>

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

chapters.layouts.SampleLogging 這個(gè)簡(jiǎn)單的應(yīng)用通過(guò)第一個(gè)參數(shù)接收配置文件,然后打印了一個(gè) debug 信息,接著打印了 error 信息。

logback-examples 文件夾下通過(guò)以下命令來(lái)運(yùn)行:

java chapters.layouts.SampleLogging src/main/java/chapters/layouts/sampleLayoutConfig.xml

將會(huì)輸出:

0 DEBUG [main] chapters.layouts.SampleLogging - Everything's going well
0 ERROR [main] chapters.layouts.SampleLogging - maybe not quite...

這種足夠簡(jiǎn)單。讀者應(yīng)該會(huì)發(fā)現(xiàn),在 MySampleLayout2.java 中,我們自定義的 layout 做了一點(diǎn)點(diǎn)的修改。正如本手冊(cè)一直提到的,為 layout 或者其它 logback 的組件添加一個(gè)屬性,跟為這個(gè)屬性添加一個(gè) set 方法一樣簡(jiǎn)單。

MySampleLayout2 類包含了兩個(gè)屬性。第一個(gè)是可以將一個(gè)前綴添加到輸出的日志中。第二個(gè)屬性可以用來(lái)選擇是否展示發(fā)送日志請(qǐng)求的線程名。

下面是 MySampleLayout2 類:

package chapters.layouts;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.LayoutBase;

public class MySampleLayout2 extends LayoutBase<ILoggingEvent> {

  String prefix = null;
  boolean printThreadName = true;

  public void setPrefix(String prefix) {
    this.prefix = prefix;
  }

  public void setPrintThreadName(boolean printThreadName) {
    this.printThreadName = printThreadName;
  }

  public String doLayout(ILoggingEvent event) {
    StringBuffer sbuf = new StringBuffer(128);
    if (prefix != null) {
      sbuf.append(prefix + ": ");
    }
    sbuf.append(event.getTimeStamp() - event.getLoggerContextVO().getBirthTime());
    sbuf.append(" ");
    sbuf.append(event.getLevel());
    if (printThreadName) {
      sbuf.append(" [");
      sbuf.append(event.getThreadName());
      sbuf.append("] ");
    } else {
      sbuf.append(" ");
    }
    sbuf.append(event.getLoggerName());
    sbuf.append(" - ");
    sbuf.append(event.getFormattedMessage());
    sbuf.append(LINE_SEP);
    return sbuf.toString();
  }
}

添加相應(yīng)的 set 方法就可以開(kāi)啟屬性的配置。PrintThreadName 屬性是 boolean 而不是 String 類型。關(guān)于配置 logback 的詳細(xì)信息請(qǐng)參見(jiàn)第三章:logback 的配置第十一章將會(huì)提供更詳細(xì)的內(nèi)容。下面是關(guān)于 MySampleLayout2 的相關(guān)配置:

<configuration>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
      <layout class="chapters.layouts.MySampleLayout2"> 
        <prefix>MyPrefix</prefix>
        <printThreadName>false</printThreadName>
      </layout>
    </encoder>
  </appender>

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

PatternLayout

logback 配備了一個(gè)更加靈活的 layout 叫做 PatternLayout。跟所有的 layout 一樣,PatternLayout 接收一個(gè)日志事件并返回一個(gè)字符串。但是,可以通過(guò)調(diào)整 PatternLayout 的轉(zhuǎn)換模式來(lái)進(jìn)行定制。

PatternLayout 中的轉(zhuǎn)換模式與 C 語(yǔ)言中 printf() 方法中的轉(zhuǎn)換模式密切相關(guān)。轉(zhuǎn)換模式由字面量與格式控制表達(dá)式也叫轉(zhuǎn)換說(shuō)明符組成。你可以在轉(zhuǎn)換模式中自由的插入字面量。每一個(gè)轉(zhuǎn)換說(shuō)明符由一個(gè)百分號(hào)開(kāi)始 '%',后面跟隨可選的格式修改器,以及用綜括號(hào)括起來(lái)的轉(zhuǎn)換字符與可選的參數(shù)。轉(zhuǎn)換字符需要轉(zhuǎn)換的字段。如:logger 的名字,日志級(jí)別,日期以及線程名。格式修改器控制字段的寬度,間距以及左右對(duì)齊。

正如我們已經(jīng)在其它地方提到過(guò)的,FileAppender 及其子類需要一個(gè) encoder。因?yàn)?,?dāng)將 FileAppender 及其子類與 PatternLayout 結(jié)合使用時(shí),PatternLayout 必須用 encoder 包裹起來(lái)。鑒于 FileAppender/PatternLayout 結(jié)合使用很常見(jiàn),因此 logback 單獨(dú)設(shè)計(jì)了一個(gè)名叫 PatternLayoutEncoder 的 encoder,包裹了一個(gè) PatternLayout,因此它可以被當(dāng)作一個(gè) encoder。下面是通過(guò)代碼配置 ConsoleAppenderPatternLayoutEncoder 使用的例子:

Example: PatternSample.java

package chapters.layouts;

import org.slf4j.LoggerFactory;

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.ConsoleAppender;

public class PatternSample {

  static public void main(String[] args) throws Exception {
    Logger rootLogger = (Logger)LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
    LoggerContext loggerContext = rootLogger.getLoggerContext();
    // we are not interested in auto-configuration
    loggerContext.reset();

    PatternLayoutEncoder encoder = new PatternLayoutEncoder();
    encoder.setContext(loggerContext);
    encoder.setPattern("%-5level [%thread]: %message%n");
    encoder.start();

    ConsoleAppender<ILoggingEvent> appender = new ConsoleAppender<ILoggingEvent>();
    appender.setContext(loggerContext);
    appender.setEncoder(encoder); 
    appender.start();

    rootLogger.addAppender(appender);

    rootLogger.debug("Message 1"); 
    rootLogger.warn("Message 2");
  } 
}

在上面這個(gè)例子中,轉(zhuǎn)換模式被設(shè)置為 "%-5level [%thread]: %message%n ",關(guān)于 logback 中簡(jiǎn)短的轉(zhuǎn)換字符將會(huì)很快給出。運(yùn)行 PatternSample

java java chapters.layouts.PatternSample

將會(huì)輸出如下信息:

DEBUG [main]: Message 1 
WARN  [main]: Message 2

在轉(zhuǎn)換模式 "%-5level [%thread]: %message%n" 中,字面量與轉(zhuǎn)換說(shuō)明符之間沒(méi)有明顯的分隔符。當(dāng)對(duì)轉(zhuǎn)換模式進(jìn)行解析的時(shí)候,PatternLayout 有能力對(duì)字面量 (空格符,方括號(hào),冒號(hào)) 和 轉(zhuǎn)換說(shuō)明符進(jìn)行區(qū)分。在上面的例子中,轉(zhuǎn)換說(shuō)明符 %-5level 表示日志事件的級(jí)別的字符應(yīng)該向左對(duì)齊,保持五個(gè)字符的寬度。具體的轉(zhuǎn)換格式將會(huì)在下面介紹。

PatternLayout 中,括號(hào)用于對(duì)轉(zhuǎn)換模式進(jìn)行分組。'(' 與 ')' 有特殊的含義,因此如果想用作字面量,需要進(jìn)行特殊的轉(zhuǎn)義。圓括號(hào)的特殊含義將在下面 進(jìn)行詳細(xì)的介紹。

之前提到過(guò),特定的轉(zhuǎn)換模式可以通過(guò)花括號(hào)指定可選的參數(shù)。一個(gè)簡(jiǎn)單的可選轉(zhuǎn)換模式可以是 %logger{10}。在這里 "logger" 就是轉(zhuǎn)換字符,10 就是可選參數(shù)??蛇x參將在下面詳細(xì)介紹。

轉(zhuǎn)換字符與它們的可選參數(shù)在下面的表格中進(jìn)行詳細(xì)敘述。當(dāng)多個(gè)轉(zhuǎn)換字符在同一個(gè)單元格中被列出來(lái),它們被當(dāng)作別名來(lái)考慮。

轉(zhuǎn)換字符 效果
c{length}<br />lo{length}<br />logger{length} 輸出 logger 的名字作為日志事件的來(lái)源。轉(zhuǎn)換字符接收一個(gè)作為它的第一個(gè)也是為一個(gè)參數(shù)。轉(zhuǎn)換器的簡(jiǎn)寫(xiě)算法將會(huì)縮短 logger 的名字,但是通過(guò)不會(huì)丟失重要的信息。設(shè)置 length 的值為 0 是一個(gè)例外。它將會(huì)導(dǎo)致轉(zhuǎn)換字符返回 logger 名字中最右邊的點(diǎn)右邊的字符。下面的表格提供了一個(gè)示例:<br /><table><tr><th>轉(zhuǎn)換說(shuō)明符</th><th>logger的名字</th><th>結(jié)果</th></tr><tr><td>%logger</td><td>mainPackage.sub.sample.Bar</td><td>mainPackage.sub.sample.Bar</td></tr><tr><td>%logger{0}</td><td>mainPackage.sub.sample.Bar</td><td>Bar</td></tr><tr><td>%logger{5}</td><td>mainPackage.sub.sample.Bar</td><td>m.s.s.Bar</td></tr><tr><td>%logger{10}</td><td>mainPackage.sub.sample.Bar</td><td>m.s.s.Bar</td></tr><tr><td>%logger{15}</td><td>mainPackage.sub.sample.Bar</td><td>m.s.sample.Bar</td></tr><tr><td>%logger{16}</td><td>mainPackage.sub.sample.Bar</td><td>m.sub.sample.Bar</td></tr><tr><td>%logger{26}</td><td>mainPackage.sub.sample.Bar</td><td>mainPackage.sub.sample.Bar</td></tr></table>logger 名字最右邊的部分永遠(yuǎn)不會(huì)被簡(jiǎn)寫(xiě),即使它的長(zhǎng)度比 length 的值要大。其它的部分可能會(huì)被縮短為一個(gè)字符,但是永不會(huì)被移除。<br />
C{length} <br />class{length} 輸出發(fā)出日志請(qǐng)求的類的全限定名稱。<br />跟 %logger% 轉(zhuǎn)換符一樣,它也可以接收一個(gè)整型的可選參數(shù)去縮短類名。0 表示特殊含義,在打印類名時(shí)將不會(huì)輸出包的前綴名。默認(rèn)表示打印類的全限定名。<br />生成調(diào)用者類的信息并不是特別快。因此,應(yīng)該避免使用,除非執(zhí)行速度不是問(wèn)題。
contextName<br />cn 輸出日志事件附加到的 logger 上下文的名字。
d{pattern} date{pattern} d{pattern, timezone} date{pattern, timezone} 用于輸出日志事件的日期。日期轉(zhuǎn)換符允許接收一個(gè)字符串作為參數(shù)。字符串的語(yǔ)法與 SimpleDateFormat 中的格式完全兼容。<br />你可以指定 "ISO8601" 來(lái)表示將日期格式為 ISO8601 類型。如果沒(méi)有指定日期格式,那么 %date 轉(zhuǎn)換字符默認(rèn)為 ISO860 類型。<br />這里有一個(gè)例子。它假設(shè)當(dāng)前時(shí)間為 2006.10.20 星期五,作者剛剛吃完飯準(zhǔn)備寫(xiě)這篇文檔。<br /><table><tr><th>轉(zhuǎn)換模式</th><th>結(jié)果</th></tr><tr><td>%d</td><td>2006-10-20 14:06:49,812</td></tr><tr><td>%date</td><td>2006-10-20 14:06:49,812</td></tr><tr><td>%date{ISO8601}</td><td>2006-10-20 14:06:49,812</td></tr><tr><td>%date{HH:mm:ss.SSS}</td><td>14:06:49.812</td></tr><tr><td>%date{dd MMM yyyy;HH:mm:ss.SSS}</td><td>20 oct. 2006;14:06:49.812</td></tr></table><br />第二個(gè)參數(shù)用于指定時(shí)區(qū)。例如, '%date{HH:mm:ss.SSS, Australia/Perth}' 將會(huì)打印世界上最孤立的城市,澳大利亞佩斯所在時(shí)區(qū)的日期。如果沒(méi)有指定時(shí)區(qū)參數(shù),則默認(rèn)使用 Java 平臺(tái)所在主機(jī)的時(shí)區(qū)。如果指定的時(shí)區(qū)不能識(shí)別或者拼寫(xiě)錯(cuò)誤,則 TimeZone.getTimeZone(String) 方法會(huì)指定時(shí)區(qū)為 GMT。<br />常見(jiàn)錯(cuò)誤: 對(duì)于 HH:mm:ss,SSS 模式,逗號(hào)會(huì)被解析為分隔符,所以最終會(huì)被解析為 HH:mm:ss,SSS 會(huì)被當(dāng)作時(shí)區(qū)。如果你想在日期模式中使用逗號(hào),那么你可以這樣使用,%date{"HH:mm:ss,SSS"} 用雙引號(hào)將日期模式包裹起來(lái)。
F / file 輸出發(fā)出日志請(qǐng)求的 Java 源文件名。<br />由于生成文件的信息不是特別快,因此,應(yīng)該避免使用,除非速度不是問(wèn)題。
caller{depth}<br />caller{depthStart..depthEnd}<br />caller{depth, evaluator-1, ... evaluator-n}<br />caller{depthStart..depthEnd, evaluator-1, ... evaluator-n} 輸出生成日志的調(diào)用者所在的位置信息。<br />位置信息依賴 JVM 的實(shí)現(xiàn),但是通常由調(diào)用方法的全限定名以及調(diào)用者的來(lái)源組成。以及由圓括號(hào)括起來(lái)的文件名與行號(hào)。<br />caller 轉(zhuǎn)換符還可以接收一個(gè)整形的參數(shù),用來(lái)配置展示信息的深度。<br />例如,%caller{2} 會(huì)展示如下的信息:<br /><pre style="background-color:#eaecef">0 [main] DEBUG - logging statement<br />Caller+0 at mainPackage.sub.sample.Bar.sampleMethodName(Bar.java:22)<br />Caller+1 at mainPackage.sub.sample.Bar.createLoggingRequest(Bar.java:17)</pre> %caller{3} 會(huì)展示如下信息:<br /><pre><span style="background-color:#eaecef">16 [main] DEBUG - logging statement <br />Caller+0 at mainPackage.sub.sample.Bar.sampleMethodName(Bar.java:22) <br />Caller+1 at mainPackage.sub.sample.Bar.createLoggingRequest(Bar.java:17) <br />Caller+2 at mainPackage.ConfigTester.main(ConfigTester.java:38) </span></pre><br />caller 轉(zhuǎn)換符還可以接收一個(gè)范圍用來(lái)展示深度在這個(gè)范圍內(nèi)的信息。<br />例如,%caller{1..2} 會(huì)展示如下信息:<br /><pre>[main] DEBUG - logging statement <br />Caller+0 at mainPackage.sub.sample.Bar.createLoggingRequest(Bar.java:17)</pre><br />轉(zhuǎn)換字符還可以接收一個(gè) evaluator,在計(jì)算調(diào)用者數(shù)據(jù)之前通過(guò)指定的標(biāo)準(zhǔn)對(duì)日志事件進(jìn)行測(cè)驗(yàn)。例如,%caller{3, CALLER_DISPLAY_EVAL} 會(huì)在 CALLER_DISPLAY_EVAL 返回一個(gè)肯定的答案,才會(huì)顯示三行堆棧信息。<br />將在下面詳細(xì)敘述 evaluator。
L / line 輸出發(fā)出日志請(qǐng)求所在的行號(hào)。<br />生成行號(hào)不是特別快。因此,不建議使用,除非生成速度不是問(wèn)題。
m / msg / message 輸出與日志事件相關(guān)聯(lián)的,由應(yīng)用程序提供的日志信息。
M / method 輸出發(fā)出日志請(qǐng)求的方法名。<br />生成方法名不是特別快,因此,應(yīng)該避免使用,除非生成速度不是問(wèn)題。
n 輸出平臺(tái)所依賴的行分割字符。<br />轉(zhuǎn)換字符提供了像 "\n" 或 "\r\n" 一樣的轉(zhuǎn)換效果。因此指定行分隔符它是首選的指定方式。
p / le / level 輸出日志事件的級(jí)別。
r / relative 輸出應(yīng)用程序啟動(dòng)到創(chuàng)建日志事件所花費(fèi)的毫秒數(shù)
t / thread 輸出生成日志事件的線程名。
X{key:-defaultVal}<br /> mdc{key:-defaultVal} 輸出生成日志事件的線程的 MDC (mapped diagnostic context)。<br />如果 MDC 轉(zhuǎn)換字符后面跟著用花括號(hào)括起來(lái)的 kye,例 %MDC{userid},那么 'userid' 所對(duì)應(yīng) MDC 的值將會(huì)輸出。如果該值為 null,那么通過(guò) :- 指定的默認(rèn)值 將會(huì)輸出。如果沒(méi)有指定默認(rèn)值,那么將會(huì)輸出空字符串。<br />如果沒(méi)有指定的 key,那么 MDC 的整個(gè)內(nèi)容將會(huì)以 "key1=val1, key2=val2" 的格式輸出。<br />查詳情請(qǐng)見(jiàn) 第八章
ex{depth} exception{depth} throwable{depth} ex{depth, evaluator-1, ..., evaluator-n} exception{depth, evaluator-1, ..., evaluator-n} throwable{depth, evaluator-1, ..., evaluator-n} 輸出日志事件相關(guān)的堆棧信息,默認(rèn)情況下會(huì)輸出全部的堆棧信息。<br /> throwable 轉(zhuǎn)換詞可以接收如下的參數(shù):<br /><ul><li>short:輸出堆棧信息的第一行
</li><li>full:輸出全部的堆棧信息</li><li>任意整數(shù):輸出指定行數(shù)的堆棧信息</li></ul><br />下面是一些示例:<br /><table><thead><th>轉(zhuǎn)換模式</th><th>結(jié)果</th></thead><tbody><tr><td>%ex</td><td><pre>mainPackage.foo.bar.TestException: Houston we have a problem<br /> at mainPackage.foo.bar.TestThrower.fire(TestThrower.java:22)<br /> at mainPackage.foo.bar.TestThrower.readyToLaunch(TestThrower.java:17)<br /> at mainPackage.ExceptionLauncher.main(ExceptionLauncher.java:38)</pre></td></tr><tr><td>%ex{short}</td><td><pre><br />mainPackage.foo.bar.TestException: Houston we have a problem<br /> at mainPackage.foo.bar.TestThrower.fire(TestThrower.java:22)</pre></td></tr><tr><td>%ex{full}</td><td><pre><br />mainPackage.foo.bar.TestException: Houston we have a problem<br /> at mainPackage.foo.bar.TestThrower.fire(TestThrower.java:22)<br /> at mainPackage.foo.bar.TestThrower.readyToLaunch(TestThrower.java:17)<br /> at mainPackage.ExceptionLauncher.main(ExceptionLauncher.java:38)</pre></td></tr><tr><td>%ex{2}</td><td><pre>mainPackage.foo.bar.TestException: Houston we have a problem<br /> at mainPackage.foo.bar.TestThrower.fire(TestThrower.java:22)<br /> at mainPackage.foo.bar.TestThrower.readyToLaunch(TestThrower.java:17)</pre></td></tr></tbody></table><br />在輸出前,轉(zhuǎn)換字符還可以使用給定的標(biāo)準(zhǔn)再次檢驗(yàn)日志事件。例如,使用 %ex{full, EX_DISPLAY_EVAL},只有 EX_DISPLAY_EVAL 返回一個(gè)否定的答案,才會(huì)輸出全部的堆棧信息。evaluator 在接下來(lái)的文檔中將會(huì)進(jìn)一步敘述。<br />如果你沒(méi)有指定 %throwable 或者其它跟 throwable 相關(guān)的轉(zhuǎn)換字符,那么 PatternLayout 會(huì)在最后一個(gè)轉(zhuǎn)換字符加上這個(gè)。因?yàn)槎褩P畔⒎浅5闹匾?。如果你不想展示堆棧信息,那么可以使?%nopex (作者原文為 $nopex) 可以替代 %throwable。詳情見(jiàn) %nopex。
<a style="text-decoration:none" name="xThrowable" href="#xThrowable">xEx{depth}</a> <br />xException{depth} xThrowable{depth} <br />xEx{depth, evaluator-1, ..., evaluator-n} <br />xException{depth, evaluator-1, ..., evaluator-n} <br />xThrowable{depth, evaluator-1, ..., evaluator-n} 跟 %throwable 類似,只不過(guò)多了類的包信息。<br />在每個(gè)堆棧信息的末尾,多了包含 jar 文件的字符串,后面再加上具體的實(shí)現(xiàn)版本。這項(xiàng)創(chuàng)造性的技術(shù)是來(lái)自 James Strachan 的建議。如果該信息不確定,那么類的包信息前面會(huì)有一個(gè)波浪號(hào) (~)。<br />下面是一個(gè)例子:<br /><pre>java.lang.NullPointerException<br /> at com.xyz.Wombat(Wombat.java:57) ~[wombat-1.3.jar:1.3]<br /> at com.xyz.Wombat(Wombat.java:76) ~[wombat-1.3.jar:1.3]<br /> at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.5.0_06]<br /> at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) ~[na:1.5.0_06]<br /> at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) ~[na:1.5.0_06]<br /> at java.lang.reflect.Method.invoke(Method.java:585) ~[na:1.5.0_06]<br /> at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:59) [junit-4.4.jar:na]<br /> at org.junit.internal.runners.MethodRoadie.runTestMethod(MethodRoadie.java:98) [junit-4.4.jar:na]<br /> ...etc </pre>logback 努力的去確保類的包信息正確的展示,即使是在復(fù)雜的類加載層次中。但是,一個(gè)不能保證信息的絕對(duì)正確,那么在這些數(shù)據(jù)的前面將會(huì)多一個(gè)波浪符 (~)。因此,從理論上來(lái)說(shuō),打印的類的包信息跟真實(shí)的類的包信息是有區(qū)別的。在上面的例子中,類 Wombat 的包信息前面有一個(gè)波浪符,在實(shí)際的情況中,它真實(shí)包可能為 [wombat.jar:1.7]。<br />但是請(qǐng)注意潛在的性能損耗,計(jì)算包信息默認(rèn)是禁止的。當(dāng)啟用了計(jì)算包信息,那么 PatternLayout 將會(huì)自動(dòng)認(rèn)為在字符串模式的末尾 %xThrowable 替代了 %throwable。<br />根據(jù)用戶的反饋,Netbeans 會(huì)阻止包信息的打印。
nopex<br />nopexception 這個(gè)轉(zhuǎn)換字符不會(huì)輸出任何數(shù)據(jù),因此,它可以用來(lái)有效忽略異常信息。<br />%nopex 轉(zhuǎn)換字符允許用戶重寫(xiě) PatternLayout 內(nèi)部的安全機(jī)制,該機(jī)制將會(huì)在沒(méi)有指定其它處理異常的轉(zhuǎn)換字符時(shí),默認(rèn)添加 %xThrowable。
marker 輸出與日志請(qǐng)求相關(guān)的標(biāo)簽。<br />一旦標(biāo)簽包含子標(biāo)簽,那么轉(zhuǎn)換器將會(huì)根據(jù)下面的格式展示父標(biāo)簽與子標(biāo)簽。<br />parentName [child1, child2]
property{key} 輸出屬性 key 所對(duì)應(yīng)的值。相關(guān)定義參見(jiàn) 定義變量 以及作用域。如果 key 在 logger context 中沒(méi)有找到,那么將會(huì)去系統(tǒng)屬性中找。<br />key 沒(méi)有默認(rèn)值,如果缺失,則會(huì)展示 " Property_HAS_NO_KEY" 的錯(cuò)誤信息。
<a style="text-decoration:none" name="replace" href="#replace">replace(p){r, t}</a> 在子模式 'p' 產(chǎn)生的字符中,將所有出現(xiàn)正則表達(dá)式 'r' 的地方替換為 't'。例如,"%replace(%msg){'\s', ''}" 將會(huì)移除事件消息中所有空格。<br />模式 'p' 可以是任意復(fù)雜的甚至由多個(gè)轉(zhuǎn)換字符組成。例如,"%replace(%logger %msg){'.', '/'}" 將會(huì)替換 logger 以及消息中所有的點(diǎn)為斜桿。
rEx{depth} rootException{depth} rEx{depth, evaluator-1, ..., evaluator-n} rootException{depth, evaluator-1, ..., evaluator-n} 輸出與日志事件相關(guān)的堆棧信息,根異常將會(huì)首先輸出,而是標(biāo)準(zhǔn)的"根異常最后輸出"。下面是一個(gè)輸出例子:<br /><pre>java.lang.NullPointerException<br /> at com.xyz.Wombat(Wombat.java:57) ~[wombat-1.3.jar:1.3]<br /> at com.xyz.Wombat(Wombat.java:76) ~[wombat-1.3.jar:1.3]<br />Wrapped by: org.springframework.BeanCreationException: Error creating bean with name 'wombat': <br /> at org.springframework.AbstractBeanFactory.getBean(AbstractBeanFactory.java:248) [spring-2.0.jar:2.0]<br /> at org.springframework.AbstractBeanFactory.getBean(AbstractBeanFactory.java:170) [spring-2.0.jar:2.0]<br /> at org.apache.catalina.StandardContext.listenerStart(StandardContext.java:3934) [tomcat-6.0.26.jar:6.0.26]</pre>%rootException 跟 %xException 類似,也允許一些可選的參數(shù),包括深度以及 evaluator。它也會(huì)輸出包信息。簡(jiǎn)單來(lái)說(shuō),%rootException 跟 %xException 非常的類似,僅僅是異常輸出的順序完全相反。<br /> %rootException 的作者 Tomasz Nurkiewicz 在他的博客說(shuō)明了他所作的貢獻(xiàn) "Logging exceptions root cause first"。

% 有特殊的含義

在給定的轉(zhuǎn)換模式上下文中,% 有特殊的含義。如果作為字面量,需要進(jìn)行轉(zhuǎn)義。例如,"%d %p % %m%n"。

轉(zhuǎn)換字符對(duì)字面量的限制

在大多數(shù)的情況下,字面量包括空格或者其它的分隔符,所以它們不會(huì)與轉(zhuǎn)換字符混淆。例如,"%level [%thread] - %message%n" 包含字面量字符 " [" 與 "] - "。但是,如果一個(gè)轉(zhuǎn)換字符后面緊跟著一個(gè)字面量,那么 logback 的模式解析器將會(huì)錯(cuò)誤的認(rèn)為這個(gè)字面量也是轉(zhuǎn)換字符的一部分。例如,"%date%nHello" 將會(huì)被解析成兩個(gè)轉(zhuǎn)換字符 %date 與 %nHello,但是 %nHello 不是一個(gè)轉(zhuǎn)換字符,所以 logback 將會(huì)輸出 %PARSER_ERROR[nHello]。如果你想要區(qū)分 %n 跟 Hello,可以通過(guò)給 %n 傳遞一個(gè)空參數(shù)。例如,"%date%n{}Hello" 將會(huì)被解析為 %date %n 再緊跟著一個(gè)字符串 "Hello"。

格式修改器

默認(rèn)情況下,相關(guān)信息按照原樣輸出。但是,在格式修改器的幫助下,可以對(duì)每個(gè)數(shù)據(jù)字段進(jìn)行對(duì)齊,以及更改最大最小寬度。

可選的格式修改器放在百分號(hào)跟轉(zhuǎn)換字符之間。

第一個(gè)可選的格式修改器是左對(duì)齊標(biāo)志,也就是減號(hào) (-) 字符。接下來(lái)的是最小字段寬度修改器,它是一個(gè)十進(jìn)制常量,表示輸出至少多少個(gè)字符。如果字段包含很少的數(shù)據(jù),它會(huì)選擇填充左邊或者右邊,直到滿足最小寬度。默認(rèn)是填充左邊 (右對(duì)齊),但是你可以通過(guò)左對(duì)齊標(biāo)志來(lái)對(duì)右邊進(jìn)行填充。填充字符為空格。如果字段的數(shù)據(jù)大于最小字段的寬度,會(huì)自動(dòng)擴(kuò)容去容納所有的數(shù)據(jù)。字段的數(shù)據(jù)永遠(yuǎn)不會(huì)被截?cái)唷?/p>

這個(gè)行為可以通過(guò)使用最大字段寬度修改器來(lái)改變,它通過(guò)一個(gè)點(diǎn)后面跟著一個(gè)十進(jìn)制常量來(lái)指定。如果字段的數(shù)據(jù)長(zhǎng)度大于最大字段的寬度,那么會(huì)從數(shù)據(jù)字段的開(kāi)頭移除多余的字符。舉個(gè)??,如果最大字段的寬度是 8,數(shù)據(jù)長(zhǎng)度是十個(gè)字符的長(zhǎng)度,那么開(kāi)頭的兩個(gè)字符將會(huì)被丟棄。這個(gè)行為跟 C 語(yǔ)言中 printf 函數(shù)從后面開(kāi)始截?cái)嗟男袨橄噙`背。

如果想從后面開(kāi)始截?cái)?,可以在點(diǎn)后面增加一個(gè)減號(hào)。如果是這樣的話,最大字段寬度是 8,數(shù)據(jù)長(zhǎng)度是十個(gè)字符的長(zhǎng)度,那么最后兩個(gè)字符將會(huì)被丟棄。

下面是各種格式修改器的例子:

格式修改器 左對(duì)齊 最小寬度 最大寬度 備注
%20logger false 20 none 如果 logger 的名字小于 20 個(gè)字符的長(zhǎng)度,那么會(huì)在左邊填充空格
%-20logger true 20 none 如果 logger 的名字小于 20 個(gè)字符的長(zhǎng)度,那么會(huì)在右邊填充空格
%.30logger NA none 30 如果 logger 的名字大于 30 個(gè)字符的長(zhǎng)度,那么從前面開(kāi)始截?cái)?/td>
%20.30logger false 20 30 如果 logger 的名字大于 20 個(gè)字符的長(zhǎng)度,那么會(huì)從左邊填充空格。但是如果 logger 的名字大于 30 字符,將會(huì)從前面開(kāi)始截?cái)?/td>
%-20.30logger true 20 30 如果 logger 的名字小于 20 個(gè)字符的長(zhǎng)度,那么從右邊開(kāi)始填充空格。但是如果 logger 的名字大于 30 個(gè)字符,將會(huì)從前面開(kāi)始截?cái)?/td>
%.-30logger NA none 30 如果 logger 的名字大于 30 個(gè)字符的長(zhǎng)度,那么從后面開(kāi)始截?cái)?/td>

下面的表格列出了格式修改器截?cái)嗟睦?。但是?qǐng)注意綜括號(hào) "[]" 不是輸出結(jié)果的一部分,它只是用來(lái)區(qū)分輸出的長(zhǎng)度。

格式修改器 logger 的名字 結(jié)果
[%20.20logger] main.Name [???????????main.Name]
[%-20.20logger] main.Name [main.Name???????????]
[%10.10logger] main.foo.foo.bar.Name [o.bar.Name]
[%10.-10logger] main.foo.foo.bar.Name [main.foo.f]

只輸出日志等級(jí)的一個(gè)字符

除了可以輸出 TRACE, DEBUG, WARN, INFO 或者 ERROR 來(lái)表示日志等級(jí)之外,還是輸出T, D, W, I 與 E 來(lái)進(jìn)行表示。你可以自定義轉(zhuǎn)換器 或者利用剛才討論的格式修改器來(lái)縮短日志級(jí)別為一個(gè)字符。這個(gè)轉(zhuǎn)換說(shuō)明符可能為 "%.-1level"。

轉(zhuǎn)換字符的選項(xiàng)

一個(gè)轉(zhuǎn)換字符后面可以跟一個(gè)選項(xiàng)。它們通過(guò)綜括號(hào)來(lái)聲明。我們之前已經(jīng)看到了一些可能的選項(xiàng)。例如之前的 MDC 轉(zhuǎn)換說(shuō)明符 %mdc{someKey}

一個(gè)轉(zhuǎn)換說(shuō)明符可能有多個(gè)可選項(xiàng)。一個(gè)轉(zhuǎn)換說(shuō)明符可以充分利用我們即將介紹到的 evaluator,可以添加多個(gè) evaluator 的名字到可選列表。如下:

<pattern>%-4relative [%thread] %-5level - %msg%n \
  %caller{2, DISP_CALLER_EVAL, OTHER_EVAL_NAME, THIRD_EVAL_NAME}</pattern>

如果這些選項(xiàng)中包含了一些特殊字符,例如花括號(hào),空格,逗號(hào)。你可以使用單引號(hào)或者雙引號(hào)來(lái)包裹它們。例如:

<pattern>%-5level - %replace(%msg){'\d{14,16}', 'XXXX'}%n</pattern>

我們傳遞 \d{16}XXXXreplace 轉(zhuǎn)換字符。它將消息中 14,15 或者 16 位的數(shù)字替換為 XXXX,用來(lái)混淆信用卡號(hào)碼。在正則表達(dá)式中,"\d" 表示一個(gè)數(shù)字的簡(jiǎn)寫(xiě)。"{14,16}" 會(huì)被解析成 "{14,16}",也就是說(shuō)前一個(gè)項(xiàng)將會(huì)被重復(fù)至少 14 次,至多 16 次。

特殊的圓括號(hào)

在 logback 里,模式字符串中的圓括號(hào)被看作為分組標(biāo)記。因此,它能夠?qū)ψ幽J竭M(jìn)行分組,并且直接對(duì)子模式進(jìn)行格式化。在 0.9.27 版本,logback 開(kāi)始支持綜合轉(zhuǎn)換字符,例如 %replace 可以對(duì)子模式進(jìn)行轉(zhuǎn)換。

例如一下模式:

%-30(%d{HH:mm:ss.SSS} [%thread]) %-5level %logger{32} - %msg%n

將會(huì)對(duì)子模式 "%d{HH:mm:ss.SSS} [%thread]" 進(jìn)行分組輸出,為了在少于 30 個(gè)字符時(shí)進(jìn)行右填充。

如果沒(méi)有進(jìn)行分組將會(huì)輸出:

13:09:30 [main] DEBUG c.q.logback.demo.ContextListener - Classload hashcode is 13995234
13:09:30 [main] DEBUG c.q.logback.demo.ContextListener - Initializing for ServletContext
13:09:30 [main] DEBUG c.q.logback.demo.ContextListener - Trying platform Mbean server
13:09:30 [pool-1-thread-1] INFO  ch.qos.logback.demo.LoggingTask - Howdydy-diddly-ho - 0
13:09:38 [btpool0-7] INFO c.q.l.demo.lottery.LotteryAction - Number: 50 was tried.
13:09:40 [btpool0-7] INFO c.q.l.d.prime.NumberCruncherImpl - Beginning to factor.
13:09:40 [btpool0-7] DEBUG c.q.l.d.prime.NumberCruncherImpl - Trying 2 as a factor.
13:09:40 [btpool0-7] INFO c.q.l.d.prime.NumberCruncherImpl - Found factor 2

如果對(duì) "%-30()" 進(jìn)行分組將會(huì)輸出:

13:09:30 [main]            DEBUG c.q.logback.demo.ContextListener - Classload hashcode is 13995234
13:09:30 [main]            DEBUG c.q.logback.demo.ContextListener - Initializing for ServletContext
13:09:30 [main]            DEBUG c.q.logback.demo.ContextListener - Trying platform Mbean server
13:09:30 [pool-1-thread-1] INFO  ch.qos.logback.demo.LoggingTask - Howdydy-diddly-ho - 0
13:09:38 [btpool0-7]       INFO  c.q.l.demo.lottery.LotteryAction - Number: 50 was tried.
13:09:40 [btpool0-7]       INFO  c.q.l.d.prime.NumberCruncherImpl - Beginning to factor.
13:09:40 [btpool0-7]       DEBUG c.q.l.d.prime.NumberCruncherImpl - Trying 2 as a factor.
13:09:40 [btpool0-7]       INFO  c.q.l.d.prime.NumberCruncherImpl - Found factor 2

后者的格式更加容易閱讀。

如果你想將圓括號(hào)當(dāng)作字面量輸出,那么你需要對(duì)每個(gè)圓括號(hào)用反斜杠進(jìn)行轉(zhuǎn)義。就像 (%d{HH:mm:ss.SSS} [%thread]) 一樣。

著色

如上所述的圓括號(hào)分組,允許對(duì)子模式進(jìn)行著色。在 1.0.5 版本,PatternLayout 可以識(shí)別 "%black","%red","%green","%yellow","%blue","%magenta","%cyan", "%white", "%gray", "%boldRed","%boldGreen", "%boldYellow", "%boldBlue", "%boldMagenta""%boldCyan", "%boldWhite" 以及 "%highlight" 作為轉(zhuǎn)換字符。這些轉(zhuǎn)換字符都還可以包含一個(gè)子模式。任何被顏色轉(zhuǎn)換字符包裹的子模式都會(huì)通過(guò)指定的顏色輸出。

下面是關(guān)于著色的配置文件。

<configuration debug="true">
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!--              在 Windows 平臺(tái)下,設(shè)置 withJansi = true 來(lái)開(kāi)啟 ANSI 顏色代碼需要 Jansi 類庫(kù) -->
<!--              需要在 classpath 引入 org.fusesource.jansi:jansi:1.8 包 -->
<!--              在基于 Unix 操作系統(tǒng),像 Linux 以及 Mac OS X 系統(tǒng)默認(rèn)支持 ANSI 顏色代碼 -->
        <withJansi>true</withJansi>
        <encoder>
            <pattern>[%thread] %highlight(%-5level) %cyan(%logger{15}) - %msg %n</pattern>
        </encoder>
    </appender>
    
    <root level="DEBUG">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

下面是相關(guān)的輸出:

[main] WARN  c.l.TrivialMain - a warning message 0
[main] DEBUG c.l.TrivialMain - hello world number1
[main] DEBUG c.l.TrivialMain - hello world number2
[main] INFO  c.l.TrivialMain - hello world number3
[main] DEBUG c.l.TrivialMain - hello world number4
[main] WARN  c.l.TrivialMain - a warning message 5
[main] ERROR c.l.TrivialMain - Finish off with fireworks

其實(shí)是有顏色的,但是 md 不支持直接對(duì)字體顏色進(jìn)行操作,而我懶得去折騰 HTML

只需要幾行代碼就可以創(chuàng)建一個(gè)著色轉(zhuǎn)換字符。在自定義轉(zhuǎn)換說(shuō)明符部分,我們將討論怎樣在配置文件中注冊(cè)一個(gè)轉(zhuǎn)換字符。

Evaluators

像之前提到的,當(dāng)一個(gè)轉(zhuǎn)換字符需要基于一個(gè)或者多個(gè) EventEvaluator 對(duì)象動(dòng)態(tài)表現(xiàn)時(shí),EventEvaluator 對(duì)象根據(jù)規(guī)則可以決定給定的日志事件是否匹配。

讓我們來(lái)回顧一下包含 EventEvaluator 的例子。下一個(gè)配置文件輸出日志事件到控制臺(tái),顯示日期,線程,日志級(jí)別,消息,以及調(diào)用者數(shù)據(jù)。獲取日志事件調(diào)用者的信息成本比較高,只有當(dāng)日志請(qǐng)求來(lái)源特定的 logger,或者消息包含特定的字符串時(shí),我們才會(huì)這樣做。換句話說(shuō),在調(diào)用者信息是多余的情況下,我們不應(yīng)該去影響應(yīng)用的性能。

Evaluator 與 評(píng)價(jià)表達(dá)式 (evaluation expressions) 都會(huì)在第七章 詳細(xì)介紹。如果你想利用 evaluator 去做一些有意思的事情,你必須看一下對(duì)這個(gè)的詳細(xì)介紹。下面的例子基于 JaninoEventEvaluator,所以需要 Janino 類庫(kù)。查看相關(guān)文檔進(jìn)行設(shè)置。

Example: callerEvaluatorConfig.xml

<configuration>
  <evaluator name="DISP_CALLER_EVAL">
    <expression>logger.contains("chapters.layouts") &amp;&amp; \
      message.contains("who calls thee")</expression>
  </evaluator>

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

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

上面的評(píng)價(jià)表達(dá)式用來(lái)匹配從名為 "chapters.layouts" logger 發(fā)出,并且消息中包含字符串 "who calls thee" 的日志事件。由于 XML 的編碼規(guī)則,& 符號(hào)需要被轉(zhuǎn)義為 &amp;。

下面的類利用了配置文件中所提到的特性。

Example: CallerEvaluatorExample.java

package chapters.layouts;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.util.StatusPrinter;

public class CallerEvaluatorExample {

  public static void main(String[] args)  {
    Logger logger = LoggerFactory.getLogger(CallerEvaluatorExample.class);
    LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();

    try {
      JoranConfigurator configurator = new JoranConfigurator();
      configurator.setContext(lc);
      configurator.doConfigure(args[0]);
    } catch (JoranException je) {
      // StatusPrinter will handle this
    }
    StatusPrinter.printInCaseOfErrorsOrWarnings(lc);

    for (int i = 0; i < 5; i++) {
      if (i == 3) {
        logger.debug("who calls thee?");
      } else {
        logger.debug("I know me " + i);
      }
    }
  }
}

上面的應(yīng)用沒(méi)有什么特別的地方。發(fā)出五條日志請(qǐng)求,第三條的的請(qǐng)求信息為 "who calls thee?"。

通過(guò)命令:

java chapters.layouts.CallerEvaluatorExample src/main/java/chapters/layouts/callerEvaluatorConfig.xml

將會(huì)輸出:

0    [main] DEBUG - I know me 0 
0    [main] DEBUG - I know me 1 
0    [main] DEBUG - I know me 2 
0    [main] DEBUG - who calls thee? 
Caller+0   at chapters.layouts.CallerEvaluatorExample.main(CallerEvaluatorExample.java:28)
0    [main] DEBUG - I know me 4

當(dāng)發(fā)出日志請(qǐng)求時(shí),會(huì)評(píng)價(jià)相應(yīng)的日志事件。僅僅只有第三個(gè)日志事件會(huì)匹配到評(píng)價(jià)規(guī)則,所以它的調(diào)用者信息會(huì)被展示出來(lái)。對(duì)于其它的日志事件,由于沒(méi)有匹配到評(píng)價(jià)規(guī)則,調(diào)用者信息不會(huì)被打印。

可以通過(guò)更改表達(dá)式來(lái)應(yīng)對(duì)真實(shí)的應(yīng)用場(chǎng)景。舉個(gè)??,你可以結(jié)合 logger 名與日志級(jí)別,日志級(jí)別在 WARN 以上的日志請(qǐng)求被當(dāng)作一個(gè)敏感的部分,在金融業(yè)務(wù)模塊中,我們可以這樣做來(lái)獲取調(diào)用者的信息。

重要:當(dāng)評(píng)價(jià)表達(dá)式true 時(shí),通過(guò) caller 轉(zhuǎn)換字符,可以輸出調(diào)用者的信息。

考慮這么一種情況,當(dāng)日志請(qǐng)求中包含異常信息時(shí),它們的堆棧信息也會(huì)輸出。但是,對(duì)于某些特定的異常信息,可能需要禁止輸出堆棧信息。

下面的代碼創(chuàng)建了三條日志請(qǐng)求,每一條都包含一個(gè)異常信息。第二條的異常信息跟其它的不一樣,它包含 "do not display this" 字符串,并且它的異常信息類型為 chapters.layouts.TestException。現(xiàn)在讓我們來(lái)阻止第二條日志的打印。

Example: ExceptionEvaluatorExample.java

package chapters.layouts;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.util.StatusPrinter;

public class ExceptionEvaluatorExample {

  public static void main(String[] args) {
    Logger logger = LoggerFactory.getLogger(ExceptionEvaluatorExample.class);
    LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();

    try {
      JoranConfigurator configurator = new JoranConfigurator();
      configurator.setContext(lc);
      lc.reset();
      configurator.doConfigure(args[0]);
    } catch (JoranException je) {
       // StatusPrinter will handle this
    }
    StatusPrinter.printInCaseOfErrorsOrWarnings(lc);

    for (int i = 0; i < 3; i++) {
      if (i == 1) {
        logger.debug("logging statement " + i, new TestException(
            "do not display this"));
      } else {
        logger.debug("logging statement " + i, new Exception("display"));
      }
    }
  }
}

下面的配置文件通過(guò)評(píng)價(jià)表達(dá)式來(lái)匹配包含 chapters.layouts.TextException 類型的日志事件,也就是我們之前說(shuō)要禁止的異常類型。

Example: exceptionEvaluatorConfig.xml

<configuration>
  <!-- evaluator 需要在 appender 前面定義 -->
  <evaluator name="DISPLAY_EX_EVAL">
    <expression>throwable != null &amp;&amp; throwable instanceof  \
      chapters.layouts.TestException</expression>
  </evaluator>
        
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%msg%n%xEx{full, DISPLAY_EX_EVAL}</pattern>
    </encoder>
  </appender>

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

作者原文里面是 %ex,應(yīng)該是筆誤

通過(guò)這個(gè)配置文件,每當(dāng)日志請(qǐng)求中包含一個(gè) chapters.layouts.TestException 時(shí),堆棧信息不會(huì)被輸出。

通過(guò)如下命令啟動(dòng):

java chapters.layouts.ExceptionEvaluatorExample src/main/java/chapters/layouts/exceptionEvaluatorConfig.xml

將會(huì)輸出:

logging statement 0
java.lang.Exception: display
    at chapters.layouts.ExceptionEvaluatorExample.main(ExceptionEvaluatorExample.java:16)
logging statement 1
logging statement 2
java.lang.Exception: display
    at chapters.layouts.ExceptionEvaluatorExample.main(ExceptionEvaluatorExample.java:16)

作者原文還輸出了 jar 包的信息,是因?yàn)榇虬笸ㄟ^(guò)命令行執(zhí)行的 (I think ??)

第二條日志沒(méi)有堆棧信息,因?yàn)槲覀兘?TextException 類型的堆棧信息。每條堆棧信息的最后用綜括號(hào)包裹起來(lái)的是具體的包信息

注意: 當(dāng) %ex 轉(zhuǎn)換說(shuō)明符中的評(píng)價(jià)表達(dá)式為 false 時(shí),堆棧信息才會(huì)輸出。

自定義轉(zhuǎn)換說(shuō)明符

我們可以在 PatternLayout 中使用內(nèi)置的轉(zhuǎn)換字符。我們也可以使用自己新建的轉(zhuǎn)換字符。

新建一個(gè)自定義的轉(zhuǎn)換字符需要兩步。

第一步

首先,你必須繼承 ClassicConverter 類。ClassicConverter 對(duì)象負(fù)責(zé)從 ILoggingEvent 實(shí)例中抽取信息并輸出字符串。例如,%logger 對(duì)應(yīng)的轉(zhuǎn)換器 LoggerConverter,可以從 ILoggingEvent 從抽取 logger 的名字,返回一個(gè)字符串。它可以縮寫(xiě) logger 的名字。

下面是一個(gè)自定義的轉(zhuǎn)換器,返回從創(chuàng)建開(kāi)始經(jīng)過(guò)的時(shí)間,單位為納秒。

Example: MySampleConverter

public class MySampleConverter extends ClassicConverter {

  long start = System.nanoTime();

  @Override
  public String convert(ILoggingEvent event) {
    long nowInNanos = System.nanoTime();
    return Long.toString(nowInNanos-start);
  }
}

這個(gè)實(shí)現(xiàn)非常簡(jiǎn)單。MySampleConverter 繼承了 ClassicConverter 并實(shí)現(xiàn)了 convert 方法,返回從創(chuàng)建開(kāi)始經(jīng)過(guò)多少納秒。

第二步

第二步,我們必須讓 logback 知道這個(gè)新建的 Converter。所以我們需要在配置文件中進(jìn)行聲明,如下:

Example: mySampleConverterConfig.xml

<configuration>

  <conversionRule conversionWord="nanos" 
                  converterClass="chapters.layouts.MySampleConverter" />
        
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%-6nanos [%thread] - %msg%n</pattern>
    </encoder>
  </appender>

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

執(zhí)行命令如下:

java chapters.layouts.SampleLogging src/main/java/chapters/layouts/mySampleConverterConfig.xml 

輸出信息如下:

26113953 [main] - Everything's going well
26672034 [main] - maybe not quite...

可以看一下其它 Converter 的實(shí)現(xiàn),例如 MDCConverter ,去定制更加復(fù)雜的功能,如可選處理。想創(chuàng)建自己的顏色主題,可以看一下 HighlightingCompositeConverter。

HTMLLayout

HTMLLayout (包含在 logback-classic 中) 以 HTML 格式生成日志。HTMLLayout 通過(guò) HTML 表格輸出日志,每一行對(duì)應(yīng)一條日志事件。

下面是 HTMLLayout 通過(guò)默認(rèn)的 CSS 樣式生成的。

[圖片上傳失敗...(image-740ad1-1547968731095)]

表格的列是通過(guò)轉(zhuǎn)換模式指定的。關(guān)于轉(zhuǎn)換模式的文檔請(qǐng)查看 PatternLayout。所以,你可以完全控制表格的內(nèi)容以及格式。你可以選擇并且展示任何跟 PatternLayout 組合的轉(zhuǎn)換器。

一個(gè)值得注意的問(wèn)題是使用 PatternLayout 中的 HTMLLayout 時(shí),不要使用空格或者其它的字面量來(lái)分隔轉(zhuǎn)換說(shuō)明符。轉(zhuǎn)換模式中的每個(gè)說(shuō)明符都會(huì)被當(dāng)做一個(gè)單獨(dú)的列。同樣的轉(zhuǎn)換模式中的每個(gè)文本塊也會(huì)被當(dāng)作一個(gè)單獨(dú)的列,這會(huì)占用屏幕的空間。

下面的 HTMLLayout 相關(guān)的配置:

Example: htmlLayoutConfig1.xml

<configuration debug="true">
  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
      <layout class="ch.qos.logback.classic.html.HTMLLayout">
        <pattern>%relative%thread%mdc%level%logger%msg</pattern>
      </layout>
    </encoder>
    <file>test.html</file>
  </appender>

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

TrivialMain 包含一些消息以及一個(gè)結(jié)束異常。執(zhí)行以下命令:

java chapters.layouts.TrivialMain src/main/java/chapters/layouts/htmlLayoutConfig1.xml

將會(huì)當(dāng)前文件夾創(chuàng)建一個(gè) test.html 文件。test.html 文件的內(nèi)容與下面類似:

[圖片上傳失敗...(image-237110-1547968731096)]

堆棧信息

如果你使用 %ex 轉(zhuǎn)換字符去展示堆棧信息,那么將會(huì)創(chuàng)建一個(gè)列來(lái)展示堆棧信息。在大多數(shù)的情況下,列會(huì)為空,那么就會(huì)浪費(fèi)屏幕的空間。而且,在單獨(dú)的列打印堆棧信息,輸出的結(jié)果閱讀起來(lái)有難度。但是,%ex 轉(zhuǎn)換字符不是唯一一個(gè)用來(lái)展示堆棧信息的。

原文第一個(gè) %ex 為 %em

一個(gè)更好的解決辦法是通過(guò)實(shí)現(xiàn) IThrowableRenderer 接口。實(shí)現(xiàn)的接口可以分配給 HTMLLayout 來(lái)管理相關(guān)的異常數(shù)據(jù)。默認(rèn)情況下,會(huì)給每個(gè) HTMLLayout 實(shí)例分配一個(gè) DefaultThrowableRenderer。它將異常的堆棧信息寫(xiě)入到表格新的一行,并且非常易讀,就跟上面展示的表格一樣。

如果在某些情況下,你仍然想要使用 %ex,那么你可以在配置文件中指定 NOPThrowableRenderer 來(lái)禁止在單獨(dú)一行展示堆棧信息。我們不理解為什么你要這樣做,但是你開(kāi)心就好。

CSS

HTMLLayout 創(chuàng)建的 HTML 是通過(guò) CSS 來(lái)控制樣式的。在缺少指定命令的情況下,HTMLLayout 會(huì)使用內(nèi)部默認(rèn)的樣式。但是,你可以告訴 HTMLLayout 去使用外部的 CSS 文件。通過(guò)在 <layout> 元素內(nèi)置 <cssBuilder> 元素可以做到。如下所示:

<layout class="ch.qos.logback.classic.html.HTMLLayout">
  <pattern>%relative...%msg</pattern>
  <cssBuilder class="ch.qos.logback.classic.html.UrlCssBuilder">
    <!-- css 文件的路徑 -->
    <url>http://...</url>
  </cssBuilder> 
</layout>

HTMLLayout 通常與 SMTPAppender 配合使用,所以郵件可以被格式化成 HTML。

Log4j XMLLayout

XMLLayout (logback-classic 的一部分) 生成一個(gè) log4j.dtd 格式的文件,用來(lái)與類似 Chainsaw 以及 Vigilog 這樣的工具進(jìn)行交互操作,這些工具可以處理由 log4j XMLLayout 生成的文件。

跟 log4j 1.2.15 版本的 XMLLayout 一樣,logback-classic 中的 XMLLayout 接收兩個(gè) boolean 屬性:locationInfoproperties。設(shè)置 locationInfo 的值為 true,可以在每個(gè)事件中開(kāi)啟包含位置信息 (調(diào)用者的數(shù)據(jù))。設(shè)置 properties 為 true,可以開(kāi)啟包含 MDC 信息。默認(rèn)情況下,兩個(gè)屬性都設(shè)置為 false。

下面是一個(gè)示例:

Example: log4jXMLLayout.xml

<configuration>
  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>test.xml</file>
    <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
      <layout class="ch.qos.logback.classic.log4j.XMLLayout">
        <locationInfo>true</locationInfo>
      </layout>
    </encoder> 
  </appender> 

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

Logback access

大多數(shù) logback-access 的 layout 僅僅只是 logback-classic 的 layout 的改編。logback-classic 與 logback-access 模塊定位不同的需求,但是都提供了類似的功能。

寫(xiě)你自己的 layout

在 logback-access 中寫(xiě)一個(gè)定制的 Layout 與在 logback-classic 的 Layout 中幾乎一致。

PatternLayout

配置 logback-access 中的 PatternLayout,與在 logback-classic 中配置相同。但是它添加了一些額外的轉(zhuǎn)換說(shuō)明符來(lái)適應(yīng) HTTP 請(qǐng)求以及 HTTP 響應(yīng)中特定信息位的記錄。

下表是 logback-access 中 PatternLayout 的相關(guān)轉(zhuǎn)換說(shuō)明符。

轉(zhuǎn)換字符 效果
a / remoteIP 遠(yuǎn)程 IP 地址
A / localIP 本地 IP 地址
b / B / bytesSent 響應(yīng)內(nèi)容的長(zhǎng)度
h / clientHost 遠(yuǎn)程 host
H / protocol 請(qǐng)求協(xié)議
l 遠(yuǎn)程日志名,在 logback-access 中,轉(zhuǎn)換器總是返回 "-"
reqParameter{paramName} 響應(yīng)參數(shù)。<br />這個(gè)轉(zhuǎn)換字符在花括號(hào)中接受一個(gè)參數(shù),在請(qǐng)求中尋找相應(yīng)的參數(shù)。<br /> %reqParameter{input_data} 展示相應(yīng)的參數(shù)。
i{header} / header{header} 請(qǐng)求頭。<br /> %header{Referer} 顯示請(qǐng)求的來(lái)源。<br />如果沒(méi)有指定選項(xiàng),將會(huì)展示所有可用的請(qǐng)求頭
m / requestMethod 請(qǐng)求方法
r / requestURL 請(qǐng)求 URL
s / statusCode 請(qǐng)求狀態(tài)碼
D / elapsedTime 請(qǐng)求所耗費(fèi)的時(shí)間,單位為毫秒
T / elapsedSeconds 請(qǐng)求所耗費(fèi)的時(shí)間,單位為秒
t / date 輸出日志事件的日期。日期說(shuō)明符需要用花括號(hào)指定。日期格式來(lái)源 java.text.SimpleDateFormat。ISO8601 也是一個(gè)有效的值。<br />例如,%t{HH:mm:ss,SSS} 或者 %t{dd MMM yyyy ;HH:mm:ss,SSS}。如果沒(méi)有指定日期格式字符,那么會(huì)默認(rèn)指定為 %t{dd/MMM/yyyy:HH:mm:ss Z}
u / user 遠(yuǎn)程用戶
q / queryString 請(qǐng)求查詢字符串,前綴為 '?'
U / requestURI 請(qǐng)求 URI
S / sessionID Session ID.
v / server 服務(wù)器名
I / threadName 處理該條請(qǐng)求的線程
localPort 本地端口
reqAttribute{attributeName} 請(qǐng)求的屬性。<br />%reqAttribute{SOME_ATTRIBUTE} 展示相應(yīng)的屬性。
reqCookie{cookie} 請(qǐng)求 cookie。<br />%cookie{COOKIE_NAME} 展示相應(yīng)的 cookie。
responseHeader{header} 響應(yīng)頭。<br />%header{Referer} 展示響應(yīng)的來(lái)源。
requestContent 展示請(qǐng)求的內(nèi)容,即請(qǐng)求的 InputStream。它與 TeeFilter 結(jié)合使用。一個(gè)使用 TeeHttpServletRequest 替代 HttpServletRequest 的 javax.servlet.Filter。前者可以多次訪問(wèn)請(qǐng)求的 InputStream 而不會(huì)丟失內(nèi)容。
fullRequest 請(qǐng)求的數(shù)據(jù)。包括所有的請(qǐng)求頭以及請(qǐng)求內(nèi)容。
responseContent 展示響應(yīng)的內(nèi)容,也就是響應(yīng)的 InputStream。 它與 TeeFilter 結(jié)合使用。一個(gè)使用 TeeHttpServletResponse 替代 HttpServletResponsejavax.servlet.Filter。前者可以多次訪問(wèn)響應(yīng) (原文為請(qǐng)求) 的 InputStream 而不會(huì)丟失內(nèi)容。
fullResponse 獲取響應(yīng)所有可用的數(shù)據(jù),包括所有的響應(yīng)頭以及響應(yīng)內(nèi)容。

logback-access 的 PatternLayout 能夠識(shí)別三個(gè)關(guān)鍵字,有點(diǎn)類似快捷鍵。

關(guān)鍵字 相等的轉(zhuǎn)換模式
common or CLF %h %l %u [%t] "%r" %s %b
combined %h %l %u [%t] "%r" %s %b "%i{Referer}" "%i{User-Agent}"

關(guān)鍵字 common 對(duì)應(yīng) '%h %l %u [%t] "%r" %s %b',分別展示客戶端主機(jī),遠(yuǎn)程日志名,用戶,日期,請(qǐng)求 URL,狀態(tài)碼,以及響應(yīng)內(nèi)容的長(zhǎng)度。

關(guān)鍵字 combined 對(duì)應(yīng) '%h %l %u [%t] "%r" %s %b "%i{Referer}" "%i{User-Agent}"'。跟 common 有點(diǎn)類似,但是它還會(huì)再顯示兩個(gè)請(qǐng)求頭,referer 以及 user-agent。

HTMLLayout

logback-access 中的 HTMLLayout 與 logback-classic 中的 HTMLLayout 有點(diǎn)類似。

默認(rèn)情況下,它會(huì)創(chuàng)建一個(gè)包含如下數(shù)據(jù)的表格:

  • 請(qǐng)求 IP (Remote IP)
  • 日期 (Date)
  • 請(qǐng)求 URL (Request URL)
  • 狀態(tài)碼 (Status code)
  • 內(nèi)容長(zhǎng)度 (Content Length)

下面是 logback-access 中的 HTMLLayout 輸出的一個(gè)例子:

[圖片上傳失敗...(image-75898-1547968731096)]

還有比真實(shí)的例子更好的例子嗎?我們自己的 log4j.properties 用于 logback 翻譯器,充分的利用了 logback-access 在線演示 RollingFileAppenderHTMLLayout 的輸出。

每一個(gè)新的用戶請(qǐng)求 翻譯器 這個(gè)網(wǎng)站,一個(gè)新的條目就會(huì)添加到訪問(wèn)日志,你可以通過(guò)這個(gè)鏈接 查看。

掃碼關(guān)注公眾號(hào):java之旅
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容