介紹:
MDC 中包含的能夠被同一線程中執行的代碼所訪問內容。當前線程的子線程會繼承其父線程中的 MDC 的內容。記錄日誌時,只須要從 MDC 中獲取所需的信息便可。
做用:
使用MDC來記錄日誌,能夠規範多開發下日誌格式。java
一:新建線程處理類 ThreadContextweb
import java.io.Serializable; import java.util.HashMap; import java.util.Map; import java.util.Optional; /** * 線程上下文 * * @date 2017年3月1日 * @since 1.0.0 */ public class ThreadContext { /** * 線程上下文變量的持有者 */ private final static ThreadLocal<Map<String, Object>> CTX_HOLDER = new ThreadLocal<Map<String, Object>>(); static { CTX_HOLDER.set(new HashMap<String, Object>()); } /** * traceID */ private final static String TRACE_ID_KEY = "traceId"; /** * 會話ID */ private final static String SESSION_KEY = "token"; /** * 操做用戶ID */ private final static String VISITOR_ID_KEY = "userId"; /** * 操做用戶名 */ private final static String VISITOR_NAME_KEY = "userName"; /** * 客戶端IP */ private static final String CLIENT_IP_KEY = "clientIp"; /** * 添加內容到線程上下文中 * * @param key * @param value */ public final static void putContext(String key, Object value) { Map<String, Object> ctx = CTX_HOLDER.get(); if (ctx == null) { return; } ctx.put(key, value); } /** * 從線程上下文中獲取內容 * * @param key */ @SuppressWarnings("unchecked") public final static <T extends Object> T getContext(String key) { Map<String, Object> ctx = CTX_HOLDER.get(); if (ctx == null) { return null; } return (T) ctx.get(key); } /** * 獲取線程上下文 */ public final static Map<String, Object> getContext() { Map<String, Object> ctx = CTX_HOLDER.get(); if (ctx == null) { return null; } return ctx; } /** * 刪除上下文中的key * * @param key */ public final static void remove(String key) { Map<String, Object> ctx = CTX_HOLDER.get(); if (ctx != null) { ctx.remove(key); } } /** * 上下文中是否包含此key * * @param key * @return */ public final static boolean contains(String key) { Map<String, Object> ctx = CTX_HOLDER.get(); if (ctx != null) { return ctx.containsKey(key); } return false; } /** * 清空線程上下文 */ public final static void clean() { CTX_HOLDER.remove(); } /** * 初始化線程上下文 */ public final static void init() { CTX_HOLDER.set(new HashMap<String, Object>()); } /** * 設置traceID數據 */ public final static void putTraceId(String traceId) { putContext(TRACE_ID_KEY, traceId); } /** * 獲取traceID數據 */ public final static String getTraceId() { return getContext(TRACE_ID_KEY); } /** * 設置會話的用戶ID */ public final static void putUserId(Integer userId) { putContext(VISITOR_ID_KEY, userId); } /** * 設置會話的用戶ID */ public final static int getUserId() { Integer val = getContext(VISITOR_ID_KEY); return val == null ? 0 : val; } /** * 設置會話的用戶名 */ public final static void putUserName(String userName) { putContext(VISITOR_NAME_KEY, userName); } /** * 獲取會話的用戶名稱 */ public final static String getUserName() { return Optional.ofNullable(getContext(VISITOR_NAME_KEY)) .map(name -> String.valueOf(name)) .orElse(""); } /** * 取出IP * * @return */ public static final String getClientIp() { return getContext(CLIENT_IP_KEY); } /** * 設置IP * * @param ip */ public static final void putClientIp(String ip) { putContext(CLIENT_IP_KEY, ip); } /** * 設置會話ID * * @param token */ public static void putSessionId(String token) { putContext(SESSION_KEY, token); } /** * 獲取會話ID * * @param token */ public static String getSessionId(String token) { return getContext(SESSION_KEY); } /** * 清空會話數據 */ public final static void removeSessionId() { remove(SESSION_KEY); } }
二:添加工具類TraceUtilspring
import java.util.UUID; import org.slf4j.MDC; import ThreadContext; /** * trace工具 * * @date 2017年3月10日 * @since 1.0.0 */ public class TraceUtil { public static void traceStart() { ThreadContext.init(); String traceId = generateTraceId(); MDC.put('traceId', traceId); ThreadContext.putTraceId(traceId); } public static void traceEnd() { MDC.clear(); ThreadContext.clean(); } /** * 生成跟蹤ID * * @return */ private static String generateTraceId() { return UUID.randomUUID().toString(); } }
三:添加ContextFilter,對於每一個請求隨機生成RequestID並放入MDCsql
import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.web.filter.OncePerRequestFilter; import com.pingan.manpan.common.util.TraceUtil; import com.pingan.manpan.user.dto.ThreadContext; import com.pingan.manpan.web.common.surpport.IpUtils; /** * 上下文Filter * * @date 2017/3/10 * @since 1.0.0 */ //@Order 標記組件的加載順序 @Order(Ordered.HIGHEST_PRECEDENCE) public class ContextFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { ThreadContext.putClientIp(IpUtils.getClientIp(request)); TraceUtil.traceStart(); filterChain.doFilter(request, response); } finally { TraceUtil.traceEnd(); } } }
四:在webConfiguriation註冊filterapp
/** * 請求上下文,應該在最外層 * * @return */ @Bean public FilterRegistrationBean requestContextRepositoryFilterRegistrationBean() { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); filterRegistrationBean.setFilter(new ContextFilter()); filterRegistrationBean.addUrlPatterns("/*"); return filterRegistrationBean; }
五:修改log4j日誌配置文件,設置日誌traceIddom
<?xml version="1.0" encoding="UTF-8"?> <configuration> <jmxConfigurator/> <property name="LOG_LEVEL_PATTERN" value="%X{traceId:-} %5p"/> <property name="LOG_FILE" value="${LOG_PATH}/web.logx"/> <property name="LOG_FILE_SUFFIX" value=".logx"/> <include resource="org/springframework/boot/logging/logback/defaults.xml"/> <include resource="org/springframework/boot/logging/logback/console-appender.xml"/> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <encoder> <pattern>${FILE_LOG_PATTERN}</pattern> </encoder> <file>${LOG_FILE}${LOG_FILE_SUFFIX}</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}${LOG_FILE_SUFFIX}</fileNamePattern> </rollingPolicy> </appender> <appender name="SYSLOG" class="ch.qos.logback.classic.net.SyslogAppender"> <syslogHost>127.0.0.1</syslogHost> <facility>local6</facility> <port>514</port> <suffixPattern>${FILE_LOG_PATTERN}</suffixPattern> </appender> <logger name="druid.sql" level="DEBUG" /> <root level="INFO"> <appender-ref ref="CONSOLE"/> <appender-ref ref="FILE"/> <appender-ref ref="SYSLOG"/> </root> </configuration>