017_layout排版
1. 什么是layout?
1.1. Layout負責把事件轉換成字符串。Layout接口的doLayout()方法的參數是代表任何類型的事件, 返回字符串。Layout接口概要如下:
1.2. 接口很簡單卻足夠完成很多格式化需求。
1.3. Logback-classic只處理ch.qos.logback.classic.spi.ILoggingEvent類型的事件。
1.4. LayoutBase類管理對所有layout實例通用的狀態, 比如layout是啟動還是停止、header、footer和content type數據。LayoutBase類允許開發者在自己的layout里實現具體的格式化方式。LayoutBase類是泛型的。
2. 自定義layout例子
2.1. 讓我們實現一個簡單卻可工作的layout, 打印內容包括: 自程序啟動以來逝去的時間、記錄事件的級別、包含在方括號里的調用者線程的名字、logger名、連字符、事件消息和換行。
2.2. 新建一個名為OwnCustomLayout的Java項目, 同時添加相關jar包
2.3. MySampleLayout.java繼承LayoutBase<ILoggingEvent>。MySampleLayout類里唯一的方法doLayout(ILoggingEvent event), 一開始先初始化一個StringBuffer, 接著添加event參數的各種字段, 然后把StringBuffer轉換成Stirng, 最后返回這個String。
2.4. 如何為layout增加選項?為layout或任何logback的其他組件添加屬性非常簡單:?聲明一個屬性及setter方法接即可。MySampleLayout類包含一個為輸出添加前綴的屬性。
package com.fj.ocl;import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.CoreConstants; import ch.qos.logback.core.LayoutBase;public class MySampleLayout extends LayoutBase<ILoggingEvent> {String prefix;public String getPrefix() {return prefix;}public void setPrefix(String prefix) {this.prefix = prefix;}@Overridepublic String doLayout(ILoggingEvent event) {StringBuffer buf = new StringBuffer(128);if(prefix != null) {buf.append(prefix + ": ");}buf.append(event.getTimeStamp() - event.getLoggerContextVO().getBirthTime());buf.append(" ");buf.append(event.getLevel());buf.append(" [");buf.append(event.getThreadName());buf.append("] ");buf.append(event.getLoggerName());buf.append(" - ");buf.append(event.getFormattedMessage());buf.append(CoreConstants.LINE_SEPARATOR);return buf.toString();} }2.5. 配置自定義layout與配置其他layout是一樣的。ConsoleAppender類需要一個encoder, 為了滿足這個需求, 我們把包裹了MySimpleLayout的LayoutWrappingEncoder實例傳遞給ConsoleAppender。
2.6. OwnCustomLayout.java
package com.fj.ocl;import org.slf4j.Logger; import org.slf4j.LoggerFactory;public class OwnCustomLayout {public static final Logger logger = LoggerFactory.getLogger(OwnCustomLayout.class);public static void main(String[] args) {logger.info("自定義一個簡單的Layout。");} }2.7. 運行程序
3. PatternLayout
3.1. 與所有Layout一樣, PatternLayout的參數是一個記錄事件并返回一個字符串, 但是被返回的字符串可以通過 PatternLayout的轉換模式進行任意定制。
3.2. PatternLayout的轉換模式與C語言里的printf()的轉換模式很接近。轉換模式是由文本文字和格式控制表達式(稱為格式轉換符(conversion specifier))組成。你可以在格式轉換符內插入任意文本文字。每個格式轉換符以"%"開頭, 接著是可選的格式修飾符(format modifier)、一個轉換符(conversion word)和放在括號里的其他可選參數。轉換符控制待轉換的數據字段, 比如logger名、級別、日期或線程名。格式修飾符控制字段的寬度、填充和左右對齊方式。
3.3. 已經講過幾次了, FileAppender及其子類需要一個encoder。所以, 當用于FileAppender或其子類時, PatternLayout必須被包裹在一個encoder里。由于FileAppender與PatternLayout的組合用法太常用了, 所以logback提供了一個名為"PatternLayoutEncoder"的encoder, 它唯一的設計目標就是包裹一個PatternLayout實例, 以便PatternLayout能被看作一個encoder。
3.4. 用編程方式為ConsoleAppender配置了一個PatternLayoutEncoder。新建一個名為ProgrammingPatternLayout的Java項目, 同時添加相關jar包。
3.5. 編寫PatternSample.java
package com.fj.ppl;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 {private static final Logger rootLogger = (Logger) LoggerFactory.getLogger("ROOT");public static void main(String[] args) {LoggerContext loggerContext = rootLogger.getLoggerContext();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("用編程方式為ConsoleAppender配置了一個PatternLayoutEncoder。");rootLogger.warn("用編程方式為ConsoleAppender配置了一個PatternLayoutEncoder。");} }3.6. 運行結果
4. 轉換符說明
4.1. 下表列出了轉換符及其選項。當單元格里同時列出多個轉換符時, 它們就是同義詞。
| 轉換符 | 作用 | ||||||||||||||||||||||||
| c{length} lo{length} logger{length} | 輸出源記錄事件的logger名。 可以有一個整數型的參數, 功能是縮短logger名。設為"0"表示只輸出logger名里最右邊的點號之后的字符串。下表是縮寫算法例子。
注意最右邊的logger名永遠不被省略, 即使它的長度超過了"length"選項。logger名里的其他片段可以被縮短為至少1個字符, 但永遠不會消失。 | ||||||||||||||||||||||||
| C{length} class{length} | 輸出執行記錄請求的調用者的全限定類名。 和上面的"%logger"一樣, 也有"length"屬性, 表示縮短類名。"length"為"0"表示不輸出包名。默認輸出類的全限定名。 輸出調用者的類信息并不很快, 所以盡量避免使用, 除非執行速度不造成任何問題。 | ||||||||||||||||||||||||
| contextName cn | 輸出事件源頭關聯的logger的logger上下文的名稱。 | ||||||||||||||||||||||||
| d{pattern} date{pattern} | 輸出記錄事件的日期。該轉換符有可選的模式字符串選項。模式語法與java.text.SimpleDateFormat的格式兼容。 可以為ISO8061日期格式指定字符串"ISO8601"。如果沒有模式選項, 則默認為ISO 8601日期格式。 下面是一些選項值的例子。
該轉換符還可以有第二個選項: 時區。因此, "date{HH:mm:ss.SSS,Australia/Perth}"會輸出澳大利亞珀斯城所在時區的時間。 由于逗號","是選項分隔符, 所以"[HH:mm:ss,SSS]"會輸出"SSS"時區的時間, 但是"SSS"時區并不存在, 因此會用默認的GMT時區輸出時間。如果想在日期模式里使用逗號, 可以用引號包含之, 例 如%date{"HH:mm:ss,SSS"}。 | ||||||||||||||||||||||||
| F file | 輸出執行記錄請求的Java源文件的文件名。 輸出文件信息并不很快, 所以盡量避免使用, 除非執行速度不造成任何問題。 | ||||||||||||||||||||||||
| caller{depth} caller{depth, evaluator-1, ... evaluator-n} | 輸出生成記錄事件的調用者的位置信息。 位置信息依賴JVM實現, 但通常由調用方法的全限定名、放在括號里的文件名和行號。 該轉換符可以有一個整數選項, 表示顯示信息的深度。 例如, %caller{2}會輸出:
例如, %caller{3}會輸出:
? 在創建輸出之前, 該轉換符可以用求值式來測試是否滿足給定的條件。 例如, 只有求值式"CALLER_DISPLAY_EVAL"返回true時, "%caller{3, CALLER_DISPLAY_EVAL}"才會輸出三行堆棧跟蹤。 | ||||||||||||||||||||||||
| L line | 輸出執行記錄請求的行號。 輸出行號并不很快, 所以盡量避免使用, 除非執行速度不造成任何問題。 | ||||||||||||||||||||||||
| m msg message | 輸出與記錄事件相關聯的應用程序提供的消息。 | ||||||||||||||||||||||||
| M method | 輸出執行記錄請求的方法名。 輸出方法名并不很快, 所以盡量避免使用, 除非執行速度不造成任何問題。 | ||||||||||||||||||||||||
| n | 輸出與平臺相關的行分隔符。 該轉換符與不可移植的行分隔符如"\n"或"\r\n"的性能幾乎一樣。所以該換行符是指定行分隔符的首選方式。 | ||||||||||||||||||||||||
| p le level | 輸出記錄事件的級別。 | ||||||||||||||||||||||||
| r relative | 輸出從程序啟動到創建記錄事件的逝去時間, 單位毫秒。 | ||||||||||||||||||||||||
| t thread | 輸出產生記錄事件的線程名。 | ||||||||||||||||||||||||
| X{key} mdc{key} | 輸出與產生記錄事件的線程相關聯的MDC。 如果該轉換符后有放在花括號里的key, 比如: %mdc{clientNumber}, 則輸出該key對應的值。 如果沒有指定key, 則輸出MDC的全部內容, 格式是"key1=val1, key2=val2"。 | ||||||||||||||||||||||||
| ex{length} exception{length} throwable{length} ? ex{length, evaluator-1, ... evaluator-n} exception{length, evaluator-1, ... evaluator-n} throwable{length, evaluator-1, ... evaluator-n} | 輸出與記錄事件相關聯的堆棧跟蹤, 如果有的話。默認輸出全部堆棧跟蹤。 ? "throw"轉換符可跟下面選項之一:
? 示例:
? 在創建輸出之前, 該轉換符可以用求值式來測試是否滿足給定的條件。例如, 只有求值式"EX_DISPLAY_EVAL"返回false時, "%ex{full, EX_DISPLAY_EVAL}"才會輸出全部堆棧跟蹤。 | ||||||||||||||||||||||||
| nopex nopexception | 表示不輸出任何堆棧跟蹤, 因此可以高效地忽略異常。 如果沒有指定"%xThrowable"或其他與throwable有關的轉換符, 則PatternLayout會自動把"%xThrowable"作為最后面的轉換符, 但本轉換符可以覆蓋這種默認行為, 從而不顯示堆棧跟蹤信息。 | ||||||||||||||||||||||||
| marker | 輸出與記錄請求相關聯的marker。 如果marker包含子marker, 則按照下面的格式輸出父、子marker的名稱: parentName[ child1, child2 ]。 | ||||||||||||||||||||||||
| property{key} | 輸出名為"key"上下文屬性的值。如果"key"不是logger上下文的屬性, 則從系統屬性里查找。 "key"沒有默認值, 如果忽略之, 則返回錯誤提示"Property_HAS_NO_KEY"。 |
4.2. 由于在轉換模式上下文里, 百分號"%"有特殊含義, 所以如果想把"%"作為普通文本文字, 則必須用"\"對它進行轉義。
5. 格式修飾符
5.1. 默認情況下, 相關信息會按原格式輸出。但是, 在格式修飾符的幫助下, 就可以為每個字段指定最小、最大寬度, 以及對齊方式。
5.2. 可選的格式修飾符位于百分號與轉換符之間。
5.3. 第一個可選的格式修飾符是左對齊標志, 符號是減號"-"。接著是可選的最小寬度修飾符, 符號是表示輸出的最少字符的十進制數字。如果字符數小于最小寬度, 則左填充或右填充。默認是左填充(即右對齊)。填充符是空格。如果字符數大于最小寬度, 則擴張到字符的寬度。字符永遠不會被截斷。
5.4. 最大寬度修飾符能夠改變上面的行為, 符號是點號"."后加數字。如果字符數大于最大寬度, 則從前面截斷字符。例如, 如果最大寬度是"8", 字符有10個, 則前兩個字符會被拋棄。C語言的printf函數是從字符尾部進行截斷。
5.5. 在點號"."后加上減號"-"表示從尾部截斷。例如, 最大寬度是"8", 字符有10個, 則最后兩個字符會被拋棄。
5.6. 下面是格式修飾符的各種例子。
5.7. 下表是格式修飾符截斷的例子。
6. 用一個字符輸出級別
6.1. 可以不打印級別的全名, 而是用T、D、W、I和E, 分別對應TRACE、DEBUG、WARN、INFO和ERROR。你既可以寫一個自定義轉換器或簡單地用上面的格式修飾符把級別縮短為一個字符, 如"%.-1level"。
7. 圓括號的特殊含義
7.1. 在logback里, 圓括號被視為編組標記。因此可以將一個子模式進行編組, 然后對這個編組應用格式化指令。
7.2. 如下將對子模式"%d{HH:mm:ss.SSS} [%thread]"產生的輸出進行編組, 結果是如果少于30個字符就右填充。
7.3. 如果要把圓括號作為普通文本文字, 則用前置"\"進行轉義, 比如"\(%d{HH:mm:ss.SSS} [%thread]\)"。
8. 選項
8.1. 格式修飾符后可以跟選項。選項總是在花括號里聲明。我們已經見過選項的一些用法, 比如與MDC轉換符聯合使用的: %mdc{someKey}。
9. 求值式(Evaluator)
9.1. 當格式轉換符需要根據一個或多個EventEvaluator對象而有動態的行為時, 選項列表就派得上用場了。EventEvaluator對象負責決定給定的記錄事件是否匹配求值式的條件。
<evaluator name="DISPLAY_CALL"> <expression>throwable != null && throwable instanceof java.lang.RuntimeException</expression> </evaluator><appender name="stdout" class="ch.qos.logback.core.ConsoleAppender"><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>%caller{5, DISPLAY_CALL}</pattern></encoder> </appender>10. 轉換符格式修飾符例子
10.1. 新建一個名為ConversionWord的Java項目, 同時添加相關jar包
10.2. 在src目錄下創建logback.xml
<!-- contextName輸出事件源頭關聯的logger的logger上下文的名稱。 --> <!-- mdc{key}輸出與產生記錄事件的線程相關聯的MDC。 --> <!-- property{key}輸出名為"key"上下文屬性的值。如果"key"不是 logger上下文的屬性, 則從系統屬性里查找。 --> <!-- relative輸出從程序啟動到創建記錄事件的逝去時間, 單位毫秒。 --> <!-- date{pattern}輸出記錄事件的日期。 --> <!-- level輸出記錄事件的級別。 --> <!-- thread輸出產生記錄事件的線程名。 --> <!-- class{length}輸出執行記錄請求的調用者的全限定類名。 --> <!-- logger{length}輸出源記錄事件的logger名。 --> <!-- file輸出執行記錄請求的Java源文件的文件名。 --> <!-- line輸出執行記錄請求的行號。 --> <!-- marker輸出與記錄請求相關聯的marker。 --> <!-- message輸出與記錄事件相關聯的應用程序提供的消息。 --> <!-- method輸出執行記錄請求的方法名。 --> <!-- caller{depth, evaluator-1, ... evaluator-n}輸出生成記錄事件的調用者的位置信息。 --> <!-- exception{length, evaluator-1, ... evaluator-n}輸出與記錄事件相關聯的堆棧跟蹤, 如果有的話。默認輸出全部堆棧跟蹤。 --> <!-- nopexception表示不輸出任何堆棧跟蹤, 因此可以高效地忽略異常。 --> <!-- n輸出與平臺相關的行分隔符。 --> <configuration><contextName>轉換符</contextName><evaluator name="DISPLAY_CALL"> <expression>throwable != null && throwable instanceof java.lang.RuntimeException</expression></evaluator><appender name="stdout" class="ch.qos.logback.core.ConsoleAppender"><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>%-32([%.-1level] [%-8.3thread] [%8.3logger{64}]:) %marker [%10.-5message] %nopexception%n%caller{5, DISPLAY_CALL}%n</pattern></encoder></appender><appender name="errorFile" class="ch.qos.logback.core.FileAppender"><file>errorFile.log</file><append>false</append><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>%contextName %mdc{mdcKey} %property{ptyKey} %relative %class{64}: %message%n%exception</pattern></encoder></appender><appender name="defaultErrorFile" class="ch.qos.logback.core.FileAppender"><file>defaultErrorFile.log</file><append>false</append><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>%date{yyy-MM-dd HH:mm:ss}: %file %method %line %message%n</pattern></encoder></appender><logger name="com.fj.cw.error.ErrorDao" level="error" additivity="false"><appender-ref ref="errorFile" /></logger><logger name="com.fj.cw.noerror.DefaultErrorDao" level="error" additivity="false"><appender-ref ref="defaultErrorFile" /></logger><root level="all"><appender-ref ref="stdout"></appender-ref></root> </configuration>10.3. DefaultErrorDao.java
package com.fj.cw.noerror;import org.slf4j.Logger; import org.slf4j.LoggerFactory;public class DefaultErrorDao {private static final Logger logger = LoggerFactory.getLogger(DefaultErrorDao.class);public void error() {try {throw new RuntimeException("運行時異常。");} catch (Exception e) {logger.error("我是一條錯誤信息。", e);}} }10.4. DefaultErrorService.java
package com.fj.cw.noerror;public class DefaultErrorService {private DefaultErrorDao dao = new DefaultErrorDao();public void error() {dao.error();} }10.5. ErrorDao.java
package com.fj.cw.error;import org.slf4j.Logger; import org.slf4j.LoggerFactory;public class ErrorDao {private static final Logger logger = LoggerFactory.getLogger(ErrorDao.class);public void error() {try {throw new RuntimeException("運行時異常。");} catch (Exception e) {logger.error("我是一條錯誤信息。", e);}} }10.6. ErrorService.java
package com.fj.cw.error;public class ErrorService {private ErrorDao dao = new ErrorDao();public void error() {dao.error();} }10.7. CwDao.java
package com.fj.cw;import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.Marker; import org.slf4j.MarkerFactory;public class CwDao {private static final Logger logger = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);public void add() {try {throw new RuntimeException("運行時異常。");} catch (Exception e) {Marker marker = MarkerFactory.getMarker("異常標記");marker.add(MarkerFactory.getMarker("子標記"));logger.error(marker, "添加一條數據", e);}}public void delete() {logger.warn("刪除一條數據");}public void modify() {logger.info("修改一條數據");}public void select() {logger.debug("查詢數據");} }10.8. CwService.java
package com.fj.cw;public class CwService {private CwDao dao = new CwDao();public void add() {dao.add();}public void delete() {dao.delete();}public void modify() {dao.modify();}public void select() {dao.select();} }10.9. CwControl.java
package com.fj.cw;import org.slf4j.LoggerFactory; import org.slf4j.MDC; import com.fj.cw.error.ErrorService; import com.fj.cw.noerror.DefaultErrorService; import ch.qos.logback.classic.LoggerContext;public class CwControl {public static void main(String[] args) {MDC.put("mdcKey", "我是和記錄事件的線程相關聯的MDC值。");LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();lc.putProperty("ptyKey", "我是上下文屬性值。");CwService service = new CwService();service.add();service.delete();service.modify();service.select();ErrorService errorService = new ErrorService();errorService.error();DefaultErrorService noErrorService = new DefaultErrorService();noErrorService.error();} }10.10. 運行項目控制臺輸出
10.11. defaultErrorFile.log
10.12. errorFile.log
11. 創建自定義格式轉換符
11.1. 創建自定義格式轉換符有兩步。第一步創建一個繼承自ClassicConverter的類; 第二步在配置文件里配置這個類。
11.2. 新建一個名為OwnConversionWord的Java項目, 同時添加相關jar包。
11.3. 首先, 必須繼承ClassicConverter類。ClassicConverter對象負責從ILoggingEvent提取信息, 并產生一個字符串。
package com.fj.ocw;import ch.qos.logback.classic.pattern.ClassicConverter; import ch.qos.logback.classic.spi.ILoggingEvent;public class MyConversion extends ClassicConverter {@Overridepublic String convert(ILoggingEvent event) {return event.getMessage().replaceAll("\\.", "。");} }11.4. 第二步在logback.xml里配置我們的Converter。
11.5. 測試類
package com.fj.ocw;import org.slf4j.Logger; import org.slf4j.LoggerFactory;/*** 替換日志信息中的英文符號為中文符號。*/ public class TestMyConverter {private static final Logger logger = LoggerFactory.getLogger(TestMyConverter.class);public static void main(String[] args) {logger.error("我是一條錯誤日志.");logger.warn("我是一條警告日志.");logger.debug("我是一條測試日志.");} }11.6. 運行項目
12. HTMLLayout
12.1. HTMLLayout以HTML表格的形式輸出記錄, 表格的每行對應于一個記錄事件。
12.2. 表格的列是由格式轉換符指定的, 因此你可以完全控制表格的內容和格式。你可以選擇和顯示任何被PatternLayout所知的轉換器的組合。
12.3. PatternLayout與HTMLLayout一起使用的一個例外是, 格式轉換符不能用空格分隔, 或更一般地說, 不能被文本文字分隔。格式轉換符里的每個轉換符都會產生一個單獨的列。同樣地, 轉換符里的每塊文本文字也會導致生成一個單獨的列。
12.4. 新建一個名為HTMLLayout的Java項目, 同時添加相關jar包。
12.5. 在src目錄下添加logback.xml
12.6. 測試類TestHTMLLayout.java
package com.fj.htmllayout;import org.slf4j.Logger; import org.slf4j.LoggerFactory;public class TestHTMLLayout {private static final Logger logger = LoggerFactory.getLogger(TestHTMLLayout.class);public static void main(String[] args) {logger.error("我是一條錯誤日志.");logger.warn("我是一條警告日志.");logger.debug("我是一條測試日志.");} }12.7. 運行結果
總結
以上是生活随笔為你收集整理的017_layout排版的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 008_logback配置语法
- 下一篇: 009-Joran配置框架