当前位置: 首页 > 科技观察

生产环境找日志太麻烦怎么办?建议了解日志框架的MDC功能

时间:2023-03-23 10:18:28 科技观察

对于每一个开发者来说,查询接口的执行日志都是一个高频操作。每当测试显示接口有问题时,我们就需要到服务器或日志系统中去检查错误的原因。一般情况下,我们会通过对应的关键字或者接口地址来查询接口报什么错误,但是这样带来了一个问题,就是我们可能会少记录或者忘记输入一些关键字,导致查询记录有点硬.那么有没有一种简单高效的方法,即使我们不在日志中打印任何关键字,系统也会自动生成一个关键字,让我们一次性查询到该接口的所有日志记录呢?MDCMDC是日志门面框架SLF4J提供的一个类,可以提供多线程情况下的日志功能。log4j、logback、log4j2都实现了这个类。MDC本质上可以看成是一个ThreadLocal,由于其线程安全的特性,可以让我们轻松安全的保存数据。MDC的主要API包括clear()、get()、put()、remove()等方法,简洁的API让我们很容易上手。使用方法1、修改日志打印格式以日志框架logback为例,在logback.log中找到日志打印规则的配置,博主这里演示,指定为reqId。<编码器><模式>-%X{reqId}%d{yyyy-MM-ddHH:mm:ss.SSS}[%thread]%-5level%file:%line%msg%nUTF-82.添加过滤器MDCFilterps。也可以使用拦截器,效果是一样的。拦截各个接口。@Component@AllArgsConstructor@Order(Ordered.HIGHEST_PRECEDENCE)@Slf4jpublicclassMDCFilterimplementsFilter{@Overridepublicvoidinit(FilterConfigfilterConfig)throwsServletException{}@OverridepublicvoiddoFilter(ServletRequestrequest,ServletResponseresponse,FilterChain链){try{//为每个请求接口生成一个requestIdStringrequestId=RandomUtil.randomNumbers(10);//这里的reqId是上面配置的,要保持一致MDC.put("reqId","reqId:"+requestId);链。doFilter(请求,响应);}最后{MDC.clear();}}@Overridepublicvoiddestroy(){}}经过两个简单的步骤,就配置好了。让我们来看看效果。@PostMapping(value="/mdcTest")publicResponseEntitymdcTest(Stringid,Stringname){log.info("测试日志打印,id={},name={}",id,name);log.info("测试日志打印1");log.info("测试日志打印2");log.info("测试日志打印3");log.info("测试日志打印4");返回响应实体。ok().build();}每一行日志都有一个关键字reqId:9723829830,所以我们在查询日志的时候,只需要查询关键字9723829830,就可以直接查出该接口的所有执行记录。如果想要更方便,也可以直接将这个关键字输出到各个接口的响应头或者响应体中。进阶使用对于普通的web应用,我们可以直接拦截各个接口,自动生成一个requestid。对于微服务项目,一个接口可能会产生很多服务调用,那么如何一次查看系统中所有的日志呢??日志的收集本文不考虑,先说说requestid如何传递。其实也很简单。当我们有多个系统之间的调用时,将reqId放在请求的header中进行传输,下游系统就可以获取到这个id。比如下面的拦截器:publicclassLogInterceptorimplementsHandlerInterceptor{@OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{//如果有上层调用,从headerStringtraceId=request.getHead("reqId");if(traceId==null){//如果没有,生成一个默认的traceId=RandomUtil.randomNumbers(10);}MDC.put("reqId",traceId);返回真;}@OverridepublicvoidpostHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,ModelAndViewmodelAndView)throwsException{}@OverridepublicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,Exceptionex)throwsException{//调用结束后删除MDC。删除(“reqId”);}}MDC的一些问题我们上面提到,MDC的本质是ThreadLocal,将所有数据绑定到当前线程。但是当我们使用多线程时,会带来一个数据丢失的问题。因此,我们需要在线程之间传递数据,保证MDC数据不丢失。以线程池传递数据为例,ThreadPoolTask??Executor提供了一个taskDecorator装饰器,通过它我们可以实现属性传递。首先,定义一个MDCContextDecorator。公共类MDCContextDecorator实现TaskDecorator{@OverridepublicRunnabledecorate(Runnablerunnable){Mapprevious=MDC.getCopyOfContextMap();return()->{try{if(previous!=null){MDC.setContextMap(previous);}runnable.run();}最后{MDC.clear();}};}}然后设置线程池的taskDecorator属性。publicThreadPoolTask??Executorexecutor(){ThreadPoolTask??Executorexecutor=newThreadPoolTask??Executor();executor.setCorePoolSize(5);executor.setMaxPoolSize(20);//...其他属性//设置线程属性的自动传递executor.setTaskDecorator(newMDCContextDecorator());returnexecutor;}最后,MDC的使用其实很简单,对我们查询日志也有很大的帮助,应用非常广泛。有兴趣的同学也可以看看它的内部实现,代码并不复杂。

最新推荐
猜你喜欢