几行代码轻松实现跨系统传递 traceId,再也不用担心对不上日志了!
前言
新项目查日志太麻烦,多台机器之间查来查去,还不知道是不是同一个请求的。打印日志时使用 MDC 在日志上添加一个 traceId,那这个 traceId 如何跨系统传递呢?
公众号:liuzhihangs,记录工作学习中的技术、开发及源码笔记;时不时分享一些生活中的见闻感悟。欢迎大佬来指导!
背景
同样是新项目开发的笔记,因为使用的是分布式架构,涉及到各个系统之间的交互
这时候就会遇到一个很常见的问题:
- 单个系统是集群部署,日志分布在多台服务器上;
- 多个系统的日志在多台机器,但是一次请求,查日志更是难上加难。
解决方案
- 使用 SkyWalking traceid 进行链路追踪;
- 使用 Elastic APM 的 trace.id 进行链路追踪;
- 自己生成 traceId 并 put 到 MDC 里面。
MDC
MDC(Mapped Diagnostic Context)是一个映射,用于存储运行上下文的特定线程的上下文数据。因此,如果使用log4j进行日志记录,则每个线程都可以拥有自己的MDC,该MDC对整个线程是全局的。属于该线程的任何代码都可以轻松访问线程的MDC中存在的值。
如何使用 MDC
-
在
log4j2-spring.xml
的日志格式中添加
%X{traceId}
配置。
<Property name="LOG_PATTERN">
[%d{yyyy-MM-dd HH:mm:ss.SSS}]-[%t]-[%X{traceId}]-[%-5level]-[%c{36}:%L]-[%m]%n
</Property>
<Property name="LOG_PATTERN_ERROR">
[%d{yyyy-MM-dd HH:mm:ss.SSS}]-[%t]-[%X{traceId}]-[%-5level]-[%l:%M]-[%m]%n
</Property>
<!-- 省略 -->
<!--这个输出控制台的配置-->
<Console name="Console" target="SYSTEM_OUT" follow="true">
<!--输出日志的格式-->
<PatternLayout charset="UTF-8" pattern="${LOG_PATTERN}"/>
</Console>
- 新增拦截器
拦截所有请求,从 header 中获取 traceId 然后放到 MDC 中,如果没有获取到,则直接用 UUID 生成一个。
@Slf4j
@Component
public class LogInterceptor implements HandlerInterceptor {
private static final String TRACE_ID = "traceId";
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception arg3) throws Exception {
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView arg3) throws Exception {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String traceId = request.getHeader(TRACE_ID);
if (StringUtils.isEmpty(traceId)) {
MDC.put(TRACE_ID, UUID.randomUUID().toString());
} else {
MDC.put(TRACE_ID, traceId);
return true;
- 配置拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Resource
private LogInterceptor logInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(logInterceptor)
.addPathPatterns("/**");
跨服务之间如何传递 traceId
- FeignClient
因为这边使用的是 FeignClient 进行服务之间的调用,只需要新增请求拦截器即可
@Configuration
public class FeignInterceptor implements RequestInterceptor {
private static final String TRACE_ID = "traceId";
@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header(TRACE_ID, MDC.get(TRACE_ID));
- Dubbo
如果是 Dubbo 可以通过扩展 Filter 的方式传递 traceId
- 编写 filter
@Activate(group = {"provider", "consumer"})
public class TraceIdFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
RpcContext rpcContext = RpcContext.getContext();
String traceId;
if (rpcContext.isConsumerSide()) {
traceId = MDC.get("traceId");
if (traceId == null) {
traceId = UUID.randomUUID().toString();
rpcContext.setAttachment("traceId", traceId);
if (rpcContext.isProviderSide()) {
traceId = rpcContext.getAttachment("traceId");
MDC.put("traceId", traceId);
return invoker.invoke(invocation);
- 指定 filter
src
|-main
|-java
|-com
|-xxx
|-XxxFilter.java (实现Filter接口)
|-resources
|-META-INF
|-dubbo