首页 > 其他分享 >SpringBoot项目requestId生成/日志打印

SpringBoot项目requestId生成/日志打印

时间:2022-08-24 22:41:07浏览次数:174  
标签:return SpringBoot private returnValue ex apiName 日志 pjp requestId

原因

SpringBoot项目中的默认日志框架SLF4J,在打印日志时,每行数据都有一个请求ID,这样会方便追踪日志。
也可以使用一些链路追踪框架来实现这种目的。

实现

SLF4J里有一个MDC类,是ThreadLocal的实现,保存在这里的变量都会绑定到某一个请求线程中,在该请求的线程里的日志代码都可以使用设置的变量。例如:

<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{REQUEST_ID}] [%thread] [%-5level] [%logger{0}:%L] : %msg%n</pattern>
其中 %X{参数名} 是引用其中的值

在项目中定义拦截器或者AOP来将拦截住所有的请求,并在MDC中设入变量。

实现步骤

我这里选择配置的是AOP切面来实现在MDC中设入REQUEST_ID来为每一个请求添加ID。

@Aspect
@Slf4j
@Component
public class ApiMessageAdvisor {

    /**
     * 为每一个调用Controller方法的请求做一个切面
     * @param pjp
     * @return
     */
    @Around("execution(public * com.sso.controller..*Controller.*(..))")
    public Object invokeAPI(ProceedingJoinPoint pjp) {
        // 1.获取当前接口对应的类名和方法名
        String apiName = this.getApiName(pjp);
        // 生成RequestId
        String requestId = this.getRequestId();
        // 配置日志文件打印 REQUEST_ID
        MDC.put("REQUEST_ID", requestId);
        Object returnValue = null;
        try{
            // 打印请求参数
            this.printRequestParam(apiName, pjp);
            returnValue = pjp.proceed();
            // 处理RequestId
            this.handleRequestId(returnValue);
        }/*catch (BusinessException ex){
            // 业务异常
            returnValue = this.handleBusinessException(apiName, ex);
        }*/catch (Throwable ex){
            // 系统异常
            returnValue = this.handleSystemException(apiName, ex);
        }finally {
            // 打印响应参数
            this.printResponse(apiName, returnValue);
            RequestIdUtils.removeRequestId();
            // 一定要清理
            MDC.clear();
        }
        return returnValue;
    }

    /**
     * 处理系统异常
     * @param apiName 接口名称
     * @param ex 系统异常
     * @return 返回参数
     */
    private Response handleSystemException(String apiName, Throwable ex){
        log.error("@Meet unknown error when do " + apiName + ":" + ex.getMessage(), ex);
        Response response = new Response(ResponseStatusEnum.EXCEPTION.getCode(), ResponseStatusEnum.EXCEPTION.getDesc(),ex.getMessage());
        response.setRequestId(RequestIdUtils.getRequestId().toString());
        return response;
    }

    /**
     * 处理业务异常
     * @param apiName 接口名称
     * @param ex 业务异常
     * @return 返回参数
     */
    /*private Response handleBusinessException(String apiName, BusinessException ex){
        log.error("@Meet error when do " + apiName + "[" + ex.getCode() + "]:" + ex.getMsg(), ex);
        Response response = new Response(ex.getCode(), ex.getMsg());
        response.setRequestId(RequestIdUtils.getRequestId().toString());
        return response;
    }*/

    /**
     * 填充RequestId
     * @param returnValue 返回参数
     */
    private void handleRequestId(Object returnValue){
        if(returnValue instanceof Response){
            Response response = (Response)returnValue;
            response.setRequestId(RequestIdUtils.getRequestId().toString());
        }
    }

    /**
     * 打印响应参数信息
     * @param apiName 接口名称
     * @param returnValue 返回值
     */
    private void printResponse(String apiName, Object returnValue){
        if (log.isInfoEnabled()) {
            log.info("@@{} done, response: {}", apiName, JSON.toJSONString(returnValue));
        }
    }

    /**
     * 打印请求参数信息
     * @param apiName 接口名称
     * @param pjp 切点
     */
    private void printRequestParam(String apiName, ProceedingJoinPoint pjp){
        Object[] args = pjp.getArgs();
        if(log.isInfoEnabled() && args != null&& args.length > 0){
            for(Object o : args) {
                if(!(o instanceof HttpServletRequest) && !(o instanceof HttpServletResponse) && !(o instanceof CommonsMultipartFile)) {
                    log.info("@@{} started, request: {}", apiName, JSON.toJSONString(o));
                }
            }
        }
    }

    /**
     * 获取RequestId
     * 优先从header头获取,如果没有则自己生成
     * @return RequestId
     */
    private String getRequestId(){
        // 因为如果有网关,则一般会从网关传递过来,所以优先从header头获取
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if(attributes != null && StringUtils.hasText(attributes.getRequest().getHeader("x-request-id"))) {
            HttpServletRequest request = attributes.getRequest();
            String requestId = request.getHeader("x-request-id");
            UUID uuid = UUID.fromString(requestId);
            RequestIdUtils.generateRequestId(uuid);
            return requestId;
        }
        //
        UUID existUUID = RequestIdUtils.getRequestId();
        if(existUUID != null){
            return existUUID.toString();
        }
        RequestIdUtils.generateRequestId();
        return RequestIdUtils.getRequestId().toString();
    }

