Mapped Diagnostic Contexts (MDC) (译:诊断上下文映射) Logback的设计目标之一是审计和调试复杂的分布式应用程序。大多数实际的分布式系统需要同时处理来自多个客户端的请求。为了区分开每个客户端的日志,也为了能够快速定位某个请求日志来自哪个客户端,最简单地方式是,给每个客户端的每个日志请求一个唯一标记。 为了对每个请求进行惟一的标记,用户将上下文信息放入MDC中。 MDC类只包含静态方法。它允许开发人员将信息放在诊断上下文中,然后通过某些logback组件检索这些信息。MDC在每个线程的基础上管理上下文信息。通常,在开始服务新的客户端请求时,开发人员会将相关的上下文信息(如客户端id、客户端IP地址、请求参数等)插入到MDC中。如果配置得当,Logback组件将自动在每个日志条目中包含此信息。另外请注意,子线程不会自动继承其父线程的映射诊断上下文的副本。 下面是一个例子: 假设Logback配置文件是这样配置的: 运行这段程序,将会看到以下输出: 请注意,在PatternLayout转换模式中使用%X 补充一句,如果不知道这些格式字符串怎么写,可以参加 ch.qos.logback.classic.PatternLayout 通常,服务器用多个线程为多个客户端提供服务。尽管MDC类中的方法是静态的,但是诊断上下文是在每个线程的基础上进行管理的,允许每个服务器线程都带有不同的MDC戳记。诸如put()和get()之类的MDC操作只影响当前线程的MDC和当前线程的子线程。其他线程中的MDC不受影响。由于MDC信息是在每个线程的基础上管理的,所以每个线程都有自己的MDC副本。因此,开发人员在使用MDC编程时不需要担心线程安全或同步问题,因为它可以安全、透明地处理这些问题。 正如我们所看到的,MDC非常有用。我们可以在MDC中设置用户名,这样便可以从日志中追踪用户行为。但是,因为MDC是在每个线程的基础上管理数据的,而且项目中基本上都是用的线程池,所以回收线程的服务器可能会导致MDC中包含错误信息。为了使MDC中包含的信息在处理请求时始终正确,一种可能的方法是在线程开始时存储用户名,并在线程结束时删除它。在这种情况下,servlet过滤器就派上用场了。 MDC与线程管理 worker线程不总是可以从启动线程那里继承诊断上下文副本,例如当使用java.util.concurrent.Executors来管理线程时就不能。在这种情况下,推荐在提交任务到executor之前在原始线程上先调用MDC.getCopyOfContextMap()。当任务启动后,第一个动作应该先调用MDC.setContextMapValues()来关联这个原始MDC的副本。 MDCInsertingServletFilter 在Web应用程序中,知道给定的HTTP请求的主机名、请求URL以及user-agent等信息通常是非常有用的。MDCInsertingServletFilter插入这些数据到MDC中。 为了引入MDCInsertingServletFilter过滤器,添加以下到web.xml中 如果有多个过滤器,请务必确保MDCInsertingServletFilter在所有其他过滤器的前面。 示例:追踪用户行为(举个栗子) 使用过滤器或者拦截器都行 pom.xml 1 2 4 4.0.0 5 6 org.springframework.boot 7 spring-boot-starter-parent 8 2.2.1.RELEASE 9 10 11 com.cjs.example 12 demo123 13 0.0.1-SNAPSHOT 14 demo123 15 16 17 1.8 18 19 20 21 22 org.springframework.boot 23 spring-boot-starter-web 24 25 26 27 ch.qos.logback 28 logback-classic 29 1.2.3 30 31 32 33 org.projectlombok 34 lombok 35 true 36 37 38 39 40 41 42 org.springframework.boot 43 spring-boot-maven-plugin 44 45 46 47 48 过滤器配置 1 package com.cjs.example.config; 2 3 import ch.qos.logback.classic.helpers.MDCInsertingServletFilter; 4 import org.springframework.boot.web.servlet.FilterRegistrationBean; 5 import org.springframework.context.annotation.Bean; 6 import org.springframework.context.annotation.Configuration; 7 8 import java.util.Arrays; 9 10 /** 11 * @author ChengJianSheng 12 * @date 2019-11-10 13 */ 14 @Configuration 15 public class WebConfig { 16 17 @Bean 18 public FilterRegistrationBean registration() { 19 MDCInsertingServletFilter filter = new MDCInsertingServletFilter(); 20 FilterRegistrationBean registration = new FilterRegistrationBean(filter); 21 registration.setUrlPatterns(Arrays.asList("/*")); 22 registration.setOrder(1); 23 return registration; 24 } 25 26 // @Bean 27 // public MDCInsertingServletFilter mdcInsertingServletFilter() { 28 // return new MDCInsertingServletFilter(); 29 // } 30 31 } 定义一个拦截器 1 package com.cjs.example.interceptor; 2 3 import org.slf4j.MDC; 4 import org.springframework.web.servlet.HandlerInterceptor; 5 import org.springframework.web.servlet.ModelAndView; 6 7 import javax.servlet.http.HttpServletRequest; 8 import javax.servlet.http.HttpServletResponse; 9 import java.util.UUID; 10 11 /** 12 * @author ChengJianSheng 13 * @date 2019-11-10 14 */ 15 public class MyInterceptor implements HandlerInterceptor { 16 17 private final String SESSION_KEY = "sessionId"; 18 19 @Override 20 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 21 String sessionId = UUID.randomUUID().toString().replace("-", ""); 22 MDC.put(SESSION_KEY, sessionId); 23 return true; 24 } 25 26 @Override 27 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 28 29 } 30 31 @Override 32 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 33 MDC.remove(SESSION_KEY); 34 } 35 } 拦截器配置 1 package com.cjs.example.config; 2 3 import com.cjs.example.interceptor.MyInterceptor; 4 import org.springframework.context.annotation.Configuration; 5 import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 6 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 7 8 /** 9 * @author ChengJianSheng 10 * @date 2019-11-10 11 */ 12 @Configuration 13 public class InterceptorConfig implements WebMvcConfigurer { 14 15 @Override 16 public void addInterceptors(InterceptorRegistry registry) { 17 registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**"); 18 } 19 } Controller和Service 1 package com.cjs.example.controller; 2 3 import com.cjs.example.service.HelloService; 4 import lombok.extern.slf4j.Slf4j; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.beans.factory.annotation.Qualifier; 7 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 8 import org.springframework.web.bind.annotation.GetMapping; 9 import org.springframework.web.bind.annotation.RequestMapping; 10 import org.springframework.web.bind.annotation.RequestParam; 11 import org.springframework.web.bind.annotation.RestController; 12 13 /** 14 * @author ChengJianSheng 15 * @date 2019-11-10 16 */ 17 @Slf4j 18 @RestController 19 @RequestMapping("/hello") 20 public class HelloController { 21 22 @Autowired 23 private HelloService helloService; 24 25 @Autowired 26 @Qualifier("taskExecutor") 27 private ThreadPoolTaskExecutor taskExecutor; 28 29 @GetMapping("/sayHello") 30 public String sayHello(@RequestParam("name") String name) { 31 log.info("收到请求:name={}", name); 32 String str = "Hello, " + name; 33 log.info(str); 34 helloService.print(); 35 36 // Map ctx = MDC.getCopyOfContextMap(); 37 // new Thread(()->{ 38 // MDC.setContextMap(ctx); 39 // log.info("1111");}).start(); 40 41 taskExecutor.submit(()->{log.info("1234234");}); 42 43 return str; 44 } 45 46 } 1 package com.cjs.example.service; 2 3 import lombok.extern.slf4j.Slf4j; 4 import org.springframework.scheduling.annotation.Async; 5 import org.springframework.stereotype.Service; 6 7 /** 8 * @author ChengJianSheng 9 * @date 2019-11-10 10 */ 11 @Slf4j 12 @Service 13 public class HelloService { 14 15 /** 16 * 使用@Aysnc异步执行任务时,虽然指定了Executor但是在真正执行时却不会调我们重写的这个submit或execute方法,因而无法将父线程MDC拷贝到子线程中 17 * 实践表明,必须手动显式调用submit或execute才行,这就相当于我们自己重写了任务的Runnable 18 */ 19 @Async("taskExecutor") 20 public void print() { 21 log.info("This is apple"); 22 } 23 } 线程池配置(可选) 1 package com.cjs.example.config; 2 3 import org.slf4j.MDC; 4 import org.springframework.context.annotation.Bean; 5 import org.springframework.context.annotation.Configuration; 6 import org.springframework.scheduling.annotation.EnableAsync; 7 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 8 9 import java.util.Map; 10 import java.util.concurrent.Future; 11 import java.util.concurrent.ThreadPoolExecutor; 12 13 /** 14 * http://logback.qos.ch/manual/mdc.html#MDC And Managed Threads 15 * @author ChengJianSheng 16 * @date 2019-11-10 17 */ 18 @EnableAsync(proxyTargetClass = true) 19 @Configuration 20 public class ThreadPoolConfig { 21 22 @Bean(name = "taskExecutor") 23 public ThreadPoolTaskExecutor taskExecutor() { 24 // 对于使用@Async方式提交的异步任务不会生效 25 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(){ 26 @Override 27 public void execute(Runnable task) { 28 Map contextMap = MDC.getCopyOfContextMap(); 29 super.execute(()->{ 30 MDC.setContextMap(contextMap); 31 try { 32 task.run(); 33 } finally { 34 MDC.clear(); 35 } 36 }); 37 } 38 39 @Override 40 public Future submit(Runnable task) { 41 Map contextMap = MDC.getCopyOfContextMap(); 42 return super.submit(()->{ 43 MDC.setContextMap(contextMap); 44 try { 45 task.run(); 46 } finally { 47 MDC.clear(); 48 } 49 }); 50 } 51 }; 52 53 executor.setCorePoolSize(10); 54 executor.setMaxPoolSize(20); 55 executor.setQueueCapacity(1000); 56 executor.setKeepAliveSeconds(60); 57 executor.setThreadNamePrefix("cjsTaskExecutor-"); 58 executor.setWaitForTasksToCompleteOnShutdown(true); 59 executor.setAwaitTerminationSeconds(10); 60 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); 61 62 return executor; 63 } 64 } logback配置 1 2 3 4 5 6 %d{HH:mm:ss.SSS} [%thread] [%X{req.remoteHost}] [%X{req.requestURI}] [%X{sessionId}] %-5level %logger{36} - %msg%n 7 8 9 10 11 12 13 运行效果 访问http://localhost:8080/hello/sayHello?name=zhangsan 复制代码 17:09:08.524 [http-nio-8080-exec-1] [0:0:0:0:0:0:0:1] [/hello/sayHello] [551f5632eddc4a1ca4a387464e954143] INFO c.c.e.controller.HelloController - 收到请求:name=zhangsan 17:09:08.525 [http-nio-8080-exec-1] [0:0:0:0:0:0:0:1] [/hello/sayHello] [551f5632eddc4a1ca4a387464e954143] INFO c.c.e.controller.HelloController - Hello, zhangsan 17:09:15.343 [cjsTaskExecutor-2] [0:0:0:0:0:0:0:1] [/hello/sayHello] [551f5632eddc4a1ca4a387464e954143] INFO c.c.e.controller.HelloController - 1234234 17:09:15.348 [cjsTaskExecutor-1] [] [] [] INFO com.cjs.example.service.HelloService - This is apple 复制代码 其它 http://logback.qos.ch/manual/mdc.html 此处没能实现@Async方式的异步任务MDC拷贝,有些遗憾,另外,跨服务调用的TraceId传递也是一个值得思考的问题,不知道Spring Cloud Sleuth的链路跟踪是怎么做的 感谢您的阅读,如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力! 欢迎各位转载,但必须在文章页面中给出作者和原文链接!https://www.cnblogs.com/cjsblog/p/11831046.html