使用log4j MDC实现日志追踪

log4j MDC实现日志追踪

MDC 中包含的可以被同一线程中执行的代码所访问内容。当前线程的子线程会继承其父线程中的 MDC 的内容。记录日志时,只需要从 MDC 中获取所需的信息即可。

作用:

使用MDC来记录日志,可以规范多开发下日志格式。

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> CTX_HOLDER = new ThreadLocal>();
    static {
        CTX_HOLDER.set(new HashMap());
    }
 
    /**
     * 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 ctx = CTX_HOLDER.get();
        if (ctx == null) {
            return;
        }
        ctx.put(key, value);
    }
    /**
     * 从线程上下文中获取内容
     *
     * @param key
     */
    @SuppressWarnings("unchecked")
    public final static  T getContext(String key) {
        Map ctx = CTX_HOLDER.get();
        if (ctx == null) {
            return null;
        }
        return (T) ctx.get(key);
    }
    /**
     * 获取线程上下文
     */
    public final static Map getContext() {
        Map ctx = CTX_HOLDER.get();
        if (ctx == null) {
            return null;
        }
        return ctx;
    }
    /**
     * 删除上下文中的key
     *
     * @param key
     */
    public final static void remove(String key) {
        Map ctx = CTX_HOLDER.get();
        if (ctx != null) {
            ctx.remove(key);
        }
    }
    /**
     * 上下文中是否包含此key
     *
     * @param key
     * @return
     */
    public final static boolean contains(String key) {
        Map 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());
    }
    /**
     * 设置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);
    }
}

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 标记组件的加载顺序
@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

    /**
     * 请求上下文,应该在最外层
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean requestContextRepositoryFilterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new ContextFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        return filterRegistrationBean;
    }

5、修改log4j日志配置文件,设置日志traceId



    
    
    
    
    
    
    
        
            ${FILE_LOG_PATTERN}
        
        ${LOG_FILE}${LOG_FILE_SUFFIX}
        
            ${LOG_FILE}.%d{yyyy-MM-dd}${LOG_FILE_SUFFIX}
        
    
    
        127.0.0.1
        local6
        514
        ${FILE_LOG_PATTERN}
    
    
    
    
        
        
        
    

log4j2实现日志跟踪

日志跟踪

在每条日志前添加一个随机字符串并且确保同一个请求的字符串相同。如下:c6019df137174d2b98631474db4156b7为此次请求的标识。通过次标识可以查询到所有该请求的日志信息

[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]-

同时可以将此标识返回给前端,便于问题查询。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

我们可以通过过滤器实现以上的功能

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;
        
        // 生成一个随机数给到前端
        String traceId = UUID.randomUUID().toString().replace("-", "");
        // 随机数放到此线程的上下文中,可以在每条日志前加入。具体看下面log4j2.xml
        ThreadContext.put(TRACE_ID, traceId);
        // 随机数放到Header中,在Response Headers中可查看到此数据
        resp.addHeader(TRACE_ID, traceId);  
        filterChain.doFilter(req, resp);
        ThreadContext.clearAll();
    }
    @Override
    public void destroy() {
        System.out.println("过滤器,被销毁:"+ description);
    }
}

log4j2.xml







    
    
    
        logs
    
    
    
    
        
        
            
            
        
    
    
    
        
        
        
        
        
        
            
        
        
        
            
        
    

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

你可能感兴趣的:(使用log4j MDC实现日志追踪)