使用log4j MDC實現(xiàn)日志追蹤
log4j MDC實現(xiàn)日志追蹤
MDC 中包含的可以被同一線程中執(zhí)行的代碼所訪問內(nèi)容。當(dāng)前線程的子線程會繼承其父線程中的 MDC 的內(nèi)容。記錄日志時,只需要從 MDC 中獲取所需的信息即可。
作用:
使用MDC來記錄日志,可以規(guī)范多開發(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";
/**
* 會話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è)置會話的用戶ID
*/
public final static void putUserId(Integer userId) {
putContext(VISITOR_ID_KEY, userId);
}
/**
* 設(shè)置會話的用戶ID
*/
public final static int getUserId() {
Integer val = getContext(VISITOR_ID_KEY);
return val == null ? 0 : val;
}
/**
* 設(shè)置會話的用戶名
*/
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);
}
/**
* 設(shè)置IP
*
* @param ip
*/
public static final void putClientIp(String ip) {
putContext(CLIENT_IP_KEY, ip);
}
/**
* 設(shè)置會話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);
}
/**
* 清空會話數(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
對于每個請求隨機生成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注冊filter
/**
* 請求上下文,應(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實現(xiàn)日志跟蹤
日志跟蹤
在每條日志前添加一個隨機字符串并且確保同一個請求的字符串相同。如下:c6019df137174d2b98631474db4156b7為此次請求的標(biāo)識。通過次標(biāo)識可以查詢到所有該請求的日志信息
[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]-
同時可以將此標(biāo)識返回給前端,便于問題查詢。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
我們可以通過過濾器實現(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 對用戶的請求添加日志編號,并將此編號返回給前端,便于日志查詢
*/
@WebFilter(filterName = "Log4j2Filter", urlPatterns = "/*", initParams = {@WebInitParam(name = "DESCRIPTION", value = "這是Log4j2Filter過濾器")})
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("過濾器初始化:"+ description);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException,ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse resp = (HttpServletResponse) servletResponse;
// 生成一個隨機數(shù)給到前端
String traceId = UUID.randomUUID().toString().replace("-", "");
// 隨機數(shù)放到此線程的上下文中,可以在每條日志前加入。具體看下面log4j2.xml
ThreadContext.put(TRACE_ID, traceId);
// 隨機數(shù)放到Header中,在Response Headers中可查看到此數(shù)據(jù)
resp.addHeader(TRACE_ID, traceId);
filterChain.doFilter(req, resp);
ThreadContext.clearAll();
}
@Override
public void destroy() {
System.out.println("過濾器,被銷毀:"+ 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級別為warn-->
<!--日志級別以及優(yōu)先級排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status,這個用于設(shè)置log4j2自身內(nèi)部的信息輸出,可以不設(shè)置,當(dāng)設(shè)置成trace時,你會看到log4j2內(nèi)部各種詳細輸出-->
<!--monitorInterval:Log4j能夠自動檢測修改配置 文件和重新配置本身,設(shè)置間隔秒數(shù)-->
<configuration status="warn" monitorInterval="30">
<!--全局參數(shù)-->
<Properties>
<Property name="logPath">logs</Property>
</Properties>
<!--先定義所有的appender-->
<appenders>
<!--這個輸出控制臺的配置-->
<console name="Console" target="SYSTEM_OUT">
<!-- traceID:就是在過濾器中生成的隨機數(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才會生效-->
<loggers>
<!--過濾掉spring和mybatis的一些無用的debug信息-->
<logger name="org.springframework" level="INFO"></logger>
<logger name="org.mybatis" level="INFO"></logger>
<logger name="com.zaxxer" level="WARN"></logger>
<!-- com.generator開發(fā)/測試環(huán)境用DEBUG,并用控制臺輸出即可 -->
<logger name="com.generator" level="DEBUG" additivity="false">
<appender-ref ref="Console"/>
</logger>
<!-- 未指定的包都按此 level 打印日志 -->
<root level="DEBUG">
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java從ftp服務(wù)器上傳與下載文件的實現(xiàn)
這篇文章主要給大家介紹了關(guān)于Java從ftp服務(wù)器上傳與下載文件的實現(xiàn)方法,最近項目中需要實現(xiàn)將文件先存放到ftp上,需要的時候再從ftp上下載,做的過程中碰到了問題,所以這里總結(jié)下,需要的朋友可以參考下2023-08-08
java socket接收保證能讀完數(shù)據(jù)的實例
這篇文章主要介紹了java socket接收保證能讀完數(shù)據(jù)的實例,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10
Java開發(fā)深入分析講解二叉樹的遞歸和非遞歸遍歷方法
樹是一種重要的非線性數(shù)據(jù)結(jié)構(gòu),直觀地看,它是數(shù)據(jù)元素(在樹中稱為結(jié)點)按分支關(guān)系組織起來的結(jié)構(gòu),很象自然界中的樹那樣。樹結(jié)構(gòu)在客觀世界中廣泛存在,如人類社會的族譜和各種社會組織機構(gòu)都可用樹形象表示,本篇介紹二叉樹的遞歸與非遞歸遍歷的方法2022-05-05
springBoot使用openfeign來遠程調(diào)用的實現(xiàn)
這篇文章主要介紹了springBoot使用openfeign來遠程調(diào)用的實現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03

