使用log4j MDC實(shí)現(xiàn)日志追蹤
log4j MDC實(shí)現(xiàn)日志追蹤
MDC 中包含的可以被同一線程中執(zhí)行的代碼所訪問(wèn)內(nèi)容。當(dāng)前線程的子線程會(huì)繼承其父線程中的 MDC 的內(nèi)容。記錄日志時(shí),只需要從 MDC 中獲取所需的信息即可。
作用:
使用MDC來(lái)記錄日志,可以規(guī)范多開(kāi)發(fā)下日志格式。
1、新建線程處理類 ThreadContext
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"; /** * 會(huì)話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"; /** * 添加內(nèi)容到線程上下文中 * * @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); } /** * 從線程上下文中獲取內(nèi)容 * * @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>()); } /** * 設(shè)置traceID數(shù)據(jù) */ public final static void putTraceId(String traceId) { putContext(TRACE_ID_KEY, traceId); } /** * 獲取traceID數(shù)據(jù) */ public final static String getTraceId() { return getContext(TRACE_ID_KEY); } /** * 設(shè)置會(huì)話的用戶ID */ public final static void putUserId(Integer userId) { putContext(VISITOR_ID_KEY, userId); } /** * 設(shè)置會(huì)話的用戶ID */ public final static int getUserId() { Integer val = getContext(VISITOR_ID_KEY); return val == null ? 0 : val; } /** * 設(shè)置會(huì)話的用戶名 */ public final static void putUserName(String userName) { putContext(VISITOR_NAME_KEY, userName); } /** * 獲取會(huì)話的用戶名稱 */ 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); } /** * 設(shè)置IP * * @param ip */ public static final void putClientIp(String ip) { putContext(CLIENT_IP_KEY, ip); } /** * 設(shè)置會(huì)話ID * * @param token */ public static void putSessionId(String token) { putContext(SESSION_KEY, token); } /** * 獲取會(huì)話ID * * @param token */ public static String getSessionId(String token) { return getContext(SESSION_KEY); } /** * 清空會(huì)話數(shù)據(jù) */ public final static void removeSessionId() { remove(SESSION_KEY); } }
2、添加工具類TraceUtil
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(); } }
3、添加ContextFilter
對(duì)于每個(gè)請(qǐng)求隨機(jī)生成RequestID并放入MDC
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 標(biāo)記組件的加載順序 @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(); } } }
4、在webConfiguriation注冊(cè)filter
/** * 請(qǐng)求上下文,應(yīng)該在最外層 * * @return */ @Bean public FilterRegistrationBean requestContextRepositoryFilterRegistrationBean() { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); filterRegistrationBean.setFilter(new ContextFilter()); filterRegistrationBean.addUrlPatterns("/*"); return filterRegistrationBean; }
5、修改log4j日志配置文件,設(shè)置日志traceId
<?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>
log4j2實(shí)現(xiàn)日志跟蹤
日志跟蹤
在每條日志前添加一個(gè)隨機(jī)字符串并且確保同一個(gè)請(qǐng)求的字符串相同。如下:c6019df137174d2b98631474db4156b7為此次請(qǐng)求的標(biāo)識(shí)。通過(guò)次標(biāo)識(shí)可以查詢到所有該請(qǐng)求的日志信息
[traceID:c6019df137174d2b98631474db4156b7]-[2020-08-11 19:56:58:204]-[http-nio-8803-exec-4]-
[traceID:c6019df137174d2b98631474db4156b7]-[2020-08-11 19:56:58:204]-[http-nio-8803-exec-4]-
[traceID:c6019df137174d2b98631474db4156b7]-[2020-08-11 19:56:58:205]-[http-nio-8803-exec-4]-
[traceID:c6019df137174d2b98631474db4156b7]-[2020-08-11 19:56:58:205]-[http-nio-8803-exec-4]-
[traceID:c6019df137174d2b98631474db4156b7]-[2020-08-11 19:56:58:209]-[http-nio-8803-exec-4]-
[traceID:c6019df137174d2b98631474db4156b7]-[2020-08-11 19:56:58:214]-[http-nio-8803-exec-4]-
[traceID:c6019df137174d2b98631474db4156b7]-[2020-08-11 19:56:58:223]-[http-nio-8803-exec-4]-
[traceID:c6019df137174d2b98631474db4156b7]-[2020-08-11 19:56:58:224]-[http-nio-8803-exec-4]-
同時(shí)可以將此標(biāo)識(shí)返回給前端,便于問(wèn)題查詢。traceID: c6019df137174d2b98631474db4156b7
Access-Control-Allow-Credentials: true Access-Control-Allow-Origin: http://test.page.qingin.cn Cache-Control: max-age=30 Connection: keep-alive Content-Type: application/json;charset=UTF-8 Date: Tue, 11 Aug 2020 12:02:19 GMT Expires: Tue, 11 Aug 2020 12:02:49 GMT Server: nginx/1.16.1 traceID: c6019df137174d2b98631474db4156b7 Transfer-Encoding: chunked Vary: Origin Vary: Access-Control-Request-Method Vary: Access-Control-Request-Headers
我們可以通過(guò)過(guò)濾器實(shí)現(xiàn)以上的功能
Log4j2Filter.java
package com.generator.admin.filter; import org.apache.logging.log4j.ThreadContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.UUID; /** * @calssName Log4j2Filter * @Description 對(duì)用戶的請(qǐng)求添加日志編號(hào),并將此編號(hào)返回給前端,便于日志查詢 */ @WebFilter(filterName = "Log4j2Filter", urlPatterns = "/*", initParams = {@WebInitParam(name = "DESCRIPTION", value = "這是Log4j2Filter過(guò)濾器")}) public class Log4j2Filter implements Filter { private String description; public static final String TRACE_ID = "traceID"; private static final Logger logger = LoggerFactory.getLogger(Log4j2Filter.class); @Override public void init(FilterConfig filterConfig) throws ServletException { description = filterConfig.getInitParameter("DESCRIPTION"); System.out.println("過(guò)濾器初始化:"+ description); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException,ServletException { HttpServletRequest req = (HttpServletRequest) servletRequest; HttpServletResponse resp = (HttpServletResponse) servletResponse; // 生成一個(gè)隨機(jī)數(shù)給到前端 String traceId = UUID.randomUUID().toString().replace("-", ""); // 隨機(jī)數(shù)放到此線程的上下文中,可以在每條日志前加入。具體看下面log4j2.xml ThreadContext.put(TRACE_ID, traceId); // 隨機(jī)數(shù)放到Header中,在Response Headers中可查看到此數(shù)據(jù) resp.addHeader(TRACE_ID, traceId); filterChain.doFilter(req, resp); ThreadContext.clearAll(); } @Override public void destroy() { System.out.println("過(guò)濾器,被銷毀:"+ description); } }
log4j2.xml <PatternLayout pattern="[traceID:%X{traceID}]-[%d{yyyy-MM-dd HH:mm:ss:SSS}]-[%t]-[%p]-[%l]-%m%n"/>
<?xml version="1.0" encoding="UTF-8"?> <!--設(shè)置log4j2的自身log級(jí)別為warn--> <!--日志級(jí)別以及優(yōu)先級(jí)排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!--Configuration后面的status,這個(gè)用于設(shè)置log4j2自身內(nèi)部的信息輸出,可以不設(shè)置,當(dāng)設(shè)置成trace時(shí),你會(huì)看到log4j2內(nèi)部各種詳細(xì)輸出--> <!--monitorInterval:Log4j能夠自動(dòng)檢測(cè)修改配置 文件和重新配置本身,設(shè)置間隔秒數(shù)--> <configuration status="warn" monitorInterval="30"> <!--全局參數(shù)--> <Properties> <Property name="logPath">logs</Property> </Properties> <!--先定義所有的appender--> <appenders> <!--這個(gè)輸出控制臺(tái)的配置--> <console name="Console" target="SYSTEM_OUT"> <!-- traceID:就是在過(guò)濾器中生成的隨機(jī)數(shù) --> <PatternLayout pattern="[traceID:%X{traceID}]-[%d{yyyy-MM-dd HH:mm:ss:SSS}]-[%t]-[%p]-[%l]-%m%n"/> </console> </appenders> <!--然后定義logger,只有定義了logger并引入的appender,appender才會(huì)生效--> <loggers> <!--過(guò)濾掉spring和mybatis的一些無(wú)用的debug信息--> <logger name="org.springframework" level="INFO"></logger> <logger name="org.mybatis" level="INFO"></logger> <logger name="com.zaxxer" level="WARN"></logger> <!-- com.generator開(kāi)發(fā)/測(cè)試環(huán)境用DEBUG,并用控制臺(tái)輸出即可 --> <logger name="com.generator" level="DEBUG" additivity="false"> <appender-ref ref="Console"/> </logger> <!-- 未指定的包都按此 level 打印日志 --> <root level="DEBUG"> <appender-ref ref="Console"/> </root> </loggers> </configuration>
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
java中實(shí)體類和JSON對(duì)象之間相互轉(zhuǎn)化
Java中關(guān)于Json格式轉(zhuǎn)化Object,Map,Collection類型和String類型之間的轉(zhuǎn)化在我們實(shí)際項(xiàng)目中應(yīng)用的很是普遍和廣泛。最近工作的過(guò)程中也是經(jīng)常有,因此,自己封裝了一個(gè)類分享給大家。2015-05-05Java從ftp服務(wù)器上傳與下載文件的實(shí)現(xiàn)
這篇文章主要給大家介紹了關(guān)于Java從ftp服務(wù)器上傳與下載文件的實(shí)現(xiàn)方法,最近項(xiàng)目中需要實(shí)現(xiàn)將文件先存放到ftp上,需要的時(shí)候再?gòu)膄tp上下載,做的過(guò)程中碰到了問(wèn)題,所以這里總結(jié)下,需要的朋友可以參考下2023-08-08java socket接收保證能讀完數(shù)據(jù)的實(shí)例
這篇文章主要介紹了java socket接收保證能讀完數(shù)據(jù)的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10Java開(kāi)發(fā)深入分析講解二叉樹(shù)的遞歸和非遞歸遍歷方法
樹(shù)是一種重要的非線性數(shù)據(jù)結(jié)構(gòu),直觀地看,它是數(shù)據(jù)元素(在樹(shù)中稱為結(jié)點(diǎn))按分支關(guān)系組織起來(lái)的結(jié)構(gòu),很象自然界中的樹(shù)那樣。樹(shù)結(jié)構(gòu)在客觀世界中廣泛存在,如人類社會(huì)的族譜和各種社會(huì)組織機(jī)構(gòu)都可用樹(shù)形象表示,本篇介紹二叉樹(shù)的遞歸與非遞歸遍歷的方法2022-05-05springBoot使用openfeign來(lái)遠(yuǎn)程調(diào)用的實(shí)現(xiàn)
這篇文章主要介紹了springBoot使用openfeign來(lái)遠(yuǎn)程調(diào)用的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03IDEA項(xiàng)目如何取消git版本管控并添加svn版本控制
在公司內(nèi)部服務(wù)器環(huán)境下,將代碼倉(cāng)庫(kù)從Gitee的Git遷移到SVN可以避免外部版本控制的風(fēng)險(xiǎn),遷移過(guò)程中,先刪除項(xiàng)目的.git文件夾,再通過(guò)Eclipse的設(shè)置界面刪除原Git配置并添加SVN配置,之后,將項(xiàng)目提交到SVN倉(cāng)庫(kù),確保使用ignore列表過(guò)濾不必要的文件2024-10-10Java實(shí)現(xiàn)動(dòng)態(tài)生成GIF圖像詳解
在互聯(lián)網(wǎng)上有許多有趣的場(chǎng)景,其中的一種就是動(dòng)圖。這不是視頻,而是一種GIF圖像信息。本文將利用Java實(shí)現(xiàn)動(dòng)態(tài)生成GIF圖像功能,需要的可以參考一下2022-09-09