在線程執行前,將原線程上下文中的MDC信息注入到新的線程中java
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD }) public @interface MDC { }
切面: spring
import java.util.UUID; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.slf4j.MDC; @Slf4j public abstract class MdcInterceptor { protected final static String LOG_ID = "logId"; @Pointcut("execution(* com.noob.api..*.*(..)) || @annotation(com.noob.aspectj.MDC)") public void pointCut() { // do nothings }; @Around(value = "pointCut()") public Object invoke(ProceedingJoinPoint point) throws Throwable { Object result; try { setTradeId(); result = point.proceed(point.getArgs()); removeTraceId(); } catch (Throwable throwable) { throw throwable; } finally { removeTraceId(); } return result; } /** * 璁劇疆traceId */ public static void setTradeId() { try { MDC.put(LOG_ID, UUID.randomUUID().toString().replace("-", "")); } catch (Exception e) { log.error("set log no exception", e); } } /** * remove traceId */ public static void removeTraceId() { try { MDC.remove(LOG_ID); } catch (Exception e) { log.error("remove log no exception", e); } } }
在使用@Sync時,Spring默認提供的TaskExecutor下的ThreadPoolTaskExecutor沒法支持MDC在線程切換時上下文內容的繼承。由於MDC本質上使用ThreadLocal來保存上下文。apache
須要注意的是:切面的織入必定要符合【外部調入】的原則!json
解決方法:api
重寫ThreadPoolTaskExecutor,在callable(真實處理過程)前注入父線程內容:
MDC.setContextMap(MDC.getCopyOfContextMap());dom
import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.UUID; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.MapUtils; import org.slf4j.MDC; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import com.google.common.base.Strings; import com.alibaba.fastjson.JSONObject; @Slf4j public class MdcThreadPoolTaskExecutor extends ThreadPoolTaskExecutor { private final static String LOG_ID = "logId"; /** * */ private static final long serialVersionUID = 1L; @Override public <T> Future<T> submit(Callable<T> task) { Map<String, String> context = MDC.getCopyOfContextMap(); log.info("----MDC content:{}", JSONObject.toJSONString(context)); return super.submit(() -> { // 將父線程的MDC內容傳給子線程 T result = null; if (MapUtils.isNotEmpty(context) && !Strings.isNullOrEmpty(context.get(LOG_ID))) { MDC.setContextMap(context); } else { MDC.put(LOG_ID, UUID.randomUUID().toString().replace("-", "")); //爲空設置新值 } try { result = task.call(); } finally { try { MDC.clear(); } catch (Exception e2) { log.warn("mdc clear exception.", e2); } } return result; }); } }