栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

logback mdc日志跟踪

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

logback mdc日志跟踪

1、简介

        MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 、logback及log4j2 提供的一种方便在多线程条件下记录日志的功能。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;


@Aspect
@Component
@Slf4j
public class WebLogAspect {

    private final ObjectMapper mapper;

    ThreadLocal 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() {
    }

    
    @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);
            }
        }
    }

    
    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();
    }

    
    @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 {
        
        private Long startTime;
        
        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;



@Slf4j
public class MdcTaskExecutor extends ThreadPoolTaskExecutor {

    @Override
    public  Future submit(Callable task) {
        log.info("mdc thread pool task executor submit");
        Map 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 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;


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 interceptors = new ArrayList<>();
    interceptors.add(new LoggingRequestInterceptor());
    restTemplate.setInterceptors(interceptors);
    return restTemplate;
}
5、示例代码

share: 分享仓库 - Gitee.com

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/874668.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号