系统日志与用户信息绑定实现日志跟踪
一、需求背景
微服务环境下,在所有微服务日志中识别用户本次访问的日志,用于用户操作日志跟踪。
二、技术实现
在用户登录时,生成一个唯一ID,用户ID+时间戳,假设唯一ID的键为userTraceId,并将其存储到MDC(Mapped Diagnostic Context)中,MDC 是Logback`支持的线程上下文存储机制,适合在日志中动态传递变量。
1.修改 logback-spring.xml
添加%X{userTraceId},:
<property name="SYSLOG" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%level] [%msg]%n [%X{userTraceId}]"/>
%X{userTraceId}会从MDC中获取userTraceId值,若不存在则为空。
2.设置拦截器
用户登录的拦截器中设置MDC值,确保在请求结束时清理MDC,避免线程池复用导致的上下文污染。
@Component
public class UserTraceInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {String userId = extractUserId(request);if (userId != null) {// 生成唯一userTraceId,例如:userId + 时间戳 + 随机UUIDString userTraceId = userId + "-" + System.currentTimeMillis() + "-" + UUID.randomUUID().toString();MDC.put("userTraceId", userTraceId);} else {// 如果没有用户信息,可以设置默认值或空MDC.put("userTraceId", "anonymous-" + UUID.randomUUID().toString());}return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {// 清理MDC,防止线程复用导致上下文污染MDC.remove("userTraceId");}private String extractUserId(HttpServletRequest request) {String authHeader = request.getHeader("Authorization");if (authHeader != null && authHeader.startsWith("Bearer ")) {return parseUserIdFromToken(authHeader.substring(7));}return null;}private String parseUserIdFromToken(String token) {return "user123"; }
}
注册拦截器到Spring配置中:
@Configuration
public class WebConfig implements WebMvcConfigurer {private final UserTraceInterceptor userTraceInterceptor;public WebConfig(UserTraceInterceptor userTraceInterceptor) {this.userTraceInterceptor = userTraceInterceptor;}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(userTraceInterceptor).addPathPatterns("/**");}
}
3.Feign传递参数-调用服务
创建一个 Feign 请求拦截器,将 MDC 中的 userTraceId 添加到 Feign 请求头中:
@Component
public class FeignTraceInterceptor implements RequestInterceptor {private static final String USER_TRACE_ID_HEADER = "X-User-Trace-Id";@Overridepublic void apply(RequestTemplate requestTemplate) {// 从 MDC 获取 userTraceIdString userTraceId = MDC.get("userTraceId");if (userTraceId != null) {// 将 userTraceId 添加到请求头requestTemplate.header("X-user-trace-id", userTraceId);}}
}
说明:
RequestInterceptor 会在每个 Feign 请求发送前被调用。它从 MDC 中读取 userTraceId,并将其放入请求头 X-User-Trace-Id。如果 userTraceId 不存在,请求头中不会添加该字段。
4.Feign传递参数-被调用微服务
在被调用微服务的 UserTraceInterceptor 中接收 userTraceId,修改 UserTraceInterceptor,在 extractUserId 方法中,优先获取请求头中的 X-UserTraceId,如果没有则按原逻辑生成新的 userTraceId:
/*** @description: 用户日志跟踪拦截器* @description: MDC(Mapped Diagnostic Context):Logback 支持的线程上下文存储机制,适合在日志中动态传递变量。*/
@Component
public class UserTraceInterceptor implements HandlerInterceptor {private static final String USER_TRACE_ID_HEADER = "X-UserTrace-Id";@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {// 获取用户信息或请求头中的 userTraceIdString userTraceId = extractUserTraceId(request);if (userTraceId != null) {MDC.put("userTraceId", userTraceId);} else {// 默认值:访客 + 时间戳MDC.put("userTraceId", "访客-" + System.currentTimeMillis());}return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {// 清理 MDC,防止线程复用导致上下文污染MDC.remove("userTraceId");}private String extractUserTraceId(HttpServletRequest request) {// 优先从请求头获取 userTraceId(来自上游服务)String userTraceId = request.getHeader("X-User-Trace-Id");if (userTraceId != null && !userTraceId.isEmpty()) {return userTraceId;}// 如果没有 userTraceId,则尝试获取 global-trace-id 或用户信息String globalTraceId = request.getHeader("global-trace-id");if (globalTraceId != null && !globalTraceId.isEmpty()) {return globalTraceId;}// 从会话中获取用户信息SellerUser sellerUser = WebSellerSession.getSellerUser(request);if (sellerUser != null && sellerUser.getName() != null) {return sellerUser.getName() + "-" + System.currentTimeMillis();}return null;}
}
说明:
extractUserTraceId 优先检查请求头 X-User-Trace-Id,以接收上游微服务传递的 userTraceId。如果没有 userTraceId,则按原逻辑检查 global-trace-id 或用户信息。如果都不可用,返回 null,在 preHandle 中生成默认的 访客-时间戳。
三、总结
通过在拦截器中设置MDC的userTraceId,并在logback-spring.xml的日志模式中添加%X{userTraceId},即可实现基于登录用户的日志跟踪功能。