logback mdc日志跟踪

2023-11-06

1、简介

        MDC(Mapped Diagnostic Context,映射调试上下文)是 log4jlogbacklog4j2 提供的一种方便在多线程条件下记录日志的功能。MDC 可以看成是一个与当前线程绑定的哈希表,可以往其中添加键值对。MDC 中包含的内容可以被同一线程中执行的代码所访问

        当前线程的子线程会继承其父线程中的 MDC 的内容。当需要记录日志时,只需要从 MDC 中获取所需的信息即可。MDC 的内容则由程序在适当的时候保存进去。对于一个 Web 应用来说,通常是在请求被处理的最开始保存这些数据。

2、API说明

  • clear() :移除所有MDC
  • get (String key) :获取当前线程MDC中指定key的值
  • getContext() :获取当前线程MDC的MDC
  • put(String key, Object o) :往当前线程的MDC中存入指定的键值对
  • remove(String key) :删除当前线程MDC中指定的键值对

3、作用

帮助开发快速定位日志位置。

  • 用户请求日志关联
  • 项目间请求日志关联
  • 多服务间日志聚合
  • 调用关系分析
  • 日志分析

4、要求与实现

4.1 要求

  • 一次请求生成一次RequestId,并且RequestId唯一
  • 一次请求响应给前端,都需要返回RequestId字段,接口正常、业务异常、系统异常,都需要返回该字段
  • 一次请求在控制台或者日志文件打印的日志,都需要显示RequestId
  • 一次请求的入参和出参都需要打印
  • 对于异步操作,需要在异步线程的日志同样显示RequestId

4.2 实现

4.2.1 接口

        前端或上一个服务节点调用当前服务节点时。

  1. header中获取RequestId,如果不存在,说明是前端,不是上一个服务节点,就生成一个RequestId。
  2. 将RequestId放到header中,前端或下个服务节点可以获取到RequestId。
  3. 将RequestId放入MDC中,日志中打印RequestId。

代码实现:

package com.ybwei.log.mdc.demo.interceptor;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ybwei.log.mdc.demo.constant.MDCConstant;
import com.ybwei.log.mdc.demo.util.MyStringUtils;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 接口日志
 *
 * @author ybwei
 * @date 2022/2/17 11:40
 **/
@Aspect
@Component
@Slf4j
public class WebLogAspect {

    private final ObjectMapper mapper;

    ThreadLocal<LogRecord> logRecordThreadLocal = new ThreadLocal<>();

    @Autowired
    public WebLogAspect(ObjectMapper mapper) {
        this.mapper = mapper;
    }

    @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public void requestLog() {
    }

    @Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
    public void postLog() {
    }

    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
    public void getLog() {
    }

    /**
     * 请求参数
     *
     * @param joinPoint
     * @return void
     * @throws
     * @methodName: doBefore
     * @author ybwei
     * @date 2022/2/17 13:54
     */
    @Before("requestLog() || postLog() || getLog()")
    public void doBefore(JoinPoint joinPoint) {
        //1、获取requestId
        String requestId = getRequestId();
        //2、往当前线程的MDC中存入指定的键值对
        MDC.put(MDCConstant.REQUEST_ID, requestId);
        //3、返回值设置requestId
        ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletResponse httpServletResponse = sra.getResponse();
        httpServletResponse.setHeader(MDCConstant.REQUEST_ID, requestId);
        //4、打印参数日志
        //4.1 接口地址
        String name = joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName();
        logRecordThreadLocal.set(new LogRecord(System.currentTimeMillis(), name));
        for (Object object : joinPoint.getArgs()) {
            if (object instanceof MultipartFile || object instanceof HttpServletRequest || object instanceof HttpServletResponse) {
                continue;
            }
            try {
                log.info("请求接口:{},请求参数 :{}", name, mapper.writeValueAsString(object));
            } catch (Exception e) {
                log.info("打印请求参数错误:", e);
            }
        }
    }

    /**
     * 获取requestId
     *
     * @methodName: getRequestId
     * @return: java.lang.String
     * @author: geoffrey
     * @date: 2022/5/10
     **/
    private String getRequestId() {
        //1、从header中获取requestId
        ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest httpServletRequest = sra.getRequest();
        String requestId = httpServletRequest.getHeader(MDCConstant.REQUEST_ID);
        if (StringUtils.isNotBlank(requestId)) {
            return requestId;
        }
        return MyStringUtils.generateUUIDNoCenterLine();
    }

