利用MDC在日志框架中打印api接口的path或dubbo的方法名

一、前言

助手希望在日志打印中能够把调用的来源打清楚,如果是API调用就打印API的path,如果是dubbo的调用就打印dubbo的接口名字。这样在后续配置日志监控告警的时候也能比较方便的配置告警。

二、实现方式

采用slf4j的MDC方式来实现,日志框架自己本身也是用MDC来实现多线程调用之间的信息传递,本质上还是利用了ThreadLocal。api框架用拦截器找到path传递进去。rpc框架用的dubbo。用dubbo的拦截器传递调用方法名。

2.1 api框架传递path

这里改用自己项目使用的api框架里的拦截器即可

java 复制代码
@Slf4j
@Component
public class MDCHandlerInterceptor implements HandlerInterceptor {
    
    @Override
    public void postHandle(AsyncRequest request, AsyncResponse response, Object handler) throws Exception {
        MDC.remove("path");
    }
    
    @Override
    public void afterCompletion(AsyncRequest request, AsyncResponse response, Object handler, Exception ex) {
        MDC.clear();
    }
    
    @Override
    public boolean preHandle(AsyncRequest request, AsyncResponse response, Object handler) throws Exception {
        String requestPath = MDC.get("path");
        if (requestPath == null || "".equals(requestPath)){
            String path = request.path();
            MDC.put("path", path);
        }
        return true;
    }
}

2.1.1 log4j配置文件

perl 复制代码
// 添加我们刚刚放入MDC里的 [%X{path}]
 <Property name="LOG_PATTERN">[%date{yyyy-MM-dd HH:mm:ss.SSS}] [%X{traceId}] [%level] [%thread] [%X{path}] [%logger{56}] %msg%n</Property>

2.2 dubbo传递调用方法名

我们用的dubbo版本是2.7.1 核心思路:利用dubbo的拦截器把调用的方法名传入的threadLocal的key为path的value里面去。

ini 复制代码
@Activate(group = {Constants.PROVIDER})
public class DubboProviderMDCFilter implements Filter {
    
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        String methodName = invocation.getMethodName();
        String requestPath = MDC.get("path");
        if (requestPath == null || "".equals(requestPath)){
            MDC.put("path", methodName);
        }
        Result invoke = invoker.invoke(invocation);
        MDC.clear();
        return invoke;
    }
    
}

2.2.1 log4j配置文件

perl 复制代码
// 添加我们刚刚放入MDC里的 [%X{path}]
<Property name="LOG_PATTERN">[%date{yyyy-MM-dd HH:mm:ss.SSS}] [%X{traceId}] [%level] [%thread] [%X{path}] [%logger{56}] %msg%n</Property>

2.3 用了线程池做并发要继续传递给线程池的线程的ThreadLocal

2.3.1 【使用】在线程池传递中使用

javascript 复制代码
````主流程代码````
// 1.先new一个MdcTraceWrapper,MdcTraceWrapper有两个成员变量记录了主线程的threadLocal上下文
MdcTraceWrapper mdcTraceWrapper = new MdcTraceWrapper();

CompletableFuture<返回结果Object> future =
    CompletableFuture.supplyAsync(() -> mdcTraceWrapper.wrap(() -> ...执行方法), 你的线程池);
futureList.add(future);
````主流程代码````

2.3.2 线程切换调用MDC包装器

typescript 复制代码
public class MdcTraceWrapper {
    // new MdcTraceWrapper的时候,把主线程的线程池上下文保留住
    private final Map<String, String> context = MDC.getCopyOfContextMap();
   
    
    /**
     * 包装执行逻辑
     */
    public <T> T wrap(Supplier<T> supplier) {
     /* 进入这个方法说明已经是线程池的线程在执行了 */
     // 把这个子线程干净的scope存储住
      
        
        try {
            if (null != context) {
               // 把主线程的上下文传递给线程池的执行线程
                MDC.setContextMap(context);
            }
            try {
                T result = supplier.get();
                return result;
            } catch (RuntimeException e) {
                throw e;
            }
            
        } finally {
            if (null != context) {
             // 执行线程执行完了任务把上下文清掉,下次它还会去执行别的任务,不能带着上一个人的threadlocal上下文
                MDC.clear();
            }
        }
    }
    
    /**
     * 包装执行逻辑
     */
    public void wrap(Runnable runnable) {
     
        try {
            if (null != context) {
                MDC.setContextMap(context);
            }
            
       
            try {
                runnable.run();
            } catch (RuntimeException e) {
                throw e;
            }
            
        } finally {
            if (null != context) {
                MDC.clear();
            }
        }
    }
    
}
相关推荐
0和1的舞者5 分钟前
公共类的注意事项详细讲解
经验分享·后端·开发·知识·总结
小北方城市网16 分钟前
Spring Cloud Gateway 自定义过滤器深度实战:业务埋点、参数校验与响应改写
运维·jvm·数据库·spring boot·后端·mysql
jason.zeng@150220720 分钟前
POM构造Spring boot多模块项目
java·spring boot·后端
猿与禅40 分钟前
Spring Boot 3.x 集成 Caffeine 缓存框架官方指南
spring boot·后端·缓存·caffeine
虾说羊1 小时前
Springboot中配置欢迎页的方式
java·spring boot·后端
漫漫求1 小时前
Go的panic、defer、recover的关系
开发语言·后端·golang
qq_12498707531 小时前
基于Spring Boot的长春美食推荐管理系统的设计与实现(源码+论文+部署+安装)
java·前端·spring boot·后端·毕业设计·美食·计算机毕业设计
Tony Bai1 小时前
2025 Go 官方调查解读:91% 满意度背后的隐忧与 AI 时代的“双刃剑”
开发语言·后端·golang
懈尘1 小时前
基于Spring Boot与LangChain4j的AI驱动新闻系统设计与工程实现
java·大数据·人工智能·spring boot·后端·langchain
翔云1234561 小时前
golang中使用 sort.Interface 实现复杂多级排序
开发语言·后端·golang