SpringBoot2.x整合logback日志框架(2)—layout和MDC機制

JAVA && Spring && SpringBoot2.x — 學習目錄

如何根據(jù)日志文件快速定位到應用的行為。

1. layout

Layout組件:([累奧特] 布局),將日志事件進行格式化,返回一個字符串

1.1 常用的轉(zhuǎn)換詞

轉(zhuǎn)換詞 備注
C{length},class{length} 輸出發(fā)出日志請求調(diào)用者的完整類名。注:類名最右邊部分不會省略,即使長度超過了設定了length長度。
contextName 日志記錄器上下文的名字
d{pattern},date{pattern},d{pattern,timezone},date{pattern,timezone} 日志事件的日期,日期轉(zhuǎn)換詞需要一個格式化字符串作為參數(shù),格式化字符串語義與java.text.SimpleDateFormat相同。
F/file 日志請求的java源文件名,一般生成文件信息比較慢,因此一般不使用這個轉(zhuǎn)換詞
caller{depth} 輸出日志調(diào)用者位置信息
L/line 日志請求的行號,由于生成行號信息比較慢,一般不使用這個轉(zhuǎn)換詞
m/msg/message 日志事件相關(guān)的應用提供信息
M/method 輸出與日志事件相關(guān)的應用提供的信息
n 輸出與平臺獨立的行分割符,等價于"\n"或者"\r\n"
p/le/level 輸出日志級別
r/relative(相對的) 輸出日志事件的相對時間
t/thread[斯亂德] 輸出產(chǎn)生日志事件的線程名
X{key:-defaultVal} 輸出與產(chǎn)生日志事件線程相關(guān)的MDC(mapped diagnosis context)。如果mdc轉(zhuǎn)換詞后面花括號中有key,那么value就會輸出,如果值為空,那么輸出默認值;若沒有默認值,則輸出空字符串。
ex{depth}[帶婆斯] 若發(fā)生異常時,指定輸出異常的行數(shù)。
replace(p){r,t} 用't'替換p中正則表達式r的內(nèi)容,例如:%replace('%m'){'\s',''}會去替換事件消息中包含的所有空格

詳細請見:https://blog.csdn.net/lingbomanbu_lyl/article/details/89852037

1.2 自定義layout

  1. 繼承自LayoutBase接口即可:
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.LayoutBase;

/**
 * @ClassName MySampleLayout
 * @Description 日志布局器
 * @Date 2019/7/1
 * @Version 1.0
 **/
public class MySampleLayout extends LayoutBase<ILoggingEvent> {
    @Override
    public String doLayout(ILoggingEvent event) {
        StringBuffer sb=new StringBuffer();
        sb.append(event.getTimeStamp()-event.getLoggerContextVO().getBirthTime());
        sb.append(" [").append(event.getThreadName()).append("] ");
        sb.append(event.getLoggerName()).append("-");
        sb.append(event.getFormattedMessage()).append(CoreConstants.LINE_SEPARATOR);
        return sb.toString();
    }
}
  1. 配置文件
<configuration>
    <springProperty scope="context" name="logPath" source="log.out.path" defalutValue="/app/test.log"/>
    <!-- %m輸出的信息,%p日志級別,%t線程名,%d日期,%c類的全名,%i索引【從數(shù)字0開始遞增】,,, -->
    <!-- appender是configuration的子節(jié)點,是負責寫日志的組件。 -->
    <!-- ConsoleAppender:把日志輸出到控制臺 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="com.tellme.layout.MySampleLayout"/>
            <!--<pattern>%d %p [%r] [%t]  [%X{traceRootId}] (%file:%line\): %m%n</pattern>-->
            <!--&lt;!&ndash; 控制臺也要使用UTF-8,不要使用GBK,否則會中文亂碼 &ndash;&gt;-->
            <!--<charset>UTF-8</charset>-->
        </encoder>
    </appender>
</configuration>
  1. 輸出格式:
1230 [restartedMain] com.MmWebApplication-The following profiles are active: test

若包含自定義參數(shù)的layout組件

  1. java實現(xiàn)類
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.LayoutBase;
import org.apache.commons.lang3.StringUtils;

/**
 * @ClassName CustomLayout
 * @Description 自定義Layout
 * @Date 2019/7/1
 * @Version 1.0
 **/
public class CustomLayout extends LayoutBase<ILoggingEvent> {

    private String prefix = null;
    boolean printThreadName = true;

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

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

    @Override
    public String doLayout(ILoggingEvent event) {

        StringBuffer sbuf = new StringBuffer(128);
        if (StringUtils.isNotBlank(prefix)) {
            sbuf.append(prefix).append(" ");
        }
        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(CoreConstants.LINE_SEPARATOR);
        return sbuf.toString();
    }
}
  1. 配置文件
