在每次请求中打出的每条日志中添加统一的请求唯一标识。通过搜索日志唯一标识,这样就可以非常高效精准排查问题。
例如:
2018-12-21 10:21:26.329 [http-nio-8080-exec-2] [my-server-logtag] [] INFO org.springframework.web.servlet.DispatcherServlet - FrameworkServlet 'dispatcherServlet': initialization completed in 18 ms
2018-12-21 10:21:26.363 [http-nio-8080-exec-2] [my-server-logtag] [aed35d43-3fa2-46e1-bb72-ce9f50c3fe4d] ERROR com.code3.modules.controller.MyController - 日志1
2018-12-21 10:21:26.367 [http-nio-8080-exec-2] [my-server-logtag] [aed35d43-3fa2-46e1-bb72-ce9f50c3fe4d] ERROR com.code3.advice.GlobalExceptionHandler - 系统异常,{}
com.code3.base.BusinessException: 异常信息1
at com.code3.modules.controller.MyController.logtag(MyController.java:18)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at
通过日志标识:aed35d43-3fa2-46e1-bb72-ce9f50c3fe4d,就可以精准定位
想要跟踪请求,第一个想到的就是当请求来时生成一个traceId放在ThreadLocal里,然后打印时去取就行了。但在不改动原有输出语句的前提下自然需要日志框架的支持了,搜索的一番发现主流日志框架都提供了MDC功能。
MDC 介绍
MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能。
MDC 可以看成是一个与当前线程绑定的Map,可以往其中添加键值对。MDC 中包含的内容可以被同一线程中执行的代码所访问。当前线程的子线程会继承其父线程中的 MDC 的内容。当需要记录日志时,只需要从 MDC 中获取所需的信息即可。
MDC 的内容则由程序在适当的时候保存进去。对于一个 Web 应用来说,通常是在请求被处理的最开始保存这些数据。
LogInterceptor.java
package com.interceptors.logtag.interceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
/**
* 日志拦截器
*
* @author zetting
* @date 2018/8/27 11:18
*/
@Component
public class LogInterceptor extends HandlerInterceptorAdapter {
private Logger log = LoggerFactory.getLogger(this.getClass());
/**
* 日志跟踪标识
*/
private static final String TRACE_ID = "TRACE_ID";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String traceId = UUID.randomUUID().toString();
if (StringUtils.isEmpty(MDC.get(TRACE_ID))) {
MDC.put(TRACE_ID, traceId);
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
MDC.remove(TRACE_ID);
}
}
package com.interceptors.logtag.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.ruoyi.framework.interceptor.LogInterceptor;
/**
* MVC配置
*
* @author zet
* @date 2018-12-21 22:30
*/
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Autowired
private LogInterceptor logInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(logInterceptor);
}
}
<configuration>
<jmxConfigurator/>
<property name="app-name" value="my-server" />
<property name="filename" value="server" />
<property name="module-name" value="logtag" />
<appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [${app-name}-${module-name}] [%X{TRACE_ID}] %highlight(%-5level) %cyan(%logger{50}) - %highlight(%msg) %npattern>
encoder>
appender>
<appender name="fileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/data/log-center/${app-name}/${module-name}/${filename}.logfile>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>/data/log-center/${app-name}/${module-name}/${filename}.%d{yyyy-MM-dd}.logfileNamePattern>
<maxHistory>30maxHistory>
rollingPolicy>
<encoder>
<pattern>[%date{ISO8601}] [%level] %logger{80} [%thread] [${app-name}-${module-name}][%X{TRACE_ID}] ${app-name}-${module-name} ==> %msg%npattern>
encoder>
appender>
<root level="INFO" additivity="false">
<appender-ref ref="fileAppender"/>
<appender-ref ref="consoleAppender"/>
root>
configuration>
打出的日志标识.jpg
gitee源码:https://gitee.com/zetting/my-gather/tree/master/springboot-interceptors-logtag
原创:https://www.jianshu.com/p/b74ade8bbb57
SpringBootApplication使用@ServletComponentScan注解作用
import java.io.IOException;
import java.util.UUID;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import org.slf4j.MDC;
@WebFilter(filterName = "logbackFilter", urlPatterns = "/*")
public class LogbackFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
UUID uuid = UUID.randomUUID();
MDC.put("traceid", uuid.toString().replace("-", ""));
try {
filterChain.doFilter(servletRequest, servletResponse);
} finally {
MDC.remove("traceid");// 保证一次请求一个唯一标识
}
}
@Override
public void destroy() {
}
}
<configuration>
<contextName>ruoyi-admincontextName>
<property name="log.pattern" value="%-5level [%contextName] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger - traceId:%X{traceid} - %msg%n" />
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}pattern>
<charset>utf8charset>
encoder>
appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
root>
configuration>
DEBUG [ruoyi-admin] 2020-04-14 14:34:02.402 [http-nio-80-exec-2] com.ruoyi.system.mapper.SysConfigMapper.selectConfigList_COUNT - traceId:a8309218ae64434d856f922a1abac3b6 - ==> Preparing: SELECT count(0) FROM sys_config
DEBUG [ruoyi-admin] 2020-04-14 14:34:02.404 [http-nio-80-exec-2] com.ruoyi.system.mapper.SysConfigMapper.selectConfigList_COUNT - traceId:a8309218ae64434d856f922a1abac3b6 - ==> Parameters:
DEBUG [ruoyi-admin] 2020-04-14 14:34:02.440 [http-nio-80-exec-2] com.ruoyi.system.mapper.SysConfigMapper.selectConfigList_COUNT - traceId:a8309218ae64434d856f922a1abac3b6 - <== Total: 1
DEBUG [ruoyi-admin] 2020-04-14 14:34:02.444 [http-nio-80-exec-2] com.ruoyi.system.mapper.SysConfigMapper.selectConfigList - traceId:a8309218ae64434d856f922a1abac3b6 - ==> Preparing: SELECT config_id, config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark FROM sys_config order by create_time desc LIMIT ?
DEBUG [ruoyi-admin] 2020-04-14 14:34:02.445 [http-nio-80-exec-2] com.ruoyi.system.mapper.SysConfigMapper.selectConfigList - traceId:a8309218ae64434d856f922a1abac3b6 - ==> Parameters: 10(Integer)
DEBUG [ruoyi-admin] 2020-04-14 14:34:02.479 [http-nio-80-exec-2] com.ruoyi.system.mapper.SysConfigMapper.selectConfigList - traceId:a8309218ae64434d856f922a1abac3b6 - <== Total: 4
自定义 LogbackFilter 需要实现 Filter
%cyan(%X{wtraceid}) 自定义traceid的颜色
日志颜色设置参考:
https://www.cnblogs.com/sxdcgaq8080/p/7885340.html
https://blog.csdn.net/qq_34313827/article/details/80394325
注:这个部署线上环境时候切记要注释掉!
原创:https://www.cnblogs.com/xiao-xin-xin/p/10729280.html
封装参考:https://blog.csdn.net/hanchao5272/article/details/92107651
前两种实现的问题:
MDC使用的InheritableThreadLocal只是在线程被创建时继承,但是线程池中的线程是复用的,后续请求使用已有的线程将打印出之前请求的traceId。这时候就需要对线程池进行一定的包装,在线程在执行时读取之前保存的MDC内容。不仅自身业务会用到线程池,spring项目也使用到了很多线程池,比如@Async异步调用,zookeeper线程池、kafka线程池等。不管是哪种线程池都大都支持传入指定的线程池实现。
详细参考:
https://blog.csdn.net/yangcheng33/article/details/80796129
https://github.com/TiantianUpup/springboot-log/tree/master/springboot-trace
https://www.jianshu.com/p/ae855779f30b
分布式环境下,由于系统不再是单点,一个业务可能会有多个系统参与执行。这就涉及到请求在多个系统之间来回转发,如果没有一个全局的标识能够追踪单个业务调用,那么就会在发生业务故障时盲目查日志,造成问题定位困难。
如果我们能在系统间透明的传递一个全局唯一ID,将请求在分布式系统中的流转路径聚合并在使用中保存和传递该id,最后通过页面进行可视化的展示,让开发、运维等人员能够轻松的发现系统故障,那么对问题的快速定位,系统的快速恢复有着非凡的意义。
实现目标
本文我们就根据谷歌的dapper论文的模型,自己开发一个最简的分布式链路追踪框架。工程上传至github,可以自行下载源码研究。
详细参考:自己写分布式链路追踪框架1–TraceId/SpanId/ParentSpanId生成及传递