一個web項目一般提供不少URL訪問地址,html
項目通常都是分層處理,例如Controller——>Service——>DAO。java
若是想根據日誌查看用戶一次請求都走了哪些方法(多數是查錯誤)。web
若是系統是多人訪問,日誌打印是無序的,想要串行拿到日誌是大海撈針。spring
能經過一個字符串就能將一次請求調用了哪些方法按照順序搜索出來就行了。apache
Slf4j的MDC ( Mapped Diagnostic Contexts )專門爲此需求而生。app
MDC的基本原理是:dom
經過一個ThreadLocal保存設置的key值,在打印的時候從ThreadLocal中獲取到打印出來。由此可知,若是一個請求中的某些方法調用是在另外的線程中執行,那MDC是獲取不到該值的。
爲何最開始子線程會獲得父線程MDC設置的內容? 建立子線程的時候會調用init(ThreadGroup g, Runnable target, String name,long stackSize)方法,判斷若是parent.inheritableThreadLocals不爲null就調用createInheritedMap方法把父線程的ThreadLocal裏保存的變量都加載到子線程的ThreadLocal中去。 因此MDC類的靜態變量mdcAdapter(LogbackMDCAdapter實現)中的copyOnInheritThreadLocal會獲得父類MDC寫入的內容,由於它用的是InheritableThreadLocal這個繼承類。
底層代碼分佈式
/** * This class hides and serves as a substitute for the underlying logging * system's MDC implementation. * * <p> * If the underlying logging system offers MDC functionality, then SLF4J's MDC, * i.e. this class, will delegate to the underlying system's MDC. Note that at * this time, only two logging systems, namely log4j and logback, offer MDC * functionality. For java.util.logging which does not support MDC, * {@link BasicMDCAdapter} will be used. For other systems, i.e. slf4j-simple * and slf4j-nop, {@link NOPMDCAdapter} will be used. * * <p> * Thus, as a SLF4J user, you can take advantage of MDC in the presence of log4j, * logback, or java.util.logging, but without forcing these systems as * dependencies upon your users. * * <p> * For more information on MDC please see the <a * href="http://logback.qos.ch/manual/mdc.html">chapter on MDC</a> in the * logback manual. * * <p> * Please note that all methods in this class are static. * * @author Ceki Gülcü * @since 1.4.1 */ public class MDC {
/** * Basic MDC implementation, which can be used with logging systems that lack * out-of-the-box MDC support. * * This code was initially inspired by logback's LogbackMDCAdapter. However, * LogbackMDCAdapter has evolved and is now considerably more sophisticated. * * @author Ceki Gulcu * @author Maarten Bosteels * @author Lukasz Cwik * * @since 1.5.0 */ public class BasicMDCAdapter implements MDCAdapter { private InheritableThreadLocal<Map<String, String>> inheritableThreadLocal = new InheritableThreadLocal<Map<String, String>>() { @Override protected Map<String, String> childValue(Map<String, String> parentValue) { if (parentValue == null) { return null; } return new HashMap<String, String>(parentValue); } }; /** * Put a context value (the <code>val</code> parameter) as identified with * the <code>key</code> parameter into the current thread's context map. * Note that contrary to log4j, the <code>val</code> parameter can be null. * * <p> * If the current thread does not have a context map it is created as a side * effect of this call. * * @throws IllegalArgumentException * in case the "key" parameter is null */ public void put(String key, String val) { if (key == null) { throw new IllegalArgumentException("key cannot be null"); } Map<String, String> map = inheritableThreadLocal.get(); if (map == null) { map = new HashMap<String, String>(); inheritableThreadLocal.set(map); } map.put(key, val); }
/** * This class extends <tt>ThreadLocal</tt> to provide inheritance of values * from parent thread to child thread: when a child thread is created, the * child receives initial values for all inheritable thread-local variables * for which the parent has values. Normally the child's values will be * identical to the parent's; however, the child's value can be made an * arbitrary function of the parent's by overriding the <tt>childValue</tt> * method in this class. * * <p>Inheritable thread-local variables are used in preference to * ordinary thread-local variables when the per-thread-attribute being * maintained in the variable (e.g., User ID, Transaction ID) must be * automatically transmitted to any child threads that are created. * * @author Josh Bloch and Doug Lea * @see ThreadLocal * @since 1.2 */ public class InheritableThreadLocal<T> extends ThreadLocal<T> { /** * Computes the child's initial value for this inheritable thread-local * variable as a function of the parent's value at the time the child * thread is created. This method is called from within the parent * thread before the child is started. * <p> * This method merely returns its input argument, and should be overridden * if a different behavior is desired. * * @param parentValue the parent thread's value * @return the child thread's initial value */ protected T childValue(T parentValue) { return parentValue; }
只須要在日誌配置文件的pattern中中添加一個{key},在請求方法入口設置一個key=某字符串,logger日誌就能輸出此字符串。logger的全部日誌方法不須要作任何改動。以下所示。ide
<?xml version="1.0" encoding="UTF-8" ?> <configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <layout class="ch.qos.logback.classic.PatternLayout"> <pattern>%d{ISO8601} [%thread] [%-5level] [%-10X{tracing_id}] %logger - %msg%n</pattern> </layout> </appender> <!-- 文件輸出日誌 (文件大小策略進行文件輸出,超過指定大小對文件備份)--> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>/data/rec_loader/logs/rec_loader.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>/data/rec_loader/logs/rec_loader.log.%d{yyyy-MM-dd}.%d{hh-mm-ss} </fileNamePattern> <maxHistory>7</maxHistory> <totalSizeCap>100GB</totalSizeCap> </rollingPolicy> <layout class="ch.qos.logback.classic.PatternLayout"> <!--** 改動在此處 tracing_id**--> <Pattern>%d{ISO8601} [%thread] [%-5level] [%-10X{tracing_id}] %logger - %msg%n</Pattern> </layout> </appender> <root level="info"> <appender-ref ref="STDOUT"/> <appender-ref ref="FILE"/> </root> </configuration>
import java.util.Objects; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.MDC; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; /* * * * Starry Starry Night * * __ __ * * \/---\/ * * ). .( * * ( (") ) * * ) ( * * / \ * * ( )`` * * ( \ /-\ / ) * * w'W W'w * * * author 楊春煉 * email qdcccc@gmail.com * date 2018-08-18 * */ @Slf4j public class ResponseMetricsInterceptor extends HandlerInterceptorAdapter { private static final String TRACING_ID = "tracing_id"; private static final String RESPONSE_TIME = "response_time"; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String tracingId; //this judgement used for webs context if (StringUtils.isBlank(request.getHeader(TRACING_ID))) { tracingId = RandomStringUtils.randomAlphanumeric(10); } else { tracingId = request.getHeader(TRACING_ID); } response.setHeader(TRACING_ID, tracingId); //add a start time in request attribute request.setAttribute(RESPONSE_TIME, System.currentTimeMillis()); MDC.put(TRACING_ID, tracingId); log.info("tracing.start.url={}", request.getServletPath()); return super.preHandle(request, response, handler); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { try { Object attribute = request.getAttribute(RESPONSE_TIME); Long timeStart = Long.valueOf(Objects.toString(attribute, "0")); long responseTime = System.currentTimeMillis() - timeStart; log.info("tracing.end.url={}, 消耗時間:{}ms", request.getServletPath(), responseTime); } catch (Exception ignored) { } finally { //prevent memory leak MDC.remove(TRACING_ID); MDC.clear(); } super.afterCompletion(request, response, handler, ex); }
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; /* * * * Starry Starry Night * * __ __ * * \/---\/ * * ). .( * * ( (") ) * * ) ( * * / \ * * ( )`` * * ( \ /-\ / ) * * w'W W'w * * * author 楊春煉 * email qdcccc@gmail.com * date 2018-08-18 * */ @Configuration public class InterceptorConfig extends WebMvcConfigurationSupport { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new ResponseMetricsInterceptor()).addPathPatterns("/**"); } }
向本身的項目發起一次http請求this
結果以下圖(順便打印了該次http請求的總耗時)
能夠自行搜索 SOFATracer,比較重,以爲通常企業不必應用。