<configuration>
    <springProperty scope="context" name="logPath" source="log.out.path" defalutValue="/app/test.log"/>
    <!-- %m輸出的信息,%p日志級別,%t線程名,%d日期,%c類的全名,%i索引【從數(shù)字0開始遞增】,,, -->
    <!-- appender是configuration的子節(jié)點,是負責寫日志的組件。 -->
    <!-- ConsoleAppender:把日志輸出到控制臺 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="com.tellme.layout.CustomLayout">
                <prefix>MyPrefix</prefix>
                <printThreadName>false</printThreadName>
            </layout>
            <!--<pattern>%d %p [%r] [%t]  [%X{traceRootId}] (%file:%line\): %m%n</pattern>-->
            <!--&lt;!&ndash; 控制臺也要使用UTF-8,不要使用GBK,否則會中文亂碼 &ndash;&gt;-->
            <!--<charset>UTF-8</charset>-->
        </encoder>
    </appender>
</configuration>
  1. 輸出格式
MyPrefix 1405 INFO com.MmWebApplication - The following profiles are active: test

2. MDC

logback內(nèi)置的日志字段還是比較少的,如果我們需要打印有關(guān)業(yè)務的更多內(nèi)容,包括自定義的一些數(shù)據(jù),需要借助logback的MDC機制,(映射診斷上下文),即將一些運行時的上下文數(shù)據(jù)通過logback打印出來;此時我們需要借助org.sl4j.MDC類。

MDC類的原理:其內(nèi)部持有一個InheritableThreadLocal [因海瑞特包 遺傳]實例,用于保存context數(shù)據(jù),MDC提供了put/get/clear等幾個接口,用來操作ThreadLocal中的數(shù)據(jù);ThreadLocal中的K-V,可以在logback.xml中聲明,最終會打印在日志中。

MDC.put("userId",1000);

那么在logback.xml中,即可在layout中通過"%X{userId}"來打印此信息。

注意:因為使用的是ThreadLocal,在需要線程退出之前,需要清除(clear)MDC里面的數(shù)據(jù);在線程池中使用MDC時,在子線程退出之前調(diào)用MDC.clear()方法。

2.1 MDC實踐

public class LogMDCUtil {
    //接口名
    private final static String SVR_ID = "SVR_ID";

    private final static Logger logger = LoggerFactory.getLogger(LogMDCUtil.class);
/**
     * 為日志設置服務名
     *
     * @param serviceId
     */
    public static void setServiceNameToLog(String serviceId) {
        try {
            
            MDC.put(SVR_ID, serviceId);
        } catch (Exception e) {
            logger.error("【Logback設置MDC】-設置服務名失敗", e);
        }
    }

    /**
     * 刪除ThreadLocal中關(guān)于ServiceName的數(shù)據(jù),防止內(nèi)存溢出
     */
    public static void removeServiceNameToLog() {
        //線程執(zhí)行完畢,需要刪除ServiceName
        try {
            MDC.remove(SVR_ID);
        } catch (Exception e) {
            logger.error("【Logback設置MDC】-刪除服務名失敗", e);
        }
    }
}

配置文件:

 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d %p [%t %X{SVR_ID}] (%C{0}:%line\): %m%n</pattern>
            <!-- 控制臺也要使用UTF-8,不要使用GBK,否則會中文亂碼 -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>

2.2 MDC實戰(zhàn)—為請求生成唯一編號

如何為每一請求生成唯一編號,并在日志中打印出來呢?

  1. 借助Redis自增命令,獲取唯一編號
@Service
public class GainIdService {

    private static Logger logger = LoggerFactory.getLogger(GainIdService.class);

    @Resource
    private StringRedisTemplate template;

    //前綴 不同系統(tǒng)的前綴可以不相同
    public String gainId(String prefix) {
        Long seq = template.opsForValue().increment("system:idseq:" + pre.getPrefix());
        DecimalFormat df = new DecimalFormat("0000");
        String str = df.format(seq);
        //substring(int beginIndex),[beginIndex,endIndex]的子串,即獲取后四位編號
        str = str.substring(str.length() - 4);
        SimpleDateFormat sf=new SimpleDateFormat("yyyyMMddHHmmss");
        String timeStr = sf.format(new Date());
        return pre.getPrefix() + timeStr  + str;
    }
}
  1. 在SpringBoot攔截器中放入MDC中

springBoot—攔截器詳解

@Component
public class LogInterceptor implements HandlerInterceptor {

    private final static String REQ_ID = "REQ_ID";

    @Autowired
    GainIdService gainIdService;

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        String traceId = gainIdService.gainId(IdPrefixEnum.REQ_ID);
        MDC.put(REQ_ID, traceId);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
        MDC.remove(REQ_ID);
    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }
}
  1. 將攔截器配置到系統(tǒng)中
@Configuration
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {

    @Autowired
    private LogInterceptor logInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(logInterceptor);
    }
}

參考文檔

(譯)(五)Logback中的Layout

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

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

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