    /**
     * 返回参数
     *
     * @param response
     * @return void
     * @throws
     * @methodName: doAfterReturning
     * @author ybwei
     * @date 2022/2/17 13:54
     */
    @AfterReturning(returning = "response", pointcut = "requestLog() || postLog() || getLog()")
    public void doAfterReturning(Object response) {
        //1、打印日志
        LogRecord logRecord = logRecordThreadLocal.get();
        log.info("接口所花费时间{}毫秒,接口:{}", System.currentTimeMillis() - logRecord.getStartTime(), logRecord.getName());
        logRecordThreadLocal.remove();
        if (response != null) {
            try {
                log.info("返回参数:{}", mapper.writeValueAsString(response));
            } catch (Exception e) {
                log.info("打印返回参数错误:", e);
            }
        }
        //2、删除当前线程MDC中指定的键值对
        MDC.remove(MDCConstant.REQUEST_ID);

    }

    @AllArgsConstructor
    @Getter
    class LogRecord {
        /**
         * 开始时间
         *
         * @author: ybwei
         * @date: 2022/3/22 9:53
         */
        private Long startTime;
        /**
         * 接口地址
         *
         * @author: ybwei
         * @date: 2022/3/22 9:55
         */
        private String name;
    }
}

4.2.2 异步和定时任务

配置线程池

package com.ybwei.log.mdc.demo.config.thread;

import com.ybwei.log.mdc.demo.constant.MDCConstant;
import com.ybwei.log.mdc.demo.util.MyStringUtils;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;


/**
 * MDC线程池
 * 实现内容传递
 *
 * @author geoffrey
 * @version V1.0
 * @className MdcTaskExecutor
 * @date 2022/5/9
 **/
@Slf4j
public class MdcTaskExecutor extends ThreadPoolTaskExecutor {

    @Override
    public <T> Future<T> submit(Callable<T> task) {
        log.info("mdc thread pool task executor submit");
        Map<String, String> context = MDC.getCopyOfContextMap();
        return super.submit(() -> {
            T result;
            if (context != null) {
                //将父线程的MDC内容传给子线程
                MDC.setContextMap(context);
            } else {
                //直接给子线程设置MDC
                MDC.put(MDCConstant.REQUEST_ID, MyStringUtils.generateUUIDNoCenterLine());
            }
            try {
                //执行任务
                result = task.call();
            } finally {
                try {
                    MDC.clear();
                } catch (Exception e) {
                    log.warn("MDC clear exception", e);
                }
            }
            return result;
        });
    }

    @Override
    public void execute(Runnable task) {
        log.info("mdc thread pool task executor execute");
        Map<String, String> context = MDC.getCopyOfContextMap();
        super.execute(() -> {
            if (context != null) {
                //将父线程的MDC内容传给子线程
                MDC.setContextMap(context);
            } else {
                //直接给子线程设置MDC
                MDC.put(MDCConstant.REQUEST_ID, MyStringUtils.generateUUIDNoCenterLine());
            }
            try {
                //执行任务
                task.run();
            } finally {
                try {
                    MDC.clear();
                } catch (Exception e) {
                    log.warn("MDC clear exception", e);
                }
            }
        });
    }
}

异步方法

添加@Async("commonThreadPool")

@Async("commonThreadPool")
public void async() {
    log.info("异步处理");
}

定时任务

@Async(value = "scheduleThreadPool")
@Scheduled(fixedRate = 2000)
public void fixedRate() {
    log.info("定时间隔任务 fixedRate = {}", LocalDateTime.now());
}

4.3 调用第三方日志

实现RestTemplate拦截器

package com.ybwei.log.mdc.demo.interceptor;

import com.ybwei.log.mdc.demo.constant.MDCConstant;
import org.slf4j.MDC;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * @author geoffrey
 * @version V1.0
 * @className RestTemplateTraceIdInterceptor
 * @date 2022/5/10
 **/
public class RestTemplateTraceIdInterceptor implements ClientHttpRequestInterceptor {
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        String traceId = MDC.get(MDCConstant.REQUEST_ID);
        if (traceId != null) {
            request.getHeaders().add(MDCConstant.REQUEST_ID, traceId);
        }
        return execution.execute(request, body);
    }
}

