logback
扩展,支持跨线程池的mdc跟踪。
集成使用了Transmittable ThreadLocal(TTL) :在使用线程池等会缓存线程的组件情况下,提供ThreadLocal值的传递功能,解决异步执行时上下文传递的问题
引入 transmittable-thread-local
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.14.2</version>
</dependency>
创建 TtlMDCAdapter
注意要放到 org.slf4j 包下面
package org.slf4j;
import com.alibaba.ttl.TransmittableThreadLocal;
import org.slf4j.spi.MDCAdapter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* @author XJW
*/
public class TtlMDCAdapter implements MDCAdapter {
/**
* use com.alibaba.ttl.TransmittableThreadLocal
*/
final ThreadLocal<Map<String, String>> copyOnInheritThreadLocal = new TransmittableThreadLocal<>();
private static final int WRITE_OPERATION = 1;
private static final int READ_OPERATION = 2;
private static TtlMDCAdapter mtcMDCAdapter;
// keeps track of the last operation performed
final ThreadLocal<Integer> lastOperation = new ThreadLocal<>();
static {
mtcMDCAdapter = new TtlMDCAdapter();
// MDC.mdcAdapter 权限问题
MDC.mdcAdapter = mtcMDCAdapter;
}
public static MDCAdapter getInstance() {
return mtcMDCAdapter;
}
private Integer getAndSetLastOperation(int op) {
Integer lastOp = lastOperation.get();
lastOperation.set(op);
return lastOp;
}
private static boolean wasLastOpReadOrNull(Integer lastOp) {
return lastOp == null || lastOp == READ_OPERATION;
}
private Map<String, String> duplicateAndInsertNewMap(Map<String, String> oldMap) {
Map<String, String> newMap = Collections.synchronizedMap(new HashMap<String, String>());
if (oldMap != null) {
// we don't want the parent thread modifying oldMap while we are
// iterating over it
synchronized (oldMap) {
newMap.putAll(oldMap);
}
}
copyOnInheritThreadLocal.set(newMap);
return newMap;
}
/**
* Put a context value (the <code>val</code> parameter) as identified with the
* <code>key</code> parameter into the current thread's context map. Note that
* contrary to log4j, the <code>val</code> parameter can be null.
* <p/>
* <p/>
* If the current thread does not have a context map it is created as a side
* effect of this call.
*
* @throws IllegalArgumentException in case the "key" parameter is null
*/
@Override
public void put(String key, String val) {
if (key == null) {
throw new IllegalArgumentException("key cannot be null");
}
Map<String, String> oldMap = copyOnInheritThreadLocal.get();
Integer lastOp = getAndSetLastOperation(WRITE_OPERATION);
if (wasLastOpReadOrNull(lastOp) || oldMap == null) {
Map<String, String> newMap = duplicateAndInsertNewMap(oldMap);
newMap.put(key, val);
} else {
oldMap.put(key, val);
}
}
/**
* Remove the the context identified by the <code>key</code> parameter.
* <p/>
*/
@Override
public void remove(String key) {
if (key == null) {
return;
}
Map<String, String> oldMap = copyOnInheritThreadLocal.get();
if (oldMap == null) {
return;
}
Integer lastOp = getAndSetLastOperation(WRITE_OPERATION);
if (wasLastOpReadOrNull(lastOp)) {
Map<String, String> newMap = duplicateAndInsertNewMap(oldMap);
newMap.remove(key);
} else {
oldMap.remove(key);
}
}
/**
* Clear all entries in the MDC.
*/
@Override
public void clear() {
lastOperation.set(WRITE_OPERATION);
copyOnInheritThreadLocal.remove();
}
/**
* Get the context identified by the <code>key</code> parameter.
* <p/>
*/
@Override
public String get(String key) {
Map<String, String> map = getPropertyMap();
if ((map != null) && (key != null)) {
return map.get(key);
} else {
return null;
}
}
/**
* Get the current thread's MDC as a map. This method is intended to be used
* internally.
*/
public Map<String, String> getPropertyMap() {
lastOperation.set(READ_OPERATION);
return copyOnInheritThreadLocal.get();
}
/**
* Return a copy of the current thread's context map. Returned value may be
* null.
*/
@Override
public Map getCopyOfContextMap() {
lastOperation.set(READ_OPERATION);
Map<String, String> hashMap = copyOnInheritThreadLocal.get();
if (hashMap == null) {
return null;
} else {
return new HashMap<>(hashMap);
}
}
@SuppressWarnings("unchecked")
@Override
public void setContextMap(Map contextMap) {
lastOperation.set(WRITE_OPERATION);
Map<String, String> newMap = Collections.synchronizedMap(new HashMap<String, String>());
newMap.putAll(contextMap);
// the newMap replaces the old one for serialisation's sake
copyOnInheritThreadLocal.set(newMap);
}
}
创建 TtlMdcListener
package ai.guiji.common.logback;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.LoggerContextListener;
import ch.qos.logback.core.spi.ContextAwareBase;
import ch.qos.logback.core.spi.LifeCycle;
import org.slf4j.TtlMDCAdapter;
/**
* @author XJW
*/
public class TtlMdcListener extends ContextAwareBase implements LoggerContextListener, LifeCycle {
@Override
public void start() {
addInfo("load TtlMDCAdapter...");
TtlMDCAdapter.getInstance();
}
@Override
public void stop() {
}
@Override
public boolean isStarted() {
return false;
}
@Override
public boolean isResetResistant() {
return false;
}
@Override
public void onStart(LoggerContext loggerContext) {
}
@Override
public void onReset(LoggerContext loggerContext) {
}
@Override
public void onStop(LoggerContext loggerContext) {
}
@Override
public void onLevelChange(Logger logger, Level level) {
}
}
在拦截器或者过滤器中配置
String traceId = UUID.randomUUID().toString(true);
MDC.put("traceId", traceId);
logback.xml 配置文件中添加
<?xml version="1.0" encoding="UTF-8"?>
<configuration >
<!-- ...(略) -->
<contextListener class="com.ofpay.logback.TtlMdcListener"/>
<!--例子: %X{uuid} 支持在跨线程池时传递-->
<property scope="context" name="APP_PATTERN"
value='%d{yyyy-MM-dd HH:mm:ss.SSS}|%X{traceId}|%level|%M|%C:%L|%thread|%replace(%.-2000msg){"(\r|\n)","\t"}|"%.-2000ex{full}"%n'/>
</configuration>