使用log4j MDC实现日志追踪

作者:houshiqun689 时间:2022-01-13 13:20:01 

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<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);
   }
}

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


<?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实现日志跟踪

日志跟踪

在每条日志前添加一个随机字符串并且确保同一个请求的字符串相同。如下: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 <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"?>
<!--设置log4j2的自身log级别为warn-->
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration status="warn" monitorInterval="30">

<!--全局参数-->
   <Properties>
       <Property name="logPath">logs</Property>
   </Properties>

<!--先定义所有的appender-->
   <appenders>
       <!--这个输出控制台的配置-->
       <console name="Console" target="SYSTEM_OUT">
           <!-- traceID:就是在过滤器中生成的随机数 -->
           <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开发/测试环境用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>

来源:https://segmentfault.com/a/1190000014236769

标签:log4j,MDC,日志追踪
0
投稿

猜你喜欢

  • Java技巧函数方法实现二维数组遍历

    2023-09-12 23:25:00
  • Spring事务管理方法步骤解析

    2021-12-15 20:09:04
  • SpringBoot整合Spring Data Elasticsearch的过程详解

    2023-01-25 22:39:41
  • 详解Java利用同步块synchronized()保证并发安全

    2021-09-12 12:15:07
  • c# winform时钟的实现代码

    2023-04-05 07:40:53
  • Unity屏幕雪花另类实现方式示例

    2022-06-18 23:32:43
  • IntelliJ IDEA maven 构建简单springmvc项目(图文教程)

    2021-09-13 05:40:40
  • Java使用JDK与Cglib动态代理技术统一管理日志记录

    2021-11-09 00:52:31
  • Java中Excel高效解析工具EasyExcel的实践

    2023-05-28 13:36:38
  • java中方法递归的简单示例

    2022-11-25 11:45:12
  • Java编程关于子类重写父类方法问题的理解

    2022-05-25 05:00:32
  • maven中下载jar包源码和javadoc的命令介绍

    2023-07-27 04:41:01
  • C#使用LitJson解析JSON的示例代码

    2021-11-17 06:35:05
  • c#正反序列化XML文件示例(xml序列化)

    2023-01-06 02:49:57
  • java实现捕鱼达人游戏

    2023-11-23 21:28:59
  • Android application捕获崩溃异常怎么办

    2023-09-26 11:07:12
  • java获取ip地址示例

    2021-12-25 07:04:22
  • 简单实现Java通讯录系统

    2021-08-16 03:28:22
  • Java如何实现树的同构?

    2023-11-28 09:55:19
  • C#中类的使用教程详解

    2023-06-12 05:42:42
  • asp之家 软件编程 m.aspxhome.com