(4) spring-boot+logback+log4j2+MDC

spring boot内部使用Commons Logging来记录日志,但也保留外部接口可以让一些日志框架来进行实现,例如Java Util Logging,Log4J2还有Logback。如果你想用某一种日志框架来进行实现的话,就必须先配置,默认情况下,Spring Boot会用Logback来记录日志,并用INFO级别输出到控制台。

1、日志配置

1.1、默认配置

依赖于开发者选择的日志框架,这些对应的配置文件会被加载;

日志框架 配置文件
Logback logback-spring.xml, logback-spring.groovy, logback.xml, logback.groovy
Log4j log4j-spring.properties, log4j-spring.xml, log4j.properties, log4j.xml
Log4j2 log4j2-spring.xml, log4j2.xml
JDK(JAVA Util Logging) logging.properties

Spring Boot官方推荐优先使用带有-spring的文件名作为你的日志配置(如使用logback-spring.xml,而不是logback.xml),命名为logback-spring.xml的日志配置文件,spring boot可以为它添加一些spring boot特有的配置项,默认的命名规则,并且放在 src/main/resources 下面即可。

1.2、修改spring-boot日志配置

若不想使用spring-boot的默认日志路径及名称,可以在application.properties修改日志配置,配置如下:

logging.config=classpath:spring/log4j2.xml

2、logback配置

Logback是log4j框架的作者开发的新一代日志框架,它效率更高、能够适应诸多的运行环境,同时天然支持SLF4J。

2.1、pom配置


  org.springframework.boot
  spring-boot-starter-logging

若项目中添加以上依赖,则spring-boot将自动使用logback作为应用的日志框架;Spring Boot启动的时候,由org.springframework.boot.logging.Logging-Application-Listener根据情况初始化并使用。但在实际的开发中无需直接添加此依赖,因为spring-boot-starter中已包含了此依赖。

2.2、配置文件位置

配置文件名称为logback-spring.xml并放在classpath下,即项目的resources目录下;或自定义配置文件名称和位置,并在application.properties中配置,如下所示:

logging.config=classpath:config/logback-spring.xml

2.3、配置文件



    
    SpringBootDemo
    
    
    

    
    
        
        ${LOG_PATH}/${APPDIR}/log_error.log
        
        
            
            ${LOG_PATH}/${APPDIR}/error/log-error-%d{yyyy-MM-dd}.%i.log
            
            
                100MB
            
        
        
        true
        
        
            ===%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n
            utf-8
        
        
        
            error
            ACCEPT
            DENY
        
    

    
    
        
        ${LOG_PATH}/${APPDIR}/log_warn.log
        
        
            
            ${LOG_PATH}/${APPDIR}/warn/log-warn-%d{yyyy-MM-dd}.%i.log
            
            
                100MB
            
        
        
        true
        
        
            ===%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n
            utf-8
        
        
        
            warn
            ACCEPT
            DENY
        
    


    
    
        
        ${LOG_PATH}/${APPDIR}/log_info.log
        
        
            
            ${LOG_PATH}/${APPDIR}/info/log-info-%d{yyyy-MM-dd}.%i.log
            
            
                100MB
            
        
        
        true
        
        
            ===%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n
            utf-8
        
        
        
            info
            ACCEPT
            DENY
        
    


    
    
        
        ${LOG_PATH}/${APPDIR}/log_all.log
        
        
            
            ${LOG_PATH}/${APPDIR}/all/log-all-%d{yyyy-MM-dd}.%i.log
            
            
                100MB
            
        
        
        true
        
        
            ===%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n
            utf-8
        
        
        
            trace
            ACCEPT
            ACCEPT
        
    

    
        
        
            ===%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n
            utf-8
        
        
        
            debug
        
    

    
    
    

    
    
        
        
        
        

        
        
    

2.3.1、设置上下文名称

每个logger都关联到logger上下文,默认上下文名称为“default”。但可以使用设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改,可以通过%contextName来打印日志上下文名称。

SpringBootDemo
2.3.2、设置变量

用来定义变量值的标签, 有两个属性,name和value;其中name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。




此处设置系统日志的path和应用日志的目录;

2.3.3、输出到RollingFileAppender

