前言
正文
前言
从文章标题就知道,这篇文章是介绍些什么。
这是我一位朋友的问题反馈:
好像是的,确实这种现象是普遍存在的。
有时候一个业务调用链场景,很长,调了各种各样的方法,看日志的时候,各个接口的日志穿插,确实让人头大。
模糊匹配搜索日志能解决吗? 能解决一点点。 但是不能完全呈现出整个链路相关的日志。
那要做到方便,很显然,我们需要的是把同一次的业务调用链上的日志串起来。
什么效果? 先看一个实现后的效果图:
这样下来,我们再配合模糊匹配查找日志,效果不就刚刚的了。
cat -n info.log |grep a415ad50dbf84e99b1b56a31aacd209c
或者
grep -10 'a415ad50dbf84e99b1b56a31aacd209c' info.log (10是指上下10行)
不多说,开整。
基于 spring boot + mybatis plus + vue & element 实现的后台管理系统 + 用户小程序,支持 rbac 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/yunaiv/ruoyi-vue-pro
视频教程:https://doc.iocoder.cn/video/
正文
惯例,先看一眼这次实战最终工程的结构:
①pom.xml 依赖
org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-logging org.projectlombok lombok 1.16.10
②整合logback,打印日志,logback-spring.xml (简单配置下)
[%x{trace_id}] %d{yyyy-mm-dd hhss.sss} [%thread] %-5level %logger{50} - %msg%n ${log}/%d{yyyy-mm-dd}.log 30 [%x{trace_id}] %d{yyyy-mm-dd hhss.sss} [%thread] %-5level %logger{50} - %msg%n 10mb
application.yml
server: port: 8826logging: 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; @override public 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; } @override public 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: */@configurationpublic class webconfigureradapter implements webmvcconfigurer { @bean public loginterceptor loginterceptor() { return new loginterceptor(); } @override public 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@enableasyncpublic 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(); } @override public void execute(runnable task) { super.execute(threadmdcutil.wrap(task, mdc.getcopyofcontextmap())); } @override public future submit(callable task) { return super.submit(threadmdcutil.wrap(task, mdc.getcopyofcontextmap())); } @override public 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 * @return */ public static callable wrap(final callable callable, final map 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 context) { return () -> { if (context == null) { mdc.clear(); } else { mdc.setcontextmap(context); } settraceidifabsent(); try { runnable.run(); } finally { mdc.clear(); } }; }}
ok,重启服务,再看看效果:
可以看的,子线程的日志也被串起来了。
什么是同时同频全双工通信模式呢?
江宁开发区引领千亿级智能电网产业,建设世界一流智能电网产业地标
“世界第一”成功晋级,海信激光电视见证比利时击败卫冕冠军葡萄牙
NOISEKEN ESS-2000 静电放电发生器
PCB layout中的走线设计
Spring Boot如何实现日志链路追踪
贵州大力推动大数据发展,构建开放体系完善物流促进外贸推动“外循环”
中国第3代半导体半导体理想封装材料——高导热氮化硅陶瓷基板突破“卡脖子”难题
深度学习聚类的综述
Dropout如何成为SDR的特殊情况
宇树科技工业级机器狗去宝钢“上班”了
为何联想会选择在这么短时间内提交就撤回申请
Linux内核结构介绍
加西贝拉即将推出的新产品采用意法半导体的先进电源技术
如何快速检查电动机控制电路_检查线路步骤及方法
招投标资讯|2022年联通数科物联网事业部南京南部新城智慧灯杆建设(二期)公开比选项目
关于电声配件行业防尘防水透声膜的解决方案
传三星新获屏下光学指纹识别专利 Note 9手机首发
AI的发展也许已经悄然进入2.0阶段
回顾2019GTC大会上中国初创企业的最新技术和创新展示方案