import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import java.util.concurrent.TimeUnit;
/**
* @author Ricky Fung
*/
public abstract class TraceContext {
private static final ThreadLocal<Long> timeThreadLocal = new ThreadLocal();
//---------接口耗时统计
/**
* 开始计时
* @return
*/
public static long start() {
long startTime = System.nanoTime();
timeThreadLocal.set(startTime);
return startTime;
}
/**
* 获取接口耗时
* @return
*/
public static long stopAndGet() {
long endTime = System.nanoTime();
Long startTime = timeThreadLocal.get();
if (startTime == null) {
throw new IllegalArgumentException("必须先调用start方法");
}
//移除
timeThreadLocal.remove();
long costTime = endTime - startTime;
return TimeUnit.MILLISECONDS.convert(costTime, TimeUnit.NANOSECONDS);
}
//---------trace相关
public static String getTraceId() {
return MDC.get(CommonConstants.SLF4J_TRACE_ID);
}
public static String getSpanId() {
return MDC.get(CommonConstants.SLF4j_SPAN_ID);
}
/**
* 获取traceId,如果不存在则生成一个
* @return
*/
public static String computeTraceId() {
String traceId = getTraceId();
if (StringUtils.isEmpty(traceId)) {
traceId = UUIDUtils.getId();
}
return traceId;
}
public static void putTraceId(String traceId) {
MDC.put(CommonConstants.SLF4J_TRACE_ID, traceId);
}
public static void putSpanId(String spanId) {
MDC.put(CommonConstants.SLF4j_SPAN_ID, spanId);
}
//-------
public static void removeTraceId() {
MDC.remove(CommonConstants.SLF4J_TRACE_ID);
}
public static void removeSpanId() {
MDC.remove(CommonConstants.SLF4j_SPAN_ID);
}
public static String genSpanId(String spanId) {
if (StringUtils.isEmpty(spanId)) {
return CommonConstants.ROOT_SPAN_ID;
}
return spanId+".1";
}
public static String rootSpanId() {
return CommonConstants.ROOT_SPAN_ID;
}
}
其中,CommonConstants定义了几个常量:
/**
* @author Ricky Fung
*/
public class CommonConstants {
//HTTP请求头字段
public static final String TRACE_ID_HEADER = "X-Trace-Id";
public static final String SPAN_ID_HEADER = "X-Span-Id";
//Logback参数
public static final String SLF4J_TRACE_ID = "X-TraceId";
public static final String SLF4j_SPAN_ID = "X-SpanId";
public static final String ROOT_SPAN_ID = "0";
}
MDC实现日志跟踪
MDC在做日志跟踪的时候用的比较多。一个系统提供服务,提供给其他系统来调用,当其他系统调用的时候,入参带上一个唯一的请求标识(traceId),把这个traceId输出到日志中,这样两个系统直接就会形成一个执行链,用traceId串联起来,当出现错误时,可以在调用方查询对应的请求日志,也可以在服务方查询请求日志。定位问题很方便。输出日志的地方很多,不能每次输出都去获取traceId,拼接到日志中,这样做很不优雅,也很容易遗漏。这个时候使用MDC配合logback中的pattern就很简单啦。
实现思路
首先请求过来,将traceId放到MDC中,然后在pattern中用表达式从MDC中获取到对应的traceId。
对MDC不熟悉的同学可以先阅读一下我的上一篇文章:Slf4j MDC 实现机制与应用
Spring Boot 实战
1、TraceContext
首先,我们封装一个 TraceContext工具类,如下:
其中,CommonConstants定义了几个常量:
2、TraceInterceptor
利用Spring MVC提供的
org.springframework.web.servlet.HandlerInterceptor
我们可以很方便的拦截HTTP请求,对请求处理前后做一些处理。在preHandle方法中,我们获取HttpServletRequest中的TraceId 和 SpanId,如果二者为空我们就生成一个并put到MDC中。 在afterCompletion方法中,我们做一些清理工作,清除我们之前设置的TraceId 和 SpanId。
另外,就是应用在请求三方系统时,需要带上TraceId,这样整个链路就串联起来了,代码如下:
3、日志输出
在logback.xml中如果想输出MDC中的自定义属性,可以通过
%X{propertyName}
方式。logback-spring.xml 如下:
我们通过 %X{X-TraceId} 和 %X{X-SpanId} 输出TraceContext中put到MDC中的TraceId 和 SpanId。