引言
在日常的后端开发工作中,最常见的操作之一就是看日志排查问题,对于大项目一般使用类似ELK的技术栈统一搜集日志,小项目就直接把日志打印到日志文件。那不管对于大项目或者小项目,查看日志都需要通过某个关键字
进行搜索,从而快速定位到异常日志的位置来进一步排查问题。
对于后端初学者来说,日志的关键字可能就是直接打印某个业务的说明加上业务标识,如果出现问题直接搜索对应的说明或者标识。例如下单场景,可能就直接打印:创建订单,订单编号:xxxx,当有问题的时候,则直接搜索订单编号或者创建订单。在这种方式下,经常会搜索出多条日志,增加问题的排查时长。
所以,今天我们就来说一说这个关键字
的设计,这里我们使用RequestId
进行精确定位问题日志的位置从而解决问题。
需求
目标: 帮助开发快速定位日志位置
思路:当前端进行一次请求的时候,在进行业务逻辑处理之前我们需要生成一个唯一的RequestId
,在业务逻辑处理过程中涉及到日志打印我们都需要带上这个RequestId
,最后响应给前端的数据结构同样需要带上RequestId
。 这样,每次请求都会有一个RequestId
,当某个接口异常则通过前端反馈的RequestId
,后端即可快速定位异常的日志位置。
总结下我们的需求:
- 一次请求生成一次
RequestId
,并且RequestId
唯一 - 一次请求响应给前端,都需要返回
RequestId
字段,接口正常、业务异常、系统异常,都需要返回该字段 - 一次请求在控制台或者日志文件打印的日志,都需要显示
RequestId
- 一次请求的入参和出参都需要打印
- 对于异步操作,需要在异步线程的日志同样显示
RequestId
实现
- 实现生成和存储
RequestId
的工具类
1 | java复制代码public class RequestIdUtils { |
因为我们一次请求会生成一次RequestId
,并且RequestId
唯一,所以这里我们使用使用UUID来生成RequestId
,并且用ThreadLocal进行存储。
- 实现一个AOP,拦截所有的Controller的方法,这里是主要的处理逻辑
1 | java复制代码@Aspect |
简单说明:
- 对于
RequestId
的获取方法getRequestId
,我们优先从header头获取,有网关的场景下一般会从网关传递过来;其次判断是否已经存在,如果存在则直接返回,这里是为了兼容有过滤器并且在过滤器生成了RequestId
的场景;最后之前2中场景都未找到RequestId
,则自己生成,并且返回 MDC.put("REQUEST_ID", requestId)
在我们生成RequestId
之后,需要设置到日志系统中,这样子日志文件才能打印RequestId
printRequestParam
和printResponse
是打印请求参数和响应参数,如果是高并发或者参数很多的场景下,最好不要打印handleRequestId
、handleBusinessException
、handleSystemException
这三个方法分别是在接口正常、接口业务异常、接口系统异常的场景下设置RequestId
- 日志文件配置
1 | xml复制代码<contextName>logback</contextName> |
这里是一个简单的日志格式配置文件,主要是关注[%X{REQUEST_ID}]
, 这里主要是把RequestId
在日志文件中打印出来
- 解决线程异步场景下
RequestId
的打印问题
1 | java复制代码public class MdcExecutor implements Executor { |
这里是一个简单的代理模式,代理了Executor
,在真正执行的run
方法之前设置RequestId
到日志系统中,这样子异步线程的日志同样可以打印我们想要的RequestId
测试效果
- 登录效果
- 正常的业务处理
- 发生业务异常
- 发生系统异常
- 异步线程
最后
通过以上骚操作,同学,你知道怎么使用RequestId看日志了吗?
还不懂的话,请看完整源码地址:gitee.com/anyin/shiro…
有问题的话,欢迎添加个人微信好友进行讨论,个人微信:daydaycoupons
本文转载自: 掘金