点击关注公众号,实用技术文章及时了解

前言

从文章标题就知道,这篇文章是介绍些什么。

这是我一位朋友的问题反馈:

好像是的,确实这种现象是普遍存在的。

有时候一个业务调用链场景,很长,调了各种各样的方法,看日志的时候,各个接口的日志穿插,确实让人头大。

模糊匹配搜索日志能解决吗? 能解决一点点。 但是不能完全呈现出整个链路相关的日志。

那要做到方便,很显然,我们需要的是把同一次的业务调用链上的日志串起来。

什么效果? 先看一个实现后的效果图:

这样下来,我们再配合模糊匹配查找日志,效果不就刚刚的了。

cat -n info.log |grep "a415ad50dbf84e99b1b56a31aacd209c"

或者

grep -10 'a415ad50dbf84e99b1b56a31aacd209c' info.log   (10是指上下10行)

不多说,开整。

正文

惯例,先看一眼这次实战最终工程的结构:

①pom.xml 依赖

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></dependency><!--lombok配置--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.10</version></dependency>
</dependencies>

②整合logback,打印日志,logback-spring.xml (简单配置下)

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false"><!--日志存储路径--><property name="log" value="D:/test/log" /><!-- 控制台输出 --><appender name="console" class="ch.qos.logback.core.ConsoleAppender"><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><!--输出格式化--><pattern>[%X{TRACE_ID}]  %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern></encoder></appender><!-- 按天生成日志文件 --><appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender"><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!--日志文件名--><FileNamePattern>${log}/%d{yyyy-MM-dd}.log</FileNamePattern><!--保留天数--><MaxHistory>30</MaxHistory></rollingPolicy><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>[%X{TRACE_ID}]  %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern></encoder><!--日志文件最大的大小--><triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"><MaxFileSize>10MB</MaxFileSize></triggeringPolicy></appender><!-- 日志输出级别 --><root level="INFO"><appender-ref ref="console" /><appender-ref ref="file" /></root>
</configuration>

application.yml

server:port: 8826
logging:config: classpath:logback-spring.xml

③自定义日志拦截器 LogInterceptor.java

用途:每一次链路,线程维度,添加最终的链路ID TRACE_ID。

import org.slf4j.MDC;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;/*** @Author: JCccc* @Date: 2022-5-30 10:45* @Description:*/
public class LogInterceptor implements HandlerInterceptor {private static final String TRACE_ID = "TRACE_ID";@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {String tid = UUID.randomUUID().toString().replace("-", "");//可以考虑让客户端传入链路ID,但需保证一定的复杂度唯一性;如果没使用默认UUID自动生成if (!StringUtils.isEmpty(request.getHeader("TRACE_ID"))){tid=request.getHeader("TRACE_ID");}MDC.put(TRACE_ID, tid);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable Exception ex) {MDC.remove(TRACE_ID);}}

MDC(Mapped Diagnostic Context)诊断上下文映射,是@Slf4j提供的一个支持动态打印日志信息的工具。

WebConfigurerAdapter.java 添加拦截器

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** @Author: JCccc* @Date: 2022-5-30 10:47* @Description:*/
@Configuration
public class WebConfigurerAdapter implements WebMvcConfigurer {@Beanpublic LogInterceptor logInterceptor() {return new LogInterceptor();}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(logInterceptor());//可以具体制定哪些需要拦截,哪些不拦截,其实也可以使用自定义注解更灵活完成
//                .addPathPatterns("/**")
//                .excludePathPatterns("/testxx.html");}
}

ps: 其实这个拦截的部分改为使用自定义注解+aop也是很灵活的。

到这时候,其实已经完成,就是这么简单。

我们写个测试接口,看下效果:

@PostMapping("doTest")
public String doTest(@RequestParam("name") String name) throws InterruptedException {log.info("入参 name={}",name);testTrace();log.info("调用结束 name={}",name);return "Hello,"+name;
}
private void testTrace(){log.info("这是一行info日志");log.error("这是一行error日志");testTrace2();
}
private void testTrace2(){log.info("这也是一行info日志");}

效果(OK的):

还没完。

接下来看一个场景, 使用子线程的场景:

故意写一个异步线程,加入这个调用里面:

再次执行看开效果,显然子线程丢失了trackId:

所以我们需要针对子线程使用情形,做调整,思路: 将父线程的trackId传递下去给子线程即可。

①ThreadPoolConfig.java 定义线程池,交给spring管理

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import java.util.concurrent.Executor;/*** @Author: JCccc* @Date: 2022-5-30 11:07* @Description:*/
@Configuration
@EnableAsync
public class ThreadPoolConfig {/*** 声明一个线程池** @return 执行器*/@Bean("MyExecutor")public Executor asyncExecutor() {MyThreadPoolTaskExecutor executor = new MyThreadPoolTaskExecutor();//核心线程数5:线程池创建时候初始化的线程数executor.setCorePoolSize(5);//最大线程数5:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程executor.setMaxPoolSize(5);//缓冲队列500:用来缓冲执行任务的队列executor.setQueueCapacity(500);//允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁executor.setKeepAliveSeconds(60);//线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池executor.setThreadNamePrefix("asyncJCccc");executor.initialize();return executor;}
}

② MyThreadPoolTaskExecutor.java 是我们自己写的,重写了一些方法:

import org.slf4j.MDC;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.Callable;
import java.util.concurrent.Future;/*** @Author: JCccc* @Date: 2022-5-30 11:13* @Description:*/
public final class MyThreadPoolTaskExecutor  extends ThreadPoolTaskExecutor  {public MyThreadPoolTaskExecutor() {super();}@Overridepublic void execute(Runnable task) {super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));}@Overridepublic <T> Future<T> submit(Callable<T> task) {return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));}@Overridepublic Future<?> submit(Runnable task) {return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));}
}

