IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> logback mdc日志跟踪 -> 正文阅读

[Java知识库]logback mdc日志跟踪

作者:https://gitee.com/xixingzhe2/share/tree/master/log/logback-mdc-demo

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

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-05-11 16:17:11  更:2022-05-11 16:18:24 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 22:54:44-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码