• Logback MDC


    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 <?xml version="1.0" encoding="UTF-8"?>
     2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     3          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
     4     <modelVersion>4.0.0</modelVersion>
     5     <parent>
     6         <groupId>org.springframework.boot</groupId>
     7         <artifactId>spring-boot-starter-parent</artifactId>
     8         <version>2.2.1.RELEASE</version>
     9         <relativePath/> <!-- lookup parent from repository -->
    10     </parent>
    11     <groupId>com.cjs.example</groupId>
    12     <artifactId>demo123</artifactId>
    13     <version>0.0.1-SNAPSHOT</version>
    14     <name>demo123</name>
    15 
    16     <properties>
    17         <java.version>1.8</java.version>
    18     </properties>
    19 
    20     <dependencies>
    21         <dependency>
    22             <groupId>org.springframework.boot</groupId>
    23             <artifactId>spring-boot-starter-web</artifactId>
    24         </dependency>
    25 
    26         <dependency>
    27             <groupId>ch.qos.logback</groupId>
    28             <artifactId>logback-classic</artifactId>
    29             <version>1.2.3</version>
    30         </dependency>
    31 
    32         <dependency>
    33             <groupId>org.projectlombok</groupId>
    34             <artifactId>lombok</artifactId>
    35             <optional>true</optional>
    36         </dependency>
    37     </dependencies>
    38 
    39     <build>
    40         <plugins>
    41             <plugin>
    42                 <groupId>org.springframework.boot</groupId>
    43                 <artifactId>spring-boot-maven-plugin</artifactId>
    44             </plugin>
    45         </plugins>
    46     </build>
    47 
    48 </project>

    过滤器配置

     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<String, String> 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<String, String> 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<String, String> 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 <?xml version="1.0" encoding="UTF-8"?>
     2 <configuration>
     3 
     4     <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
     5         <layout class="ch.qos.logback.classic.PatternLayout">
     6             <Pattern>%d{HH:mm:ss.SSS} [%thread] [%X{req.remoteHost}] [%X{req.requestURI}] [%X{sessionId}] %-5level %logger{36} - %msg%n</Pattern>
     7         </layout>
     8     </appender>
     9   
    10     <root level="INFO">
    11         <appender-ref ref="STDOUT" />
    12     </root>
    13 </configuration> 

    运行效果

    访问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的链路跟踪是怎么做的

  • 相关阅读:
    取随机数
    端口号
    cut命令
    渗透
    ssh免密登陆
    漏洞扫描
    信息收集1:DNSEUM命令
    Centos6与Centos7的区别
    HAPROXY+KEEPALIVED实现负载均衡
    this的用法
  • 原文地址:https://www.cnblogs.com/cjsblog/p/11831046.html
Copyright © 2020-2023  润新知