TFdream / blog

个人技术博客,博文写在 Issues 里。
Apache License 2.0
129 stars 18 forks source link

Slf4j MDC 实现机制与应用 #266

Open TFdream opened 4 years ago

TFdream commented 4 years ago

如今,在 Java 开发中,日志的打印输出是必不可少的,slf4j + logback 的组合是最常见的方式(slf4j + log4j 2.x也不错,不喜勿喷)。

有了日志之后,我们就可以追踪各种线上问题。但是,在分布式系统中,各种无关日志穿行其中,导致我们可能无法直接定位整个操作流程。因此,我们可能需要对一个用户的操作流程进行归类标记,比如使用线程+时间戳,或者用户身份标识等;如此,我们可以从大量日志信息中grep出某个用户的操作流程,或者某个时间的流转记录。

因此,这就有了 Slf4j MDC 方法。

Slf4j MDC 介绍

MDC ( Mapped Diagnostic Contexts ),顾名思义,其目的是为了便于我们诊断线上问题而出现的方法工具类。虽然,Slf4j 是用来适配其他的日志具体实现包的,但是针对 MDC功能,目前只有logback 以及 log4j 支持,或者说由于该功能的重要性,slf4j 专门为logback系列包装接口提供外部调用。

先来看看 MDC 类的定义:

    public static void put(String key, String val) throws IllegalArgumentException {
        if (key == null) {
            throw new IllegalArgumentException("key parameter cannot be null");
        }
        if (mdcAdapter == null) {
            throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL);
        }
        mdcAdapter.put(key, val);
    }

    public static String get(String key) throws IllegalArgumentException {
        if (key == null) {
            throw new IllegalArgumentException("key parameter cannot be null");
        }

        if (mdcAdapter == null) {
            throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL);
        }
        return mdcAdapter.get(key);
    }

    public static void remove(String key) throws IllegalArgumentException {
        if (key == null) {
            throw new IllegalArgumentException("key parameter cannot be null");
        }

        if (mdcAdapter == null) {
            throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL);
        }
        mdcAdapter.remove(key);
    }

    /**
     * Clear all entries in the MDC of the underlying implementation.
     */
    public static void clear() {
        if (mdcAdapter == null) {
            throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL);
        }
        mdcAdapter.clear();
    }

    public static Map<String, String> getCopyOfContextMap() {
        if (mdcAdapter == null) {
            throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL);
        }
        return mdcAdapter.getCopyOfContextMap();
    }

    public static void setContextMap(Map<String, String> contextMap) {
        if (mdcAdapter == null) {
            throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL);
        }
        mdcAdapter.setContextMap(contextMap);
    }

    public static MDCAdapter getMDCAdapter() {
        return mdcAdapter;
    }

初始化:

    static MDCAdapter mdcAdapter;

    private static MDCAdapter bwCompatibleGetMDCAdapterFromBinder() throws NoClassDefFoundError {
        try {
            return StaticMDCBinder.getSingleton().getMDCA();
        } catch (NoSuchMethodError nsme) {
            // binding is probably a version of SLF4J older than 1.7.14
            return StaticMDCBinder.SINGLETON.getMDCA();
        }
    }

    static {
        try {
            mdcAdapter = bwCompatibleGetMDCAdapterFromBinder();
        } catch (NoClassDefFoundError ncde) {
            mdcAdapter = new NOPMDCAdapter();
            String msg = ncde.getMessage();
            if (msg != null && msg.contains("StaticMDCBinder")) {
                Util.report("Failed to load class \"org.slf4j.impl.StaticMDCBinder\".");
                Util.report("Defaulting to no-operation MDCAdapter implementation.");
                Util.report("See " + NO_STATIC_MDC_BINDER_URL + " for further details.");
            } else {
                throw ncde;
            }
        } catch (Exception e) {
            // we should never get here
            Util.report("MDC binding unsuccessful.", e);
        }
    }

应用

TraceLogInterceptor

import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author Ricky Fung
 */
public class TraceLogInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //设置traceId
        String traceId = request.getHeader(TraceConstants.TRACE_ID);
        String spanId = request.getHeader(TraceConstants.SPAN_ID);
        if (StringUtils.isEmpty(traceId)) {
            traceId = UUIDUtils.getCompactId();
        }
        if (StringUtils.isEmpty(spanId)) {
            spanId = UUIDUtils.getCompactId();
        }
        //init trace
        TraceContextUtils.putTraceId(traceId);
        TraceContextUtils.putSpanId(spanId);

        return super.preHandle(request, response, handler);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 删除TraceId
        TraceContextUtils.removeTraceId();
    }
}

AppWebMvcConfigure

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
 * @author Ricky Fung
 */
@Configuration
public class AppWebMvcConfigure extends WebMvcConfigurerAdapter {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //身份校验
        registry.addInterceptor(traceInterceptor()).addPathPatterns("/**");
    }

    @Bean
    public TraceLogInterceptor traceInterceptor() {
        return new TraceLogInterceptor();
    }
}

TraceContextUtils

import org.slf4j.MDC;

/**
 * @author Ricky Fung
 */
public abstract class TraceContextUtils {
    //---便捷方法
    public static void putTraceId(String traceId) {
        MDC.put(TraceConstants.TRACE_ID, traceId);
    }
    public static void putSpanId(String spanId) {
        MDC.put(TraceConstants.SPAN_ID, spanId);
    }

    public static void removeTraceId() {
        MDC.remove(TraceConstants.TRACE_ID);
    }
    public static void removeSpanId() {
        MDC.remove(TraceConstants.SPAN_ID);
    }

    public static String getTraceId() {
        return MDC.get(TraceConstants.TRACE_ID);
    }
    public static String getSpanId() {
        return MDC.get(TraceConstants.SPAN_ID);
    }

    //-----------
    public static String get(String key) {
        return MDC.get(key);
    }

    public static String get(String key, String defaultValue) {
        String value = MDC.get(key);
        return value == null ? defaultValue : value;
    }

    public static void put(String key, String value) {
        MDC.put(key, value);
    }

    public static void clear() {
        MDC.clear();
    }

    public static void remove(String key) {
        MDC.remove(key);
    }

}

相关资料