③ThreadMdcUtil.java

import org.slf4j.MDC;import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;/*** @Author: JCccc* @Date: 2022-5-30 11:14* @Description:*/
public final class ThreadMdcUtil {private static final String TRACE_ID = "TRACE_ID";// 获取唯一性标识public static String generateTraceId() {return UUID.randomUUID().toString();}public static void setTraceIdIfAbsent() {if (MDC.get(TRACE_ID) == null) {MDC.put(TRACE_ID, generateTraceId());}}/*** 用于父线程向线程池中提交任务时,将自身MDC中的数据复制给子线程** @param callable* @param context* @param <T>* @return*/public static <T> Callable<T> wrap(final Callable<T> callable, final Map<String, String> context) {return () -> {if (context == null) {MDC.clear();} else {MDC.setContextMap(context);}setTraceIdIfAbsent();try {return callable.call();} finally {MDC.clear();}};}/*** 用于父线程向线程池中提交任务时,将自身MDC中的数据复制给子线程** @param runnable* @param context* @return*/public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {return () -> {if (context == null) {MDC.clear();} else {MDC.setContextMap(context);}setTraceIdIfAbsent();try {runnable.run();} finally {MDC.clear();}};}
}

OK,重启服务,再看看效果:

可以看的,子线程的日志也被串起来了。

来源:blog.csdn.net/qq_35387940/article/

details/125062368

推荐

Java面试题宝典

技术内卷群,一起来学习!!

PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。点“在看”支持我们吧!

