background
In the current micro-service system, there are many service applications and complex call chains, so the difficulty of troubleshooting problems increases accordingly. When an exception occurs in the application, we need to quickly locate the problem log, which requires us to trace the request link and generate an ID that identifies the entire request life cycle when the request arrives in the system.
MDC (Mapped Diagnostic Contexts) Introduction
MDC is an important tool to realize distributed multithreaded log data transmission in Slf4J logging system. Users can print some context data at run time by using MDC. Currently only Log4J and LogBack provide native MDC support
Source code analysis
MDC maintains a Map
property in the thread context, which can be understood as a thread-level container. In logback, you can obtain the interface provided by the value MDC in the MDC context via %X{key}
public interface MDCAdapter {
// Gets the value of the specified key in the current thread MDC context
void put(String var1, String var2);
// Go to the current thread MDC context
String get(String var1);
// Remove the key specified in the current thread MDC context
void remove(String var1);
// Clear the MDC context
void clear(a);
// Get the MDC context
Map<String, String> getCopyOfContextMap(a);
// Set the MDC context
void setContextMap(Map<String, String> var1);
}
Copy the code
Logback implementation logback MDcAdapter
public class LogbackMDCAdapter implements MDCAdapter {
final ThreadLocal<Map<String, String>> copyOnThreadLocal = new ThreadLocal();
private static final int WRITE_OPERATION = 1;
private static final int MAP_COPY_OPERATION = 2;
final ThreadLocal<Integer> lastOperation = new ThreadLocal();
public LogbackMDCAdapter(a) {}...Copy the code
You can see that LogbackMDC declares a map of type ThreadLocal. ThreadLocal provides thread-local instances. It differs from a normal variable in that each thread that uses it initializes a completely separate instance copy, meaning that ThreadLocal variables are isolated between threads and can be shared between methods or classes
LogbackMDCAdapter in the put ()
public void put(String key, String val) throws IllegalArgumentException {
if (key == null) {
throw new IllegalArgumentException("key cannot be null");
} else {
Map<String, String> oldMap = (Map)this.copyOnThreadLocal.get();
Integer lastOp = this.getAndSetLastOperation(1);
if (!this.wasLastOpReadOrNull(lastOp) && oldMap ! =null) {
oldMap.put(key, val);
} else {
Map<String, String> newMap = this.duplicateAndInsertNewMap(oldMap); newMap.put(key, val); }}}Copy the code
Validates and sets the key values into the container of ThreadLocal
duplicateAndInsertNewMap()
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;
}
Copy the code
Create a thread-safe HashMap as a container and place it in a ThreadLocal
Scheme and Implementation
Declare filters to intercept requests
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean registFilter(a) {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new DyeFilter());
registration.addUrlPatterns("/ *");
registration.setName("DyeFilter");
registration.setOrder(1);
returnregistration; }}Copy the code
Build the context object and assign it to MDC
@WebFilter(filterName = "DyeFilter")
public class DyeFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) req;
initGlobalContext(request);
try {
chain.doFilter(req, resp);
} finally{ ContextUtil.clearContext(); }}private static void initGlobalContext(HttpServletRequest servletRequest) {
GlobalContext context = new GlobalContext();
String contextStr = servletRequest.getHeader(ContextConstant.REQUEST_CONTEXT);
if(! StringUtils.isEmpty(contextStr)){ context = JSON.parseObject(contextStr,GlobalContext.class); }else{
context.setTraceId(UUID.randomUUID().toString());
context.setClientIp(IpUtil.getClientAddress(servletRequest));
}
ContextUtil.setCurrentContext(context);
}
@Override
public void init(FilterConfig config){}@Override
public void destroy(a) {}}Copy the code
The GlobalContext is constructed in the business entry, and in the subsequent invocation chain, the constructed GlobalContext is retrieved from the request header
public class ContextUtil {
private static ThreadLocal<GlobalContext> currentThreadLocal = ThreadLocal.withInitial(GlobalContext::new);
public static void setCurrentContext(GlobalContext context) {
currentThreadLocal.set(context);
String traceId = context.getTraceId();
if(traceId ! =null && traceId.length() > 0 && MDC.get(ContextConstant.TRACK_ID) == null) { MDC.put(ContextConstant.TRACK_ID, traceId); }}public static GlobalContext getCurrentContext(a) {
return currentThreadLocal.get();
}
public static void clearContext(a) { MDC.clear(); currentThreadLocal.remove(); }}Copy the code
Get the GlobalContext and pass it through the request header
public class FeignConfig {
@Bean
public RequestInterceptor header(a){
return this::setRequestHeader;
}
private void setRequestHeader(RequestTemplate requestTemplate){
GlobalContext context = ContextUtil.getCurrentContext();
if(context! =null){ requestTemplate.header(ContextConstant.REQUEST_CONTEXT, JSONParser.quote(JSON.toJSONString(context))); }}}Copy the code
This is just an example of how to call a Service through Feign. There are many other ways to call a service to another service. The main idea is to pass the GlobalContext transparently to the next service
Configure variables in the MDC container in the Logback configuration file%X{trackId}
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>% d {MM - dd yyyy - HH: MM: ss. The SSS} {trackId} % 5 level % X 15.15 t] [% % class. {39} % method [L] % : % m % n</pattern>
<! -- Console should also use UTF-8, do not use GBK, otherwise Chinese garble -->
<charset>UTF-8</charset>f
</encoder>
</appender>l
Copy the code
DEMO address: github.com/chenxuancod…