使用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


猜你喜欢
- 最近重新研究Java基础知识,发现以前太多知识知识略略带过了,比较说Java的线程机制,在Java中有两类线程:User Thread(用户
- 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。示例 1:输入: 1->1->2输出: 1->2示例 2:输
- 欲达此目的,可以采用下列两种作法: ◆使用XmlConvert类。 ◆将一个XSLT转换套用至DataSet数据的XML表示。 程序范例 本
- 1.线程与进程进程是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,线程则是一个实体,一个进程中至少有一个线程,是CPU
- 一、背景1.1、前言当我们写好代码并测试功能符合要求时,有可能每天都要执行这个程序(比如我写了一个爬虫脚本,每天定时运行获取我想看的小说更新
- 1. 初始 Spring Boot1.1 什么是Spring BootSpring 的诞生是为了简化 Java 程序的开发的Spring B
- Java线程池的几种实现方法和区别介绍import java.text.DateFormat;import java.text.Simple
- 前言在之前的文章我们复习了 ViewGroup 的测量与布局,那么我们这一篇效果就可以在之前的基础上实现一个灵活的九宫格布局。那么一个九宫格
- SlidingDrawer效果想必大家也见到过,它就是1.5模拟器上进入应用程序列表的效果。下面是截图一、简介 SlidingDr
- 前言在Java中,集合和数组是我们经常会用到的数据结构,需要经常对他们做增、删、改、查、聚合、统计、过滤等操作。相比之下,关系型数据库中也同
- 之前有简单介绍过java多线程的使用,已经Thread类和Runnable类,为了更好地理解多线程,本文就Thread进行详细的分析。sta
- 1.问题引出源码: public static void main(String[] args) { List&l
- 引导语Socket 面试最终题一般都是让你写一个简单的客户端和服务端通信的例子,本文就带大家一起来写这个 demo。1、要求可以使用 Soc
- 直接上代码,看下最简单也是最常用的方法,将Object 转为 JSON 以及将Json转为Object方式public class Test
- 本文实例讲述了C#中数组段用法。分享给大家供大家参考。具体分析如下:1.数组段说明① 结构ArraySegment<T>表示数组
- 一、dfs(深度优先搜索)1.图的dfs/** * 深度优先搜索 * * @param node * @param set */publi
- 我们知道:int i = 5;long j = 7;i = i + j不能编译,但i += j却能编译运行,结果i = 12。这是因为:i
- 本文实例为大家分享了java实现五子棋游戏的具体代码,供大家参考,具体内容如下此游戏具有双人对战功能和人机对战功能一、游戏界面的实现一个游戏
- 今天在工作中突然遇到这个问题,开始郁闷得不行,查阅了很多资料才解决。话不多少先上图①解决连接超时问题1:在Linux下输入命令ifconfi
- 本文实例为大家分享了SpringBoot使用POI进行Excel下载的具体代码,供大家参考,具体内容如下使用poi处理Excel特别方便,此