为RestTemplate添加拦截器:

@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
    // boot中可使用RestTemplateBuilder.build创建
    RestTemplate restTemplate = new RestTemplate(new BufferingClientHttpRequestFactory(clientHttpRequestFactory));
    // 配置请求工厂
//        restTemplate.setRequestFactory(clientHttpRequestFactory);
    List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
    interceptors.add(new LoggingRequestInterceptor());
    restTemplate.setInterceptors(interceptors);
    return restTemplate;
}

5、示例代码

share: 分享仓库 - Gitee.com

6、日志跟踪

快速定位日志,提高解决问题效率。

6.1 浏览器端

访问接口,响应头会返回x-request-Id。

6.2 服务器端

> grep '50db615d2d6648dba59cdfab574c3eb7' logs/all/all.log 
[INFO ] 2022-06-02 09:51:46.354 [50db615d2d6648dba59cdfab574c3eb7] [http-nio-8080-exec-72] c.z.z.s.interceptor.WebLogAspect - 请求接口:com.zt.ztsc.server.modules.user.controller.SysConfMerchantController.findMerchantList,请求参数 :{"name":"","type":null,"pageNum":1,"pageSize":10}
[INFO ] 2022-06-02 09:51:46.355 [50db615d2d6648dba59cdfab574c3eb7] [http-nio-8080-exec-72] c.z.z.s.interceptor.WebLogAspect - 请求接口:com.test.controller.TestController.findList,请求参数 :"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJNSU5HIiwiZXhwIjoxNjU0MTM0NzAwLCJpYXQiOjE2NTQxMzQ3MDAsInVzZXJJZCI6IjEwMDAwMSJ9.LzWC6ZSU3CJoIXfKanvKNoInz6cOsjFPYdkPh4MxwY8"
.....

 根据浏览器的x-request-Id可以跟踪此次访问日志。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

logback mdc日志跟踪 的相关文章