随着应用的运行时间越来越长,日志也会增长的越来越多,将他们输出到同一个文件并非一个好办法。RollingFileAppender用于切分文件日志,配置如下:

    
    
        
        ${LOG_PATH}/${APPDIR}/log_error.log
        
        
            
            ${LOG_PATH}/${APPDIR}/error/log-error-%d{yyyy-MM-dd}.%i.log
            
            
                100MB
            
        
        
        true
        
        
            ===%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n
            utf-8
        
        
        
            error
            ACCEPT
            DENY
        
    
  • 此处配置日志类型为RollingFileAppender,配置滚动存储的策略,一天一个文件,当文件大于100M时,按文件大小分文件存储。
  • 配置日志文件的格式,定义了格式;
  • 定义日志过滤器,本文件的日志级别为error,当日志级别为error时进行存储,不符合时不处理;
2.3.4、配置说明

本日志配置了五个处理器,分别处理error、warn、info、all及debug日志;

  • error处理器只处理并存储error级别的日志;
  • warn处理器只处理并存储warn级别的日志;
  • info处理器只处理并存储info级别的日志;
  • all处理器处理所有级别的日志;
  • debug及以上的日志级别都会在标准输出中输出;

最终的日志文件会包含4个:

  • log_all.log:存储所有debug及以上级别的日志;
  • log_error.log:存储error级别的日志;
  • log_warn.log:存储warn级别的日志;
  • log_info.log:存储info级别的日志;

3、log4j2配置

由于spring-boot的所有starter的默认日志框架为logback,所以在配置log4j2时,需要将logback的包给排除,以免日志包冲突;

3.1、pom配置

    
        
            org.springframework.boot
            spring-boot-starter-web
            
                
                    org.springframework.boot
                    spring-boot-starter-logging
                
            
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
            
                
                    org.springframework.boot
                    spring-boot-starter-logging
                
            
        

        
            org.springframework.boot
            spring-boot-starter-thymeleaf
        

       


        
            mysql
            mysql-connector-java
            runtime
        
        
        
            log4j
            log4j
            1.2.17
        


        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
            1.3.2
            
                
                    org.springframework.boot
                    spring-boot-starter-logging
                
            
        

        
            com.alibaba
            druid-spring-boot-starter
            1.1.8
            
                
                    org.springframework.boot
                    spring-boot-starter-logging
                
            
        
        
            org.springframework.boot
            spring-boot-starter-log4j2
        

        
            org.springframework.boot
            spring-boot-starter-aop
        
    

将所有会包含spring-boot-starter-logging的包给去除,并添加spring-boot-starter-log4j2的依赖包;

3.1、配置文件



    
        C:\Users\zhaozhou\Desktop\demo
        %-d{yyyy-MM-dd HH:mm:ss}[ %t:%r ]- [%X{userName}] - [%X{reqId}] - [%-5p] %c-%M:%L - %m%n%throwable{full}
    
    
        
        
        
            
        
        
            
            
            
            
        

        
            
            
            
            
        

        
            
            
            
            
        

    
    

        
        

        
        


        

        

        


        
            
            
            
            
        
    

配置文件大体和上面logback的配置差不多,在此不多赘述;

4、MDC配置

4.1、MDC简介

MDC 是 Mapped Diagnostic Context 的缩写,“映射诊断上下文”看起来高大上的样子,其实是非常简单的,就是一个临时存放k-v对的容器。和普通Map的区别是它是基于ThreadLocal实现的,所以不存在资源竞争问题,可以放心的往里面放东西。其主要用于打LOG时跟踪一个“会话“、一个”事务“。举例,有一个web controller,在同一时间可能收到来自多个客户端的请求,如果一个请求发生了错误,我们要跟踪这个请求从controller开始一步步都执行到了哪些代码、有哪些log的输出。这时我们可以看log文件,但是log文件是多个请求同时记录的,基本无法分辨哪行是哪个请求产生的,虽然我们可以看线程,但线程可能被复用,也是很难分辨出,这时MDC就派上用场了。

4.2、主要api

public class MDC {
    public static void put(String key, String val) //设置k-v
    public static String get(String key) //获取value
    public static void remove(String key) //移除k-v
    public static void clear() //clearMDC
    public static Map getCopyOfContextMap() //获取MDC的备份map
    public static void setContextMap(Map contextMap) //设置MDC的map
}
  • put()和setContextMap()主要进行属性设置;
  • get()和getCopyOfContextMap()主要进行属性获取;
  • remove()移除属性信息,主要在线程结束时处理设置的属性;

4.3、基于spring的HandlerInterceptorAdapter

此种方法主要适用场景为跟踪http请求,通过拦截http请求,设置MDC,并在日志中打印设置参数,即可通过日志跟踪请求链;

4.3.1、HandlerInterceptorAdapter拦截器配置

登录拦截器:

public class LoginInterceptor extends HandlerInterceptorAdapter {

