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个方法。
设置traceId信息,key为traceId的key值;val为traceId的value值,可用分布式ID的ID生成策 略生成。
获取treaceId信息。
从上下文中移除当前key值。
清楚MDC信息。
获取一个当前线程的上下文存储对下。
设置当前线程的上下文信息。
需要在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();
}
}
代码开发完成后,需要在项目启动时加载进去。共有三种方式。
org.springframework.context.ApplicationContextInitializer=\
com.fly.monitor.core.adapter.TtlMDCAdapterInitializer
context:
initializer:
classes: com.fly.monitor.core.adapter.TtlMDCAdapterInitializer
SpringApplication application = new SpringApplication(MonitorConsoleApplication.class);
application.addInitializers(new TtlMDCAdapterInitializer());
application.run(args);