Java日志追踪唯一id如何实现?

SpringBoot 2.3+
Java 1.8+

Slf4j记录日志,每条打印的日志都会有不同的ID,不方便追踪当前请求日志记录,给当前请求添加唯一ID,
网上找到一些比较老的文章,说的是过滤器AOP实现,
现在主流是啥方式啊,大佬们

方案1

https://tlog.yomahub.com/pages/eea781/
找到个开源的轻量级包,适用于小项目
image.png

方案2,根据回答zxdposter的思路

image.png

1.TraceInterceptor拦截器

@Component
public class TraceInterceptor implements HandlerInterceptor {

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

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

2.TraceInterceptor注册

@Configuration
public class TraceConfig implements WebMvcConfigurer {
    @Autowired
    private TraceInterceptor traceInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(traceInterceptor)
                .addPathPatterns("/**")//指定该类拦截的url
                .excludePathPatterns("/static/**");//过滤静态资源
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowCredentials(true)
                .allowedMethods("GET", "POST", "DELETE", "PUT", "OPTIONS", "HEAD")
                .allowedHeaders("*")
                .maxAge(3600);
    }
}

3.application.properties 文件路径

logging.file.path=/home/logs/baba

4.logback-spring.xml 配置文件

<configuration scan="true" scanPeriod="10 seconds">
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <include resource="org/springframework/boot/logging/logback/console-appender.xml" />

    <springProperty scope="context" name="fileActive" source="spring.profiles.active"/>

    <property name="LOG_PATH" value="${LOG_PATH:-.}"/>
    <property name="CONSOLE_LOG_PATTERN" value="%X{TID}|--${CONSOLE_LOG_PATTERN}"/>

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>${LOG_PATH}/info.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/info-%d{yyyyMMdd}.log.%i</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>16MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>10</maxHistory>
        </rollingPolicy>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} -[%X{TID}]- [%thread] %-5level %logger{36} -%msg%n
            </Pattern>
        </layout>
    </appender>

    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
        <File>${LOG_PATH}/error.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/error-%d{yyyyMMdd}.log.%i
            </fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>16MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>10</maxHistory>
        </rollingPolicy>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%msg%n</Pattern>
        </layout>
    </appender>

    <!-- hibernate日志输入 -->
    <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="INFO"/>
    <logger name="org.hibernate.type.descriptor.sql.BasicExtractor" level="INFO"/>
    <logger name="org.hibernate.SQL" level="TRACE"/>

    <root level="INFO">
        <appender-ref ref="INFO_FILE"/>
        <appender-ref ref="ERROR_FILE"/>
        <appender-ref ref="CONSOLE" />
    </root>

</configuration>
阅读 3.9k
3 个回答

我的方案

我认为最好的方案是用拦截器 + LogFormat + MDC

利用日志框架自身提供的特性解决问题。

第一步

首先配置日志格式,下面的格式是我从 springboot 中拷贝的,可以直接使用,之后增加一个 %X{REQUEST_ID} 标志占位。

logging:
  pattern:
    console: '%clr(%d{${LOG_DATEFORMAT_PATTERN:yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:%5p}) %clr(${PID: }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint}%clr(%X{REQUEST_ID}){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}'
    file: '%d{${LOG_DATEFORMAT_PATTERN:yyyy-MM-dd HH:mm:ss.SSS}} ${LOG_LEVEL_PATTERN:%5p} ${PID: } --- [%t] %-40.40logger{39} :%X{REQUEST_ID} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}'

第二步

增加一个 http 请求拦截器,我用的是 spring boot 2.7,实现方式是增加 WebMvcConfigurer 类型的 bean,再实现 addInterceptors 方法,增加自定义的 HandlerInterceptor 类。

第三步

实现自定义 HandlerInterceptor 类中的方法 preHandle
在每次请求前,生成一个唯一 ID,调用 MDC 的静态方法 put

示例(kotlin)

class MvcLoggingConfigurer : WebMvcConfigurer {
    override fun addInterceptors(registry: InterceptorRegistry) {
        registry.addInterceptor(LoggingHandlerInterceptor())
    }

    internal class LoggingHandlerInterceptor : HandlerInterceptor {
        override fun preHandle(
            httpServletRequest: HttpServletRequest,
            httpServletResponse: HttpServletResponse,
            o: Any
        ): Boolean {
            val requestId = httpServletRequest.getHeader(SystemConstant.REQUEST_ID)
            if (CharSequenceUtil.isNotBlank(requestId)) {
                MDC.put(SystemConstant.LOGGING_PATTERN_REQUEST_ID, "[$requestId]")
            }
            return true
        }
    }
}

主流方式还是过滤器 + MDC,因为整体逻辑还是比较简单的,所以可能也就没啥变化

用spring-cloud-starter-sleuth的链路追踪; 支持新线程,线程池等场景;过滤器/拦截器+MDC,在新线程中就追踪不到了

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