    /**
     * 获取当前接口对应的类名和方法名
     * @param pjp 切点
     * @return apiName
     */
    private String getApiName(ProceedingJoinPoint pjp){
        String apiClassName = pjp.getTarget().getClass().getSimpleName();
        String methodName = pjp.getSignature().getName();
        return apiClassName.concat(":").concat(methodName);
    }
}

logback-spring.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <contextName>logback</contextName>
    <springProperty scope="context" name="level" source="logging.level.root"/>


    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <Target>System.out</Target>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter" >
            <level>DEBUG</level>
        </filter>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{REQUEST_ID}] [%thread] [%-5level] [%logger{0}:%L] : %msg%n</pattern>
        </encoder>
    </appender>


    <root level="${level}">
        <appender-ref ref="console"/>
    </root>
</configuration>

RequestIdUtils.java

//生成全局RequestId的工具类
public class RequestIdUtils {
    private static final ThreadLocal<UUID> requestIdHolder = new ThreadLocal<>();
    private RequestIdUtils() {
    }
    public static void generateRequestId() {
        requestIdHolder.set(UUID.randomUUID());
    }
    public static void generateRequestId(UUID uuid) {
        requestIdHolder.set(uuid);
    }
    public static UUID getRequestId() {
        return (UUID)requestIdHolder.get();
    }
    public static void removeRequestId() {
        requestIdHolder.remove();
    }
}

补充

当开启异步方法的时候,子线程获取不到主线程的REQUEST_ID
解决方法:配置线程装饰器

/**
 * 装饰器
 */
public class MdcTaskDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(Runnable runnable) {
        Map<String, String> map = MDC.getCopyOfContextMap();
        return () -> {
            try {
                MDC.setContextMap(map);
                String requestId = MDC.get("REQUEST_ID");
                if (StringUtils.isEmpty(requestId)) {
                    requestId = UUID.randomUUID().toString();
                    MDC.put("REQUEST_ID", requestId);
                }
                runnable.run();
            } finally {
                MDC.clear();
            }
        };
    }
}
@Configuration
public class ThreadPoolExecutors {
    private int corePoolSize = Runtime.getRuntime().availableProcessors() + 1;
    private int maxPoolSize = corePoolSize * 2;
    private static final int queueCapacity = 50;
    private static final int keepAliveSeconds = 30;

    @Bean(name = "threadPoolTaskExecutor")
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setMaxPoolSize(maxPoolSize);
        executor.setCorePoolSize(corePoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAliveSeconds);
        // 配置装饰器
        executor.setTaskDecorator(new MdcTaskDecorator());
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }
}

标签:return,SpringBoot,private,returnValue,ex,apiName,日志,pjp,requestId
From: https://www.cnblogs.com/zhaoxu46/p/16137932.html

相关文章

  • 自动化测试如何解决日志问题
    前言前几天在知识星球会员群里,有同学问了一个自动化测试实践中遇到的问题:持续集成的自动化用例很多,测试环境日志level为debug,日志量大概40G/每天,定位问题时日志查询很慢......
  • web项目开发写接口时,为什么需要在关键位置打印日志-2022新项目
    一、业务场景最近在开发新功能,新功能主要就是写app的首页查询接口,接口比较多有十几个,首页会有各种查询,新增操作比较少。由于用户量比较大,据说并发量不小,所以首页的很......
  • SpringBoot Excel导入导出
    一、引入pom.xml依赖<!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.24</version><......
  • 使用zap接收gin框架默认的日志并配置日志归档
    使用zap接收gin框架默认的日志并配置日志归档gin默认的中间件 本文介绍了在基于gin框架开发的项目中如何配置并使用zap来接收并记录gin框架默认的日志和如何配置......
  • SpringBoot使用RabbitMq实现队列和延时队列
    闲来无事看了看RabbitMq的队列,总结了一些队列的实现方法,当然,免不了各种看别人的博客哈哈哈其中延时队列有两种方式,一种是使用TTl+死信队列实现,一种是直接用RabbitMq的官方......
  • 日志切面类
    packagecn.yto.stl.freight.web.api.common.aspect;importcn.yto.base.exception.IException;importcn.yto.stl.freight.web.api.common.domain.DataResult;importlomb......
  • linux查询进程被kill的日志 oom killer
    OOMKilledLinux内核有个机制叫OOMkiller(OutOfMemorykiller),该机制会监控那些占用内存过大,尤其是瞬间占用内存很快的进程,然后防止内存耗尽而自动把该进程杀掉,内核检......
  • python 二次封装logging,打印日志文件名正确,且正确写入/结合pytest执行,日志不输出的问
    基于之前日志问题,二次封装日志后,导致日志输出的文件名不对,取到的文件一直都是当前二次封装的log的文件名,基于这个问题,做了优化,详细看https://www.cnblogs.com/cuitang/p/1......
  • 【Springboot】过滤器
    Springboot实现过滤器实现过滤器方式有两种:Filter过滤器具体实现类通过@WebFilter注解来配置1、Filter过滤器具体实现类1.1实现Filter@Component@Slf4jpublic......
  • springboot+mybatis-plus-join+mysql实现连表查询
    1.简介  Mybatis是目前比较主流的持久层框架,使用非常广泛。Mybatis-Plus是基于Mybatis增强工具包,越来越受到开发人员的喜爱。  在使用Mybatis-Plus开发时,简单的crud......