    private static String MDC_KEY_USER_NAME = "userName";
    private static String MDC_KEY_REQ_ID = "reqId";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        MDC.put(MDC_KEY_USER_NAME,"zhaozhou");
        MDC.put(MDC_KEY_REQ_ID, UUID.randomUUID().toString());
        return super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {

        super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        MDC.remove(MDC_KEY_USER_NAME);
        MDC.remove(MDC_KEY_REQ_ID);
        super.afterCompletion(request, response, handler, ex);
    }

    @Override
    public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        super.afterConcurrentHandlingStarted(request, response, handler);
    }
}

注册拦截器:

@Component
public class MvcLoginInterceptor implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册拦截器
        InterceptorRegistration ir = registry.addInterceptor(new LoginInterceptor());
        // 配置拦截的路径
        ir.addPathPatterns("/**");
        // 配置不拦截的路径
        ir.excludePathPatterns("/static/**","/templates/**");
    }
}
4.3.2、log4j2的pattern配置

在log4j2-spring.xml的配置做如下修改:


    C:\Users\zhaozhou\Desktop\demo
    %-d{yyyy-MM-dd HH:mm:ss}[ %t:%r ]- [%X{userName}] - [%X{reqId}] - [%-5p] %c-%M:%L - %m%n%throwable{full}

在patternlayout属性中添加userName和reqId的打印;

对于logback日志的配置同此处一样;

4.3.2、输出结果

浏览器请求:http://localhost:8888/

2018-10-31 12:11:06[ http-nio-8888-exec-8:17662 ]- [] - [] - [DEBUG] org.springframework.beans.factory.support.DefaultListableBeanFactory-doGetBean:254 - Returning cached instance of singleton bean 'testController'
2018-10-31 12:11:06[ http-nio-8888-exec-8:17662 ]- [] - [] - [DEBUG] org.springframework.web.servlet.DispatcherServlet-doDispatch:979 - Last-Modified value for [/] is: -1
2018-10-31 12:11:06[ http-nio-8888-exec-8:17663 ]- [zhaozhou] - [d3b60c00-7946-4d5d-bf15-016d286c110c] - [DEBUG] org.mybatis.spring.SqlSessionUtils-getSqlSession:97 - Creating a new SqlSession
2018-10-31 12:11:06[ http-nio-8888-exec-8:17664 ]- [zhaozhou] - [d3b60c00-7946-4d5d-bf15-016d286c110c] - [DEBUG] org.mybatis.spring.SqlSessionUtils-registerSessionHolder:148 - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1366610e] was not registered for synchronization because synchronization is not active
2018-10-31 12:11:06[ http-nio-8888-exec-8:17665 ]- [zhaozhou] - [d3b60c00-7946-4d5d-bf15-016d286c110c] - [DEBUG] org.springframework.jdbc.datasource.DataSourceUtils-doGetConnection:114 - Fetching JDBC Connection from DataSource
2018-10-31 12:11:06[ http-nio-8888-exec-8:17665 ]- [zhaozhou] - [d3b60c00-7946-4d5d-bf15-016d286c110c] - [DEBUG] org.mybatis.spring.transaction.SpringManagedTransaction-openConnection:87 - JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@2c6224d7] will not be managed by Spring
2018-10-31 12:11:06[ http-nio-8888-exec-8:17666 ]- [zhaozhou] - [d3b60c00-7946-4d5d-bf15-016d286c110c] - [DEBUG] com.springboot.demo.dao.UserDao.getUserById-debug:159 - ==> Preparing: select id, name, tel, email, create_time from t_user WHERE id = ?
2018-10-31 12:11:06[ http-nio-8888-exec-8:17667 ]- [zhaozhou] - [d3b60c00-7946-4d5d-bf15-016d286c110c] - [DEBUG] com.springboot.demo.dao.UserDao.getUserById-debug:159 - ==> Parameters: 1(Long)
2018-10-31 12:11:06[ http-nio-8888-exec-8:17673 ]- [zhaozhou] - [d3b60c00-7946-4d5d-bf15-016d286c110c] - [DEBUG] com.springboot.demo.dao.UserDao.getUserById-debug:159 - <== Total: 1
2018-10-31 12:11:06[ http-nio-8888-exec-8:17674 ]- [zhaozhou] - [d3b60c00-7946-4d5d-bf15-016d286c110c] - [DEBUG] org.mybatis.spring.SqlSessionUtils-closeSqlSession:191 - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1366610e]
2018-10-31 12:11:06[ http-nio-8888-exec-8:17674 ]- [zhaozhou] - [d3b60c00-7946-4d5d-bf15-016d286c110c] - [DEBUG] org.springframework.jdbc.datasource.DataSourceUtils-doReleaseConnection:340 - Returning JDBC Connection to DataSource
2018-10-31 12:11:06[ http-nio-8888-exec-8:17675 ]- [zhaozhou] - [d3b60c00-7946-4d5d-bf15-016d286c110c] - [DEBUG] org.springframework.web.servlet.view.ContentNegotiatingViewResolver-getMediaTypes:269 - Requested media types are [text/html, application/xhtml+xml, image/webp, application/xml;q=0.9, */*;q=0.8] based on Accept header types and producible media types [*/*])
2018-10-31 12:11:06[ http-nio-8888-exec-8:17675 ]- [zhaozhou] - [d3b60c00-7946-4d5d-bf15-016d286c110c] - [DEBUG] org.springframework.web.servlet.view.BeanNameViewResolver-resolveViewName:81 - No matching bean found for view name '/index.html'
2018-10-31 12:11:06[ http-nio-8888-exec-8:17675 ]- [zhaozhou] - [d3b60c00-7946-4d5d-bf15-016d286c110c] - [DEBUG] org.springframework.web.servlet.view.ContentNegotiatingViewResolver-getBestView:348 - Returning [org.thymeleaf.spring5.view.ThymeleafView@199e0a7e] based on requested media type 'text/html'
2018-10-31 12:11:06[ http-nio-8888-exec-8:17675 ]- [zhaozhou] - [d3b60c00-7946-4d5d-bf15-016d286c110c] - [DEBUG] org.springframework.web.servlet.DispatcherServlet-render:1319 - Rendering view [org.thymeleaf.spring5.view.ThymeleafView@199e0a7e] in DispatcherServlet with name 'dispatcherServlet'
2018-10-31 12:11:06[ http-nio-8888-exec-8:17678 ]- [] - [] - [DEBUG] org.springframework.web.servlet.DispatcherServlet-processRequest:1000 - Successfully completed request
2018-10-31 12:11:06[ http-nio-8888-exec-8:17678 ]- [] - [] - [DEBUG] org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter-doFilterInternal:104 - Cleared thread-bound request context: org.apache.catalina.connector.RequestFacade@4b2bca43
2018-10-31 12:11:06[ http-nio-8888-exec-1:17767 ]- [] - [] - [DEBUG] org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter-initContextHolders:114 - Bound request context to thread: org.apache.catalina.connector.RequestFacade@4b2bca43
2018-10-31 12:11:06[ http-nio-8888-exec-1:17767 ]- [] - [] - [DEBUG] org.springframework.web.servlet.DispatcherServlet-doService:891 - DispatcherServlet with name 'dispatcherServlet' processing GET request for [/favicon.ico]

