基于slf4j日志MDC輸出格式的問題
slf4j日志MDC輸出格式
配置使用
// 自動配置模板
...
<Property name="layout">%d %p %X{traceId} [%t] %c{10}:%M:%L %m%n</Property>
...
<PatternLayout pattern="${layout}"/>
...
// 具體項目覆蓋配置的格式
<Property name="layout">%d %p [%t] %c{1.}:%M:%L %X{myTraceId} %m%n</Property>
MDC.put("myTraceId", myTraceId);
try {
...
} catch (Exception e) {
...
} finally {
MDC.clear();
}
日志輸出效果發(fā)現(xiàn)是直接打印了myTraceId所對應(yīng)的的值,而我們期望是這樣的格式{myTraceId=123}。
原因分析
查看格式化的實現(xiàn)類PatternLayout,內(nèi)部通過PatternSelector匹配選擇器根據(jù)Pattern匹配選擇對應(yīng)的轉(zhuǎn)換器進行格式化
默認(rèn)使用MarkerPatternSelector實現(xiàn),選擇器構(gòu)造器中解析獲取各個匹配模式對應(yīng)的格式化實現(xiàn)列表PatternFormatter
PatternFormatter實現(xiàn)的實例屬性LogEventPatternConverter抽象類對具體的日志內(nèi)容進行格式化轉(zhuǎn)換,查看其實現(xiàn)類
直接查看MdcPatternConverter實現(xiàn)
構(gòu)造器中按照逗號“,”切分MDC的key配置
// options, 對應(yīng)配置中的key列表
private MdcPatternConverter(final String[] options) {
super(options != null && options.length > 0 ? "MDC{" + options[0] + '}' : "MDC", "mdc");
if (options != null && options.length > 0) {
full = false;
if (options[0].indexOf(',') > 0) {
// 按照逗號切分key
keys = options[0].split(",");
for (int i = 0; i < keys.length; i++) {
keys[i] = keys[i].trim();
}
key = null;
} else {
keys = null;
key = options[0];
}
} else {
full = true;
key = null;
keys = null;
}
}
// 格式化
public void format(final LogEvent event, final StringBuilder toAppendTo) {
final ReadOnlyStringMap contextData = event.getContextData();
// if there is no additional options, we output every single
// Key/Value pair for the MDC in a similar format to Hashtable.toString()
// 如果沒有附加的屬性,我們輸出每一個單獨的MDC配置的key/value對,類似與Hashtable.toString()的格式
if (full) {
if (contextData == null || contextData.size() == 0) {
toAppendTo.append("{}");
return;
}
appendFully(contextData, toAppendTo);
} else {
if (keys != null) {
if (contextData == null || contextData.size() == 0) {
toAppendTo.append("{}");
return;
}
// 存在附加屬性配置
appendSelectedKeys(keys, contextData, toAppendTo);
} else if (contextData != null){
// otherwise they just want a single key output
final Object value = contextData.getValue(key);
if (value != null) {
StringBuilders.appendValue(toAppendTo, value);
}
}
}
}
我們配置了%X擴展即存在附加屬性配置
// 按照配置的MDC keys輸出,輸出格式為{key=value,key2=value2}
private static void appendSelectedKeys(final String[] keys, final ReadOnlyStringMap contextData, final StringBuilder sb) {
// Print all the keys in the array that have a value.
final int start = sb.length();
sb.append('{');
for (int i = 0; i < keys.length; i++) {
final String theKey = keys[i];
final Object value = contextData.getValue(theKey);
if (value != null) { // !contextData.containskey(theKey)
if (sb.length() - start > 1) {
sb.append(", ");
}
sb.append(theKey).append('=');
StringBuilders.appendValue(sb, value);
}
}
sb.append('}');
}
問題定位后修改配置即可,修改配置后驗證格式符合我們的期望
<Property name="layout">%d %p [%t] %c{1.}:%M:%L %X{myTraceId,} %m%n</Property>
小結(jié)一下:MDC配置的key,日志會按照逗號切分出keys列表,如果keys列表小于等于1則直接輸出一個單獨的value值。如果大于1則按照map的格式輸出,即:{key1=value1,key2=value2}
slf4j輸出日志的語法
slf4j輸出log的語法
1. 直接拼接字符串
用字符串拼接的構(gòu)造方式輸出log,字符串消息還是會被求值,存在類型轉(zhuǎn)換和字符串連接的性能消耗。例:
int index = 1;
logger.info("這是第"+index+"條數(shù)據(jù)");
logger.info("這是第"+String.valueOf(index)+"條數(shù)據(jù)");
輸出結(jié)果:

2. 使用SLF4J的格式化功能
這種用法不存在上面提到的缺點。SLF4J使用自己的格式化語法{},同時提供了適合不同參數(shù)個數(shù)的方法重載:
logger.debug(String format, Object param); //支持一個參數(shù) logger.debug(String format, Object param1, Object param2); //支持兩個參數(shù) logger.debug(String format, Object… param); //任意數(shù)量參數(shù),構(gòu)造參數(shù)數(shù)組具有一定的性能損耗
例:
int index1=1;int index2=2;i
logger.info("這是第{}條數(shù)據(jù)",index1);
logger.info("這是第{}、{}條數(shù)據(jù)",index1,index2);
輸出:

3. 格式化占位符的轉(zhuǎn)義
連續(xù)的{}才被認(rèn)為是格式化占位符
例:
logger.info("{1,2} 這是第{}條數(shù)據(jù)",index2);
logger.info("{1,2} 這是第{{}}條數(shù)據(jù)",index2);
輸出:

用”\”轉(zhuǎn)義{}占位符
例:
/**用”\”轉(zhuǎn)義{}占位符*/
logger.info("\\{} 這是第{}條數(shù)據(jù) ",index2);
/**用“\”本身轉(zhuǎn)義“{}”中的”\”*/
logger.info("\\\\{} 這是第{}條數(shù)據(jù) ",index3);
輸出:

4. log前做條件判斷
isDebugEnabled()的方法在debug disabled的情況下不存在構(gòu)造字符串參數(shù)的性能消耗,但是如果debug enabled,debug是否被enabled將會被求值兩次:
- 一次是isDebugEnabled(),
- 一次是debug()本身(該影響較小,因為求值logger狀態(tài)花費的時間比真正log一條語句花費的時間的1%都還要小)。
例:
if(logger.isDebugEnabled()){
logger.info("這是第{}條數(shù)據(jù) ",index2);
}
輸出:

5、打印異常堆棧
logger.error("Failed to format {}", s, e);
slf4j總結(jié)
slf4j是Java的一種Log Api,類似Apache Commons Logging 。
官網(wǎng)介紹:http://www.slf4j.org/.
在SLF4J中,不需要進行字符串拼接,不會導(dǎo)致使用臨時字符串帶來的消耗。
相反,我們使用帶占位符的模板消息來記錄日志信息,并提供實際值作為參數(shù)??梢允褂脦?shù)版的日志方法,也可以通過Object數(shù)組傳入。在為日志信息產(chǎn)生最終的字符串之前,該方法會檢查是否開啟了特定的日志級別,這不僅降低了內(nèi)存占用,而且預(yù)先減少了執(zhí)行字符串拼接所消耗的CPU時間。
log.debug("Found {} records matching filter: '{}'", records, filter);//slf4j
log.debug("Found " + records + " records matching filter: '" + filter + "'");//log4j
可以看出SLF4J的優(yōu)點有:
更簡略易讀;
在日志級別不夠時,少了字符串拼接的開銷,不會調(diào)用對象(records/filter)的toString方法。通過使用日志記錄方法,直到你使用到的時候,才會去構(gòu)造日志信息(字符串),這就同時提高了內(nèi)存和CPU的使用率。
Slf4j在1.6.0之后,更是支持了異常堆棧的打印,作為最后一個參數(shù)傳入即可,基本滿足了日志的常見打印場景。
在你的開源庫或者私有庫中使用SLF4J,可以使它獨立于任何的日志實現(xiàn),這就意味著不需要管理多個庫和多個日志文件。
SLF4J提供了占位日志記錄,通過移除對isDebugEnabled(), isInfoEnabled()等等的檢查提高了代碼的可讀性。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
springboot動態(tài)加載jar包動態(tài)配置實例詳解
這篇文章主要給大家介紹了關(guān)于springboot動態(tài)加載jar包動態(tài)配置的相關(guān)資料,在項目開發(fā)的過程中,有時候需要動態(tài)靈活的加載某個jar包并執(zhí)行其里面的方法的時候,需要的朋友可以參考下2023-11-11
SpringBoot整合SpringSecurityOauth2實現(xiàn)鑒權(quán)動態(tài)權(quán)限問題
這篇文章主要介紹了SpringBoot整合SpringSecurityOauth2實現(xiàn)鑒權(quán)-動態(tài)權(quán)限,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-06-06
SpringBoot2底層注解@ConfigurationProperties配置綁定
這篇文章主要介紹了SpringBoot2底層注解@ConfigurationProperties配置綁定,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-05-05
Java微信公眾平臺開發(fā)(14) 微信web開發(fā)者工具使用
這篇文章主要為大家詳細(xì)介紹了Java微信公眾平臺開發(fā)第十四步,微信web開發(fā)者工具的使用方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04

