内容简介:MDC在做日志跟踪的时候用的比较多。一个系统提供服务,提供给其他系统来调用,当其他系统调用的时候,入参带上一个唯一的请求标识(traceId),把这个traceId输出到日志中,这样两个系统直接就会形成一个执行链,用traceId串联起来,当出现错误时,可以在调用方查询对应的请求日志,也可以在服务方查询请求日志。定位问题很方便。输出日志的地方很多,不能每次输出都去获取traceId,拼接到日志中,这样做很不优雅,也很容易遗漏。这个时候使用MDC配合logback中的pattern就很简单啦。首先请求过来,将
MDC实现日志跟踪
MDC在做日志跟踪的时候用的比较多。一个系统提供服务,提供给其他系统来调用,当其他系统调用的时候,入参带上一个唯一的请求标识(traceId),把这个traceId输出到日志中,这样两个系统直接就会形成一个执行链,用traceId串联起来,当出现错误时,可以在调用方查询对应的请求日志,也可以在服务方查询请求日志。定位问题很方便。输出日志的地方很多,不能每次输出都去获取traceId,拼接到日志中,这样做很不优雅,也很容易遗漏。这个时候使用MDC配合logback中的pattern就很简单啦。
实现思路
首先请求过来,将traceId放到MDC中,然后在pattern中用表达式从MDC中获取到对应的traceId。
对MDC不熟悉的同学可以先阅读一下我的上一篇文章: Slf4j MDC 实现机制与应用
Spring Boot 实战
1、TraceContext
首先,我们封装一个 TraceContext工具类,如下:
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";
}
2、TraceInterceptor
利用Spring MVC提供的 org.springframework.web.servlet.HandlerInterceptor
我们可以很方便的拦截HTTP请求,对请求处理前后做一些处理。
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author Ricky Fung
*/
public class TraceInterceptor extends HandlerInterceptorAdapter {
private final Logger LOG = LoggerFactory.getLogger(this.getClass());
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取trace
String traceId = request.getHeader(CommonConstants.TRACE_ID_HEADER);
String spanId = request.getHeader(CommonConstants.SPAN_ID_HEADER);
if (StringUtils.isEmpty(traceId)) { //生成根
traceId = UUIDUtils.getId();
}
//生成spanId
if (StringUtils.isEmpty(spanId)) {
spanId = CommonConstants.ROOT_SPAN_ID;
} else {
spanId = TraceContext.genSpanId(spanId);
}
//设置trace
TraceContext.putTraceId(traceId);
TraceContext.putSpanId(spanId);
TraceContext.start();
LOG.info("客服呼叫中心-链路追踪开始, traceId:{}, spanId:{}", traceId, spanId);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
long costTime = TraceContext.stopAndGet();
LOG.info("客服呼叫中心-链路追踪结束, 接口URI:{}, 耗时:{} ms", request.getRequestURI(), costTime);
//清除
TraceContext.removeTraceId();
TraceContext.removeSpanId();
}
}
在preHandle方法中,我们获取HttpServletRequest中的TraceId 和 SpanId,如果二者为空我们就生成一个并put到MDC中。
在afterCompletion方法中,我们做一些清理工作,清除我们之前设置的TraceId 和 SpanId。
另外,就是应用在请求三方系统时,需要带上TraceId,这样整个链路就串联起来了,代码如下:
@Resource
private RestTemplate restTemplate;
private String doRequest(Long userId, String token, String payload, String apiUri, HttpMethod httpMethod) {
//trace
String traceId = TraceContext.getTraceId();
String spanId = TraceContext.getSpanId();
LOG.info("客服呼叫中心-转发请求开始, userId:{}, traceId:{}, httpMethod:{}, apiUri:{}", userId, traceId, httpMethod, apiUri);
StringBuilder sb = new StringBuilder(80);
sb.append(bpHost).append(apiUri);
String reqUrl = sb.toString();
//创建请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.add(CommonConstants.AUTHORIZATION_HEADER, token);
headers.add(CommonConstants.TRACE_ID_HEADER, StringUtils.isNotEmpty(traceId) ? traceId : StringUtils.EMPTY);
headers.add(CommonConstants.SPAN_ID_HEADER, StringUtils.isNotEmpty(spanId) ? spanId : StringUtils.EMPTY);
HttpEntity<String> httpEntity = new HttpEntity<>(payload, headers);
ResponseEntity<String> responseEntity = restTemplate.exchange(reqUrl, httpMethod, httpEntity, String.class);
if (responseEntity.getStatusCode() != HttpStatus.OK) {
LOG.info("客服呼叫中心-转发请求, userId:{}, traceId:{} 请求服务器:{} 状态码异常:{}", userId, traceId, reqUrl, responseEntity.getStatusCodeValue());
}
String json = responseEntity.getBody();
LOG.info("客服呼叫中心-转发请求, userId:{}, traceId:{}, 请求服务器:{} 响应结果:{}", userId, traceId, reqUrl, json);
if (StringUtils.isNotEmpty(json)) {
return json;
}
return null;
}
3、日志输出
在logback.xml中如果想输出MDC中的自定义属性,可以通过 %X{propertyName}
方式。
logback-spring.xml 如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{X-TraceId} | %X{X-SpanId}] %logger - %msg%n"/>
<springProperty scope="context" name="prodName" source="spring.application.name" defaultValue="crm-call-center-proxy"/>
<appender name="CONSOLE">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
我们通过 %X{X-TraceId} 和 %X{X-SpanId} 输出TraceContext中put到MDC中的TraceId 和 SpanId。
以上所述就是小编给大家介绍的《Logback 之 MDC 日志跟踪、日志自定义》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Logback 之 MDC 日志跟踪、日志自定义
- 调用链与日志的关联式跟踪查询
- 针对 Windows 事件跟踪日志篡改的攻防研究
- Python中使用 logging 和 traceback 模块记录日志和跟踪异常
- Firefox 65 计划推出新反跟踪方法,专门针对跨站点跟踪
- Thinkphp漏洞跟踪
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
CSS 压缩/解压工具
在线压缩/解压 CSS 代码
图片转BASE64编码
在线图片转Base64编码工具