日志监控MDC TreaceId原理详解与重写

日志监控MDC TreaceId原理详解与重写_第1张图片

LoggerTrace

背景及介绍

在业务系统的开发工程中,时常会遇到查看系统的日志,但是当TO C的场景中,会产生大量的日志,我们在查看日志是大多情况下无法快速定位问题,这是我们就需要traceId,进行快速检索请求信息,定位问题,查询出日志的链路关系。

目前市面上大多数的系统都继承了slf4j,集成MDC,开发者可通过MDC.put(“traceId”, UUID.randomUUID().toString()),方式设置traceId信息,然后再log配置中,指定traceId,便可在日志打印时带有traceId信息。

MDC的实现原理为ThreadLocal,在并发场景中便获取不到traceId信息,对于日志的检索或调用链路的形成都是一大难题。

JD自研的Pfinder,可以跨线程传递traceId,但是Pfinder也有弊端:1.集成较为繁琐,需配置脚本,配置环境变量;2.必须指定JD的JDK修改的版本。所以在JD还是有一部分系统并没有集成pfinder。

MDC的底层是ThreaLocal,而MDC又不是直接操作ThreadLocal,说白了MDC只是对MDCAdaper的再次包装,而真正操作ThreadLocal的则是MDCAdaper,如果我们要想实现可以跨线程,传递traceId,那么我则需要实现MDCAdapter接口,在实现类中使用可继承的ThreadLocal。

MDCAdapter接口共提供了必须实现的6个方法,接下来分别介绍一下这6个方法。

  • put(String key, String val);

        设置traceId信息,key为traceId的key值;val为traceId的value值,可用分布式ID的ID生成策            略生成。

  • get(String key);

        获取treaceId信息。

  • remove(String key);

        从上下文中移除当前key值。

  • clear();

        清楚MDC信息。

  • Map getCopyOfContextMap();

        获取一个当前线程的上下文存储对下。

  • void setContextMap(Map contextMap);

        设置当前线程的上下文信息。

代码实现

需要在org.slf4j包下,否则需要自定义类加载器,加载该类,可根据时机情况取舍
public class TtlMDCAdapter implements MDCAdapter {

    final ThreadLocal<Map<String, String>> copyOnThreadLocal = new InheritableThreadLocal<>();

    private static final int WRITE_OPERATION = 1;

    private static final int MAP_COPY_OPERATION = 2;

    final ThreadLocal<Integer> lastOperation = new ThreadLocal<>();

    private static final TtlMDCAdapter mtcMDCAdapter;

    static {
        mtcMDCAdapter = new TtlMDCAdapter();
        MDC.mdcAdapter = mtcMDCAdapter;
    }

    public TtlMDCAdapter() {
    }

    public static MDCAdapter getInstance() {
        return mtcMDCAdapter;
    }

    private Integer getAndSetLastOperation(int op) {
        Integer lastOp = this.lastOperation.get();
        this.lastOperation.set(op);
        return lastOp;
    }

    private boolean wasLastOpReadOrNull(Integer lastOp) {
        return lastOp == null || lastOp == MAP_COPY_OPERATION;
    }



    private Map<String, String> duplicateAndInsertNewMap(Map<String, String> oldMap) {
        Map<String, String> newMap = Collections.synchronizedMap(new HashMap<>());
        if (oldMap != null) {
            synchronized (oldMap) {
                newMap.putAll(oldMap);
            }
        }
        this.copyOnThreadLocal.set(newMap);
        return newMap;
    }

    @Override
    public void put(String key, String val) throws IllegalArgumentException {
        if (key == null) {
            throw new IllegalArgumentException("key cannot be null");
        } else {
            Map<String, String> oldMap = this.copyOnThreadLocal.get();
            Integer lastOp = this.getAndSetLastOperation(WRITE_OPERATION);
            if (!this.wasLastOpReadOrNull(lastOp) && oldMap != null) {
                oldMap.put(key, val);
            } else {
                Map<String, String> newMap = this.duplicateAndInsertNewMap(oldMap);
                newMap.put(key, val);
            }
        }
    }

    @Override
    public void remove(String key) {
        if (key != null) {
            Map<String, String> oldMap = this.copyOnThreadLocal.get();
            if (oldMap != null) {
                Integer lastOp = this.getAndSetLastOperation(WRITE_OPERATION);
                if (this.wasLastOpReadOrNull(lastOp)) {
                    Map<String, String> newMap = this.duplicateAndInsertNewMap(oldMap);
                    newMap.remove(key);
                } else {
                    oldMap.remove(key);
                }
            }
        }
    }

    @Override
    public void clear() {
        this.lastOperation.set(WRITE_OPERATION);
        this.copyOnThreadLocal.remove();
    }

    @Override
    public String get(String key) {
        Map<String, String> map = this.copyOnThreadLocal.get();
        return map != null && key != null ? map.get(key) : null;
    }

    public Map<String, String> getPropertyMap() {
        this.lastOperation.set(MAP_COPY_OPERATION);
        return this.copyOnThreadLocal.get();
    }

    public Set<String> getKeys() {
        Map<String, String> map = this.getPropertyMap();
        return map != null ? map.keySet() : null;
    }

    @Override
    public Map<String, String> getCopyOfContextMap() {
        Map<String, String> hashMap = copyOnThreadLocal.get();
        return hashMap == null ? null : new HashMap<>(hashMap);
    }

    @Override
    public void setContextMap(Map contextMap) {
        this.lastOperation.set(WRITE_OPERATION);
        Map<String, String> newMap = Collections.synchronizedMap(new HashMap<>());
        newMap.putAll(contextMap);
        this.copyOnThreadLocal.set(newMap);
    }
}
public class TtlMDCAdapterInitializer implements ApplicationContextInitializer {

    @Override
    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
        TtlMDCAdapter.getInstance();
    }
}

加载

代码开发完成后,需要在项目启动时加载进去。共有三种方式。

spring.facories

org.springframework.context.ApplicationContextInitializer=\
com.fly.monitor.core.adapter.TtlMDCAdapterInitializer

通过配置文件方式

context:
  initializer:
    classes: com.fly.monitor.core.adapter.TtlMDCAdapterInitializer

Application启动类

SpringApplication application = new SpringApplication(MonitorConsoleApplication.class);
application.addInitializers(new TtlMDCAdapterInitializer());
application.run(args);

你可能感兴趣的:(java,数据库,log4j)