手动实现 SpringBoot 日志链路追踪,无需引入组件,日志定位更方便!相关推荐

  1. 微服务架构 | 如何利用好日志链路追踪做性能分析?

    导读:做性能分析听到最多的歪理就是,服务做水平.垂直扩容.分表分库.读写分离.XX中间件.资源静态化等等但是归根到底这些方案都是为了尽可能减少对数据库的访问以及堆栈的释放,提高数据库IO的读写速度和程 ...

  2. Springboot starter开发之traceId请求日志链路追踪

    一.请求链路追踪是什么? 能标识一次请求的完整流程,包括日志打印.响应标识等,以便于出现问题可以快速定位并解决问题. 二.使用步骤 1. 相关知识点 ThreadLocal:一种保证一种规避多线程访问 ...

  3. MDC实现日志链路追踪

    开发过程中难免遇到需要查看日志来找出问题出在哪一环节的情况,而在实际情况中服务之间互相调用所产生的日志冗长且复杂,若是再加上同一时间别的请求所产生的日志,想要精准定位自己想要查看的日志就比较麻烦.为解 ...

  4. 基于SLF4J MDC机制实现日志的链路追踪

    一.打印HTTP request body和response body实现日志跟踪 request 的inputStream和response 的outputStream默认情况下是只能读一次, 不可 ...

  5. 链路追踪在ERP系统中的应用实践

    源宝导读:随着ERP的部署架构越来越复杂,对运维监控.问题排查等工作增加了难度,本文将介绍通过引入链路追踪技术,提高ERP系统问题排查效率,支撑更全面监控系统运行情况的实践过程. 一.导读 随着ERP ...

  6. 阿里云发布链路追踪服务Tracing Analysis

    近日,在杭州云栖大会上,阿里云发布了链路追踪服务Tracing Analysis,成本是自建链路追踪系统的1/5或更少,可为分布式应用的开发者提供完整的调用链路还原.调用请求量统计.链路拓扑.应用依赖 ...

  7. 一键托管,阿里云全链路追踪服务正式商用:成本仅自建1/5或更少

    随着互联网架构的扩张,分布式系统变得日趋复杂,越来越多的组件开始走向分布式化,如微服务.消息收发.分布式数据库.分布式缓存.分布式对象存储.跨域调用,这些组件共同构成了繁杂的分布式网络. 在一次800 ...

  8. 阿里云产品专家解读链路追踪(Tracing Analysis)

    摘要: 概要 阿里云上最近推出了一款新产品 链路追踪 ,专注于帮助开发者快速分析和诊断分布式应用架构下的性能瓶颈,提高微服务时代下的开发诊断效率. 分布式应用环境下的链路追踪,并不是一个新话题.在早些 ...

  9. 服务追踪系统_一键托管,阿里云全链路追踪服务正式商用:成本仅自建1/5或更少...

    随着互联网架构的扩张,分布式系统变得日趋复杂,越来越多的组件开始走向分布式化,如微服务.消息收发.分布式数据库.分布式缓存.分布式对象存储.跨域调用,这些组件共同构成了繁杂的分布式网络. 在一次800 ...

最新文章

  1. python 十进制转二进制,十进制转八进制,十进制转十六进制 的方法
  2. 【项目实践】基于Mask R-CNN的道路物体检测与分割(从数据集制作到视频测试)...
  3. 企业中的混乱:如何对云计算具有信心
  4. python--thread多线程总结
  5. Java后台解析前台的get中文请求
  6. linux下使用idl生成h文件,LINIUX下IDL的安装
  7. Array.Resize(ref arry, size);
  8. 【常用】2DUI跟随3D物体移动(待修复)
  9. linux环境下通过nginx实现tomcat集群
  10. Gb28181-2016 相关标准文档参考
  11. PyQt5-网格布局(QGridLayout)-10
  12. CentOS配置静态IP
  13. reportmachine中怎样实现“数据注脚footer1”里面的内容在每页显示
  14. getParameterValues使用
  15. python中使用缩进来体现代码之间的逻辑关系_Python使用缩进来体现代码之间的逻辑关系....
  16. 160cracked-2
  17. 浅析“关于区块链解决资本主义问题还是社会主义问题”的对与错
  18. 五金制品厂物料产品编码方案(2)
  19. Android 12 Beta正式亮相;5 月 19 日凌晨 1 点 阔别 2 年的 Google I/O 开发者大会内容集锦
  20. R语言使用table1包绘制(生成)三线表、使用单变量分列构建三线表、自定义设置不显示overall

热门文章

  1. win7的Active Directory用户和计算机怎么打开,如何打开 Active Directory 用户和计算机...
  2. gsm模块发中文短信
  3. AE脚本:像素破损撕拉花屏效果Datamosh
  4. [noip模拟赛2017.7.16]
  5. 均热板 热管_使用均热板 提升热管利用率_酷冷至尊 V8GTS_散热器评测-中关村在线...
  6. Java进阶知识笔记14--【junit、反射、注解】
  7. mac citrix workspace删除_Mac平台图片处理工具套装
  8. 企业电子商刊(杂志)制作软件国际标准 iebook超级精灵发布
  9. 关于南京市软件行业协会程序员分会(筹)吸收第一期正式会员(兼工作人员)的通知]
  10. 深度拷贝Excel文件中sheet工作簿