Logback 之 MDC 日志跟踪、日志自定义

栏目: IT技术 · 发布时间: 4年前

内容简介: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 日志跟踪、日志自定义》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Where Wizards Stay Up Late

Where Wizards Stay Up Late

Katie Hafner / Simon & Schuster / 1998-1-21 / USD 16.00

Twenty five years ago, it didn't exist. Today, twenty million people worldwide are surfing the Net. "Where Wizards Stay Up Late" is the exciting story of the pioneers responsible for creating the most......一起来看看 《Where Wizards Stay Up Late》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码