可以看到,开始系统启动过程中是无userName喝reqId打印的,当进行http请求时,MDC配置的两个参数都打印出来,且reqId一直不变,通过此id即可追踪调用链;

4.4、基于AOP设置MDC

此种方法所有类型的调用请求的调用跟踪,例如http、基于tcp的RPC调用等;

4.4.1、pom配置

    org.springframework.boot
    spring-boot-starter-aop

4.4.2、切面定义
@Aspect
@Component
public class LoginAop {

    private static String MDC_KEY_USER_NAME = "userName";
    private static String MDC_KEY_REQ_ID = "reqId";

    @Pointcut("execution(public * com.springboot.demo.controller..*.*(..))")
    public void webLog(){}

    @Before("webLog()")
    public void before(JoinPoint joinPoint){
        MDC.put(MDC_KEY_USER_NAME,"zhaozhou");
        MDC.put(MDC_KEY_REQ_ID, UUID.randomUUID().toString());
    }

    @AfterReturning(pointcut = "webLog()", returning = "ret")
    public void afterReturning(Object ret){
        MDC.remove(MDC_KEY_USER_NAME);
        MDC.remove(MDC_KEY_REQ_ID);
    }
}

MDC参数与4.3.1配置相同;

4.4.3、输出结果

浏览器请求:http://localhost:8888/

