前言新项目查日志太麻烦,多台机器之间查来查去,还不知道是否是同一个请求的。打印日志时使用 MDC 在日志上添加一个 traceId,那这个 traceId 如何跨系统传递呢?html
一样是新项目开发的笔记,由于使用的是分布式架构,涉及到各个系统之间的交互java
这时候就会遇到一个很常见的问题:git
MDC(Mapped Diagnostic Context)是一个映射,用于存储运行上下文的特定线程的上下文数据。所以,若是使用log4j进行日志记录,则每一个线程均可以拥有本身的MDC,该MDC对整个线程是全局的。属于该线程的任何代码均可以轻松访问线程的MDC中存在的值。github
%X{traceId}
配置。<Property name="LOG_PATTERN"> [%d{yyyy-MM-dd HH:mm:ss.SSS}]-[%t]-[%X{traceId}]-[%-5level]-[%c{36}:%L]-[%m]%n </Property> <Property name="LOG_PATTERN_ERROR"> [%d{yyyy-MM-dd HH:mm:ss.SSS}]-[%t]-[%X{traceId}]-[%-5level]-[%l:%M]-[%m]%n </Property> <!-- 省略 --> <!--这个输出控制台的配置--> <Console name="Console" target="SYSTEM_OUT" follow="true"> <!--输出日志的格式--> <PatternLayout charset="UTF-8" pattern="${LOG_PATTERN}"/> </Console>
拦截全部请求,从 header 中获取 traceId 而后放到 MDC 中,若是没有获取到,则直接用 UUID 生成一个。spring
@Slf4j @Component public class LogInterceptor implements HandlerInterceptor { private static final String TRACE_ID = "traceId"; @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception arg3) throws Exception { } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView arg3) throws Exception { } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String traceId = request.getHeader(TRACE_ID); if (StringUtils.isEmpty(traceId)) { MDC.put(TRACE_ID, UUID.randomUUID().toString()); } else { MDC.put(TRACE_ID, traceId); } return true; } }
@Configuration public class WebConfig implements WebMvcConfigurer { @Resource private LogInterceptor logInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(logInterceptor) .addPathPatterns("/**"); } }
由于这边使用的是 FeignClient 进行服务之间的调用,只须要新增请求拦截器便可apache
@Configuration public class FeignInterceptor implements RequestInterceptor { private static final String TRACE_ID = "traceId"; @Override public void apply(RequestTemplate requestTemplate) { requestTemplate.header(TRACE_ID, MDC.get(TRACE_ID)); } }
若是是 Dubbo 能够经过扩展 Filter 的方式传递 traceId服务器
@Activate(group = {"provider", "consumer"}) public class TraceIdFilter implements Filter { @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { RpcContext rpcContext = RpcContext.getContext(); String traceId; if (rpcContext.isConsumerSide()) { traceId = MDC.get("traceId"); if (traceId == null) { traceId = UUID.randomUUID().toString(); } rpcContext.setAttachment("traceId", traceId); } if (rpcContext.isProviderSide()) { traceId = rpcContext.getAttachment("traceId"); MDC.put("traceId", traceId); } return invoker.invoke(invocation); } }
src |-main |-java |-com |-xxx |-XxxFilter.java (实现Filter接口) |-resources |-META-INF |-dubbo |-org.apache.dubbo.rpc.Filter (纯文本文件,内容为:xxx=com.xxx.XxxFilter)
截图以下:架构
测试结果以下:app
dubbo filter 相关源码地址在文末
也能够关注公众号,发送 traceid 获取
固然若是小伙伴们有使用 SkyWalking 或者 Elastic APM 也能够经过如下方式进行注入:dom
<dependency> <groupId>org.apache.skywalking</groupId> <artifactId>apm-toolkit-log4j-2.x</artifactId> <version>{project.release.version}</version> </dependency
而后将 [%traceId]
配置在 log4j2.xml 文件的 pattern 中便可
Elastic APM
%X{trace.id}
配置在 log4j2.xml 文件的 pattern 中虽然有了 traceId 能够进行全链路追踪查询日志,可是毕竟也是在多台服务器上,为了提升查询效率,能够考虑将日志汇总到一块儿。
经常使用的使用方法就是基于 ELK 的日志系统:
本文主要记录近期开发过程当中的遇到的一点问题,但愿对小伙伴也有所帮助。不足之处,欢迎指正。若是小伙伴有其余的建议或者观点欢迎留言讨论,共同进步。