一、前言
助手希望在日志打印中能够把调用的来源打清楚,如果是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();
}
}
}
}