随机推荐

  • Cesium教程(十九):Cesium粒子系统

    Cesium教程 十九 Cesium粒子系统 1 粒子系统 1 1 什么是粒子系统 Cesium粒子系统是一种模拟复杂物理效应的图形技术 是由小图像组成的集合 当他们在一起形成更复杂的 模糊 对象时 会形成火 烟 云或烟火等 1 2 初始粒
  • 将figma的设计图上传到蓝湖

    前言 将figma的设计图上传到蓝湖 实现步骤 1 把文件保存到本地 拿到一个 fig的文件 2 打开一个在线的 ui编辑软件 MasterGo 注 该软件跟figma一样 都可以在线编辑 好用 点个赞 注册个账户或者微信登录 3 导入我们
  • 如何将TXT文本编码变为GB2312

    如果你用记事本 另存为选择ansi就是gb2312 另外 如果你不确认文件是什么编码 推荐用Replace Pioneer 首先用Replace Pioneer检测出一个文件是什么编码 1 选择Tools gt Encoding Detec
  • RPC服务

    1 rpc是什么 RPC Remote Procedure Call 是远程过程调用 它是一种通过网络从远程计算机程序上请求服务 而不需要了解底层网络技术的协议 简单的理解是一个节点请求另一个节点提供的服务 RPC协议假定某些传输协议的存在
  • Latex报错_101719.bbl! perhaps a missing \item. immediate help.immediate help.l.24\end{thebibliography}

    IEEElatex模板 报这个错 可能是bib文件中文献重复 或者tex文件里面cite的文献不在bib文件里面造成的 要仔细检查bib文件 补充 IEEE使用bib应用参考文献的标准用法 去IEEE官网 IEEE Manuscript T
  • 入职后发现是超级「屎山」代码,怎么破?网友看不下去了...... 丨黑马头条

    大家好 我是播妞 昨天有位粉丝和我聊天 说他喜欢的一位女生说他是 技术宅 问我这是褒义还是贬义 思考了两秒我回答 如果妹子喜欢你就是褒义 不喜欢你就是贬义 看到文章的各位技术宅们 大家觉得是什么意思呢 又到了每周的黑马头条时间 最近的技术圈
  • 09 线性回归及矩阵运算

    09 线性回归及矩阵运算 线性回归 定义 通过一个或者多个自变量与因变量之间进行建模的回归分析 其中可以为一个或者多个自变量之间的线性组合 一元线性回归 涉及到的变量只有一个 多元线性回归 变量两个或以上 通用公式 h w w0 w1x1
  • kafka生产者幂等与事务

    目录 前言 幂等 事务 总结 参考资料 前言 Kafka 消息交付可靠性保障以及精确处理一次语义的实现 所谓的消息交付可靠性保障 是指 Kafka 对 Producer 和 Consumer 要处理的消息提供什么样的承诺 常见的承诺有以下三
  • 【杨氏矩阵】

    文章目录 前言 一 题目描述 二 题目解析 一 解法1 二分查找 二 解法2 Step wise线性搜索解法 总结 前言 大家好 我是熊猫 今天要和大家一起学习的是在杨氏矩阵中寻找数字的问题 一 题目描述 有一个数字矩阵 矩阵的每行从左到右
  • centos和ubantu安装软件的区别

    序言 安装软件时经常会遇到类似下面这张图 那这些不同的Linux版本有哪些区别 安装软件又应该注意哪些 本文将就以下问题展开讨论 Linux发行版本有哪些 Linux不同版本安装软件的方式和区别 说明 图中FreeBSD Oracle So
  • 小技巧:如何在R语言与excel/word之间进行复制粘贴

    原创 康哥 勤用统计 问 R语言中能进行类似电脑中control C control V的操作吗 现实数据处理过程中 经常需要进行R语言与Excel word等文件的数据传输 笨方法 是直接导出or导入整个文件 答 R语言也可以与Excel
  • [蓝桥杯][算法提高VIP]我们的征途是星辰大海

    题目 题目链接 题解 实现题 这也很基础 写代码的时候细心点就行 代码 include
  • 时序预测

    时序预测 MATLAB实现PSO BP时间序列预测 粒子群优化BP神经网络时间序列预测 多指标评价 目录 时序预测 MATLAB实现PSO BP时间序列预测 粒子群优化BP神经网络时间序列预测 多指标评价 效果一览 基本介绍 程序设计 参考
  • 动态路由协议

    动态路由协议 在各台路由器上 激活同一种协议后 路由器间沟通计算获取未知路由信息 最终生成路由表实现全网可达 静态协议的缺点 1 在中大型网络中配置量大 2 不能实时收敛 不能基于拓扑的变化而变化 动态协议的优点 1 在中大型的配置量较静态
  • CRM-统计分析--线索统计--新增线索数量折线图(接口实现)

    统计分析 线索统计 新增线索数量折线图 需求 统计出一段时间内的每一天 新增的线索数量 通过每天新增的线索数量和线索总数量 分析线上线下活动的执行情况 难度级别 B级 接口名 report salesStatistics 请求方式 get请
  • 个人免签支付云端监听免挂机支付宝收款

    GOGO支付 打不开了 貌似liangle 然后自己根据原理实现了一套 方案用来替代 gogo支付收款 云端监听免挂机 支付宝采用抓包技术云端调用官方接口 获取收款信息 监听效率非常高 而且很稳定 GOGO支付个人免签支付系统实现原理说明
  • 让flexmojos modulefiles支持通配符文件集,模块输出不带版本号且按包结构输出

    jar包下载 http download csdn net source 1879817 1 从http svn sonatype org flexmojos tags flexmojos 3 2 0 check out 源码 2 修改fl
  • 《Attention Is All You Need》

    论文地址 https arxiv org abs 1706 03762 谷歌于2017年发布论文 Attention Is All You Need 提出了一个只基于attention的结构来处理序列模型相关的问题 比如机器翻译 相比传统的
  • 什么是Base64

    一 什么是Base64 百度百科中对Base64有一个很好的解释 Base64是网络上最常见的用于传输8Bit字节码的编码方式之一 Base64就是一种基于64个可打印字符来表示二进制数据的方法 什么是 可打印字符 呢 为什么要用它来传输8
  • logback mdc日志跟踪

    1 简介 MDC Mapped Diagnostic Context 映射调试上下文 是 log4j logback及log4j2 提供的一种方便在多线程条件下记录日志的功能 MDC 可以看成是一个与当前线程绑定的哈希表 可以往其中添加键值