logback打印日志输出线程ID:切面模式

一、前言

经常处理业务问题的同仁,一定都经常与日志打交道。当并发量高、多线程编程时,日志往往是一大堆,为了快速精确的定位、处理问题,我们需要区分各个用户的不同会话请求,需要从一坨坨日志中做链路追踪。

思路:在输出日志的时候,将每个线程的ID同时输出,当然前提是保证每个线程的ID是唯一的。sl4j 提供的一个工具类MDC,支持 logback和log4j,作用就是扩展变量值到日志中并输出。

二、切面模式输出线程ID

通过自定义切面,拦截有注解@LogId的请求,附加会话ID输出到日志。

  1. 加入POM引用
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
        <exclusions>
            <exclusion>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

     

  2. 自定义切点
    package cn.wuhg.climbing.service.design.patterns.sessionid.aspect;
    
    import java.lang.annotation.*;
    
    /**
     * 自定义日志注解
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface LogId {
        String value() default "";
    }
    

     

  3. 自定义日志切面
    package cn.wuhg.climbing.service.design.patterns.sessionid.aspect;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.slf4j.MDC;
    import org.springframework.stereotype.Component;
    
    import java.util.UUID;
    
    /**
     * 自定义日志切面
     */
    @Aspect
    @Component
    public class LogAspect {
        private final static String SESSION_ID = "sessionId";
    
        /**
         * 自定义切点
         */
        @Pointcut("@annotation(cn.wuhg.climbing.service.design.patterns.sessionid.aspect.LogId)")
        public void pointCut() {
        }
    
        /**
         *  前置通知-记录请求信息
         * @param joinPoint
         */
        @Before("pointCut()")
        public void doBeforeAdvice(JoinPoint joinPoint) {
            // MDC容器增加requestId
            String uuid = UUID.randomUUID().toString().replaceAll("-", "");
            MDC.put(SESSION_ID, uuid);
    
        }
    
        /**
         * 后置通知-记录返回信息
         * @param joinPoint
         * @param result
         */
        @AfterReturning(pointcut = "pointCut()", returning = "result")
        public void doAfterReturningAdvice(JoinPoint joinPoint, Object result) {
            MDC.remove(SESSION_ID);
        }
    
        /**
         * 后置异常通知-记录返回出现异
         * @param joinPoint
         * @param exception
         */
        @AfterThrowing(value = "pointCut()", throwing = "exception")
        public void doAfterThrowingAdvice(JoinPoint joinPoint, Throwable exception) {
            MDC.remove(SESSION_ID);
        }
    
    }
    
    

     

  4. logback配置
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
        <contextName>SpringBootDemo</contextName>
        <property name="LOG_PATH" value="ToolLogs" />
        <!--设置系统日志目录 -->
        <property name="APPDIR" value="design-patterns" />
        <!-- 日志记录器,日期滚动记录 -->
        <appender name="FILEALL"
    		class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_PATH}/${APPDIR}/logs.log</file>
            <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <!-- 归档的日志文件的路径,例如今天是2013-12-21日志,当前写的日志文件路径为file节点指定,可以将此文件与file指定文件路径设置为不同路径,从而将当前日志文件或归档日志文件置不同的目录。 
    				而2013-12-21的日志文件在由fileNamePattern指定。%d{yyyy-MM-dd}指定日期格式,%i指定索引 -->
                <fileNamePattern>${LOG_PATH}/${APPDIR}/logs-%d{yyyy-MM-dd}.%i.log
    			</fileNamePattern>
                <!-- 除按日志记录之外,还配置了日志文件不能超过20M,若超过20M,日志文件会以索引0开始, 命名日志文件,例如log-error-2013-12-21.0.log -->
                <timeBasedFileNamingAndTriggeringPolicy
    				class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>20MB</maxFileSize>
                </timeBasedFileNamingAndTriggeringPolicy>
            </rollingPolicy>
            <!-- 追加方式记录日志 -->
            <append>true</append>
            <!-- 日志文件的格式 -->
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{sessionId}] %-5level %class.%method Line:%-3L - %msg%n</pattern>
                <charset>utf-8</charset>
            </encoder>
        </appender>
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <!--encoder 默认配置为PatternLayoutEncoder -->
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{sessionId}] %-5level %class.%method Line:%-3L - %msg%n</pattern>
                <charset>utf-8</charset>
            </encoder>
            <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息 -->
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                <level>debug</level>
            </filter>
        </appender>
        <!-- 生产环境下,将此级别配置为适合的级别,以免日志文件太多或影响程序性能 -->
        <root level="INFO">
            <appender-ref ref="FILEALL" />
            <appender-ref ref="STDOUT" />
        </root>
    </configuration>

     

  5. Controller接口
    @LogId
    @GetMapping("/log")
        public void logId(){
        log.info("测试slf4j日志线程ID");
    }
  6. 日志截图,红框处是会话ID
  7. 整体切面过滤
    可以通过制定路径范围,来整体过滤,去除了@LogId注解的限制,仅需将自定义切面替换即可
    @Pointcut("execution(public * cn.wuhg.climbing.service.design.patterns..*(..))")
        public void pointCut() {
    }

三、往期推荐

logback打印日志输出线程ID:mvc拦截器模式

 

作者:Smile潇洒Tel 

转载请注明出处,谢谢合作!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章