这篇文章主要介绍“如何使用MDC快速查询应用接口全部执行日志”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“如何使用MDC快速查询应用接口全部执行日志”文章能帮助大家解决问题。
MDC
MDC是日志门面框架SLF4J提供的一个类,可以提供在多线程情况下记录日志的功能,log4j、logback、log4j2都有对这个类的实现。
从本质上来说,MDC可以看做一个ThreadLocal,由于其线程安全的特性,可以让我们轻松安全的保存数据。
MDC主要的API有clear()、get()、put()、remove()方法等,简洁的api让我们使用上手基本没有难度。
如何使用
1,修改日志打印格式
以日志框架logback为例,在logback.xml中,找到日志打印规则的配置,添加**-%X{reqId}** 属性,其中reqId可以任意指定,你写其他的属性也可以,博主这里演示指定为reqId。
<encoder> <pattern>-%X{reqId} %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %file:%line %msg%n</pattern> <charset>UTF-8</charset></encoder>
2,添加过滤器MDCFilter
ps.使用拦截器也可以,效果是一样的。对每个接口做拦截。
@Component@AllArgsConstructor@Order(Ordered.HIGHEST_PRECEDENCE)@Slf4jpublic class MDCFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { try { //给每个请求接口生成一个requestId String requestId = RandomUtil.randomNumbers(10); //这里的reqId就是上面配置的,要保持一致 MDC.put("reqId", "reqId:" + requestId); chain.doFilter(request, response); } finally { MDC.clear(); } } @Override public void destroy() { }}
经过简单的两步就配置好了,下面我们看一下效果。
@PostMapping(value = "/mdcTest")public ResponseEntity<Object> mdcTest(String id, String name) { log.info("测试日志打印,id={},name={}", id, name); log.info("测试日志打印1"); log.info("测试日志打印2"); log.info("测试日志打印3"); log.info("测试日志打印4"); return ResponseEntity.ok().build();}
每一行日志都有一个关键字reqId:9723829830,这样我们查询日志时只需要查询关键字9723829830就可以直接查出来这个接口所有的执行记录了。
如果想更方便的话,也可以把这个关键字直接输出到每一个接口的响应头或者响应体中。
进阶使用
对于普通的web应用我们可以直接拦截每个接口,自动生成一个请求id,那么对于微服务项目,一个接口可能会产生很多服务的调用,那如何一次性查出来所有系统内的日志呢?
对于日志的搜集本文暂不考虑,咱们先说如何做请求id的传递。
其实也很简单,当我们有多个系统间的调用时,把reqId放到request的header中进行传递,然后下游系统获取这个id就可以了。
比如下方的拦截器:
public class LogInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //如果有上层调用就从header中取出上层的ID String traceId = request.getHeader("reqId"); if (traceId == null) { //如果没有,就生成一个默认的 traceId = RandomUtil.randomNumbers(10); } MDC.put("reqId", traceId); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { //调用结束后删除 MDC.remove("reqId"); }}
MDC存在的一些问题
我们在上文说过,MDC的本质是ThreadLocal,它会把数据都绑定到当前线程上。但是当我们使用多线程的时候,就会带来一个数据丢失的问题。
所以,我们需要进行线程间的数据传递,保证MDC数据不丢失。
以线程池传递数据为例,ThreadPoolTaskExecutor提供了一个taskDecorator装饰器,通过这个属性,我们就可以实现属性的传递。
首先,定义一个MDCContextDecorator,
public class MDCContextDecorator implements TaskDecorator { @Override public Runnable decorate(Runnable runnable) { Map<String,String> previous = MDC.getCopyOfContextMap(); return () -> { try { if (previous != null) { MDC.setContextMap(previous); } runnable.run(); } finally { MDC.clear(); } }; }}
然后设置线程池的taskDecorator属性,
public ThreadPoolTaskExecutor executor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(20); //...其他属性 //设置线程属性的自动传递 executor.setTaskDecorator(new MDCContextDecorator()); return executor;}
关于“如何使用MDC快速查询应用接口全部执行日志”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注编程网行业资讯频道,小编每天都会为大家更新不同的知识点。