2018-10-31 12:46:24[ http-nio-8888-exec-6:24774 ]- [] - [] - [DEBUG] org.springframework.beans.factory.support.DefaultListableBeanFactory-doGetBean:254 - Returning cached instance of singleton bean 'testController'
2018-10-31 12:46:24[ http-nio-8888-exec-6:24774 ]- [] - [] - [DEBUG] org.springframework.web.servlet.DispatcherServlet-doDispatch:979 - Last-Modified value for [/] is: -1
2018-10-31 12:46:24[ http-nio-8888-exec-6:24774 ]- [zhaozhou] - [519e55f8-45be-4923-a2cc-1b5181eb3050] - [DEBUG] org.mybatis.spring.SqlSessionUtils-getSqlSession:97 - Creating a new SqlSession
2018-10-31 12:46:24[ http-nio-8888-exec-6:24774 ]- [zhaozhou] - [519e55f8-45be-4923-a2cc-1b5181eb3050] - [DEBUG] org.mybatis.spring.SqlSessionUtils-registerSessionHolder:148 - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@675b44f3] was not registered for synchronization because synchronization is not active
2018-10-31 12:46:24[ http-nio-8888-exec-6:24775 ]- [zhaozhou] - [519e55f8-45be-4923-a2cc-1b5181eb3050] - [DEBUG] org.springframework.jdbc.datasource.DataSourceUtils-doGetConnection:114 - Fetching JDBC Connection from DataSource
2018-10-31 12:46:24[ http-nio-8888-exec-6:24775 ]- [zhaozhou] - [519e55f8-45be-4923-a2cc-1b5181eb3050] - [DEBUG] org.mybatis.spring.transaction.SpringManagedTransaction-openConnection:87 - JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@121b39d5] will not be managed by Spring
2018-10-31 12:46:24[ http-nio-8888-exec-6:24776 ]- [zhaozhou] - [519e55f8-45be-4923-a2cc-1b5181eb3050] - [DEBUG] com.springboot.demo.dao.UserDao.getUserById-debug:159 - ==> Preparing: select id, name, tel, email, create_time from t_user WHERE id = ?
2018-10-31 12:46:24[ http-nio-8888-exec-6:24776 ]- [zhaozhou] - [519e55f8-45be-4923-a2cc-1b5181eb3050] - [DEBUG] com.springboot.demo.dao.UserDao.getUserById-debug:159 - ==> Parameters: 1(Long)
2018-10-31 12:46:24[ http-nio-8888-exec-6:24782 ]- [zhaozhou] - [519e55f8-45be-4923-a2cc-1b5181eb3050] - [DEBUG] com.springboot.demo.dao.UserDao.getUserById-debug:159 - <== Total: 1
2018-10-31 12:46:24[ http-nio-8888-exec-6:24783 ]- [zhaozhou] - [519e55f8-45be-4923-a2cc-1b5181eb3050] - [DEBUG] org.mybatis.spring.SqlSessionUtils-closeSqlSession:191 - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@675b44f3]
2018-10-31 12:46:24[ http-nio-8888-exec-6:24783 ]- [zhaozhou] - [519e55f8-45be-4923-a2cc-1b5181eb3050] - [DEBUG] org.springframework.jdbc.datasource.DataSourceUtils-doReleaseConnection:340 - Returning JDBC Connection to DataSource
2018-10-31 12:46:24[ http-nio-8888-exec-6:24784 ]- [] - [] - [DEBUG] org.springframework.web.servlet.view.ContentNegotiatingViewResolver-getMediaTypes:269 - Requested media types are [text/html, application/xhtml+xml, image/webp, application/xml;q=0.9, */*;q=0.8] based on Accept header types and producible media types [*/*])
2018-10-31 12:46:24[ http-nio-8888-exec-6:24784 ]- [] - [] - [DEBUG] org.springframework.web.servlet.view.BeanNameViewResolver-resolveViewName:81 - No matching bean found for view name '/index.html'

可以看到,输出与4.3.2基本相同;

参考源码:https://github.com/zhaozhou11/spring-boot-demo.git

相关阅读:
spring-boot基础环境搭建 【https://www.jianshu.com/p/ee36bb9faa10】
spring-boot配置详解【https://www.jianshu.com/p/1d037ab638ef】
spring-boot+druid+mybatis环境搭建【https://www.jianshu.com/p/e6c9e9945e45】

参考博客:

http://blog.51cto.com/11931236/2058708
http://tengj.top/2017/04/05/springboot7/
https://juejin.im/post/5a1f86f0f265da4326529c61
https://juejin.im/post/5b128f326fb9a01e8b7814c4
https://blog.csdn.net/Evankaka/article/details/50637994
https://blog.csdn.net/wohaqiyi/article/details/72853962
https://blog.csdn.net/wohaqiyi/article/details/72853962
https://my.oschina.net/gmd/blog/615849
https://www.jianshu.com/p/44640b298c45
https://blog.csdn.net/u012050154/article/details/77370297

你可能感兴趣的:((4) spring-boot+logback+log4j2+MDC)