前言

Github:https://github.com/HealerJean

博客:http://blog.healerjean.com

一、日志格式

1)log4j

%d{yyyy-MM-dd HH:mm:ss} %-5level -[%-32X{REQ_UID}]- %msg%xEx %logger{36}.%M[%L]%n

2)logback

%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level -[%-32X{REQ_UID}] - %msg  -%logger{50}.%M[%L]%n

二、日志打印案例

1、接口调用-需抛异常

@LogIndex
List<SignOutWhiteConfigBatchDto> queryPerson(MerchantSignOutWhiteConfigQueryListReq request) {

  ListResponse<SignOutWhiteConfigBatchDto> response;
  try {
    response = venderSignOutWhiteConfigResource.queryPerson(request);

  } catch (Exception e) {
    log.error("UserService#queryPerson] ERROR, request:{}, e:{}", JSON.toJSONString(request), ExceptionUtils.getStackTrace(e));
    throw new ApiException(ErrorCodeEnum.ERROR_CODE_1011.getCode(), e.getMessage());
  }

  if (response != null && Boolean.TRUE.equals(response.getSuccess())) {
    //log.info("UserService#queryPerson] SUCCESS, request:{}, response:{}", JSON.toJSONString(request), JSON.toJSONString(response));
    return response.getValues();
  }

  log.error("UserService#queryPerson] FAIL, request:{}, response:{}", JSON.toJSONString(request), JSON.toJSONString(response));
  throw new ApiDataException(ErrorCodeEnum.ERROR_CODE_1012.getCode(), response == null ? ErrorCodeEnum.ERROR_CODE_1012.getMessage() : response.getMsg());
}

2、接口调用-可兜底

@LogIndex
public List<Person> queryPerson(List<String> userIds) {
  try {
    UserResponse response  = userServiceThrift.queryPerson(userIds);
    if (response != null && response.isSuccess() && CollectionUtils.isNotEmpty(response.getList())) {
      return res.getList();
    }
    log.error("[UserService#queryPerson] fail, userIds:{}, signInsuranceInfoQueryRes:{} ", 
              JsonUtils.toJsonString(res), JsonUtils.toJsonString(response));
  } catch (Exception e) {
    log.error("[UserService#queryPerson] error, userIds:{}, e:{} ",
              JsonUtils.toJsonString(userIds), ExceptionUtils.getStackTrace(e));
  }
  return null;
}

3、无切面日志-可兜底

public List<Person> queryPerson(List<String> userIds) {
  try {
    UserResponse response  = userServiceThrift.queryPerson(userIds);
    if (response != null && response.isSuccess() && CollectionUtils.isNotEmpty(response.getList())) {
      log.info("[UserService#queryPerson] success, userIds:{}, signInsuranceInfoQueryRes:{} ", 
               JsonUtils.toJsonString(res), JsonUtils.toJsonString(response));
      return res.getList();
    }
    log.error("[UserService#queryPerson] fail, userIds:{}, signInsuranceInfoQueryRes:{} ", 
              JsonUtils.toJsonString(res), JsonUtils.toJsonString(response));
  } catch (Exception e) {
    log.error("[UserService#queryPerson] error, userIds:{}, e:{} ",
              JsonUtils.toJsonString(userIds), ExceptionUtils.getStackTrace(e));
  }
  return null;
}

三、工具:切面打印接口出入参

1、LogAspect

1)LogIndex

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogIndex {

    /**
     * 接口描述
     */
    String value() default "";

    /**
     * 是否打印入参日志,默认是
     */
    boolean enableReq() default true;

    /**
     * 是否打印出参日志,默认是
     */
    boolean enableRes() default true;

    /**
     * 预估时间(毫秒)
     */
    int estimateTime() default -1;

}

2)LogConfiguration

package com.healerjean.proj.config;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;

/**
 * LogConfiguration
 *
 * @author zhangyujin
 * @date 2025/12/18
 */
@Data
@Slf4j
@Configuration
@ConfigurationProperties("log")
public class LogConfiguration {

    /**
     * 动态日志配置
     */
    private Map<String, DynamicLogConfigDTO> dynamicLogMap;

    /**
     * 动态日志配置结果(请求/响应开关)
     *
     * @param key 方法唯一标识(类名#方法名)
     * @return {@link Pair<Boolean, Boolean>} 左:是否打印请求,右:是否打印响应
     */
    public Pair<Boolean, Boolean> dynamicLogRes(String key) {
        DynamicLogConfigDTO keyLogConfig = MapUtils.getObject(dynamicLogMap, key);
        if (Objects.isNull(keyLogConfig)) {
            return null;
        }
        return Pair.of(keyLogConfig.getEnableReq(), keyLogConfig.getEnableRes());
    }

    /**
     * 判断当前请求是否命中采样率(需要打印日志)
     * <p>
     * 采样逻辑:
     * <ul>
     *     <li>未配置采样率(null)或采样率为 1.0:全量打印(命中)</li>
     *     <li>采样率在 (0, 1) 之间:按概率判定</li>
     *     <li>采样率为 0:不打印(不命中)</li>
     * </ul>
     *
     * @param key 方法唯一标识(类名#方法名)
     * @return true-命中采样,需要打印日志;false-未命中,跳过日志打印
     */
    public boolean shouldSample(String key) {
        DynamicLogConfigDTO config = MapUtils.getObject(dynamicLogMap, key);
        if (Objects.isNull(config)) {
            // 未配置时默认全量打印
            return true;
        }

        Double sampleRate = config.getSampleRate();
        if (Objects.isNull(sampleRate)) {
            // 采样率为 null 时默认全量打印
            return true;
        }

        // 采样率 <= 0,不打印
        if (sampleRate <= 0) {
            return false;
        }

        // 采样率 >= 1,全量打印
        if (sampleRate >= 1.0) {
            return true;
        }

        // 按概率采样:使用 ThreadLocalRandom 提升性能
        return ThreadLocalRandom.current().nextDouble() < sampleRate;
    }


    /**
     * LogConfiguration
     *
     * @author zhangyujin
     * @date 2025-12-18 11:12:00
     */
    @Data
    public static class DynamicLogConfigDTO {

        /**
         * 支持req打印
         */
        private Boolean enableReq;

        /**
         * 支持 res 打印
         */
        private Boolean enableRes;

        /**
         * 采样率(0.0-1.0),默认 1.0 表示 100% 打印
         * 例如:0.1 表示 10% 的请求会打印日志
         */
        private Double sampleRate;
    }


}

3)LogAspect

package com.healerjean.proj.aspect;

import com.healerjean.proj.common.anno.LogIndex;
import com.healerjean.proj.config.LogConfiguration;
import com.healerjean.proj.utils.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;

/**
 * 统一日志切面:基于 {@link LogIndex} 注解,对被标注的方法进行入参/返回值/耗时统一记录。
 * <p>
 * 核心职责:
 * <ul>
 *     <li>从目标类中反射获取业务自身的 Logger,保证日志归属正确。</li>
 *     <li>结合 {@link LogConfiguration} 的动态开关,决定是否打印请求/响应。</li>
 *     <li>识别慢调用(超过注解配置的预估耗时),打上 timeOut 标记。</li>
 * </ul>
 * 所属业务模块:通用基础设施 - 日志监控。
 *
 * @author zhangyujin
 * @date 2023/6/14 18:17
 */
@Slf4j
@Component
@Aspect
public class LogAspect {

    /**
     * 日志动态配置:支持运行时控制是否打印某个方法的入参/返回值
     */
    @Resource
    private LogConfiguration logConfiguration;

    /**
     * 环绕通知:拦截所有标注 {@link LogIndex} 的方法,统一打印业务日志。
     * <p>
     * 流程:获取目标 Logger → 反射解析 Method → 执行原方法 → 在 finally 中组装并输出日志。
     *
     * @param pjp      AspectJ 切入点,封装目标方法的调用上下文
     * @param logIndex 方法上的日志注解,提供业务标识、开关、预估耗时等元数据
     * @return 原方法的执行结果
     * @throws Throwable 原方法抛出的任意异常,原样向上抛出,不做吞噬
     */
    @Around(value = "(execution(* *(..)) && @annotation(logIndex))", argNames = "pjp,logIndex")
    public Object printLog(final ProceedingJoinPoint pjp, LogIndex logIndex) throws Throwable {

        // 1. 切面上下文准备与反射解析
        //    1) 入参数组、方法签名等基础上下文
        Object[] methodArgs = pjp.getArgs();
        Signature signature = pjp.getSignature();
        //    2) 通过反射定位真正的 Method,便于后续读取参数名等元数据
        Method targetMethod = null;
        if ((signature instanceof MethodSignature)) {
            MethodSignature methodSignature = (MethodSignature) signature;
            Object targetBean = pjp.getTarget();
            targetMethod = targetBean.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
        } else {
            log.error("signature is not instanceof MethodSignature!");
        }
        //    3) 若无法解析 Method,则直接放行原方法,不再打印结构化日志
        if (targetMethod == null) {
            return pjp.proceed();
        }

        // 2. 执行原方法并组装结构化日志
        //    1) 记录开始时间、构造方法唯一标识(类名#方法名)
        long invokeStartMillis = System.currentTimeMillis();
        String methodKey = pjp.getTarget().getClass().getSimpleName() + "#" + pjp.getSignature().getName();
        
        //    2) 采样率判断:在执行原方法前判断是否需要打印日志
        boolean shouldLog = logConfiguration.shouldSample(methodKey);
        
        Object res = null;
        try {
            //    3) 执行被代理的原方法,捕获返回值
            res = pjp.proceed();
        } finally {

            //    4) 命中采样才执行日志打印逻辑
            if (shouldLog) {
                try {
                    //    5) 动态开关优先级最高:运行时配置可覆盖注解上的静态开关
                    Pair<Boolean, Boolean> dynamicLogSwitch = logConfiguration.dynamicLogRes(methodKey);
                    boolean enableReqLog = Optional.ofNullable(dynamicLogSwitch).map(Pair::getLeft).orElse(logIndex.enableReq());
                    boolean enableResLog = Optional.ofNullable(dynamicLogSwitch).map(Pair::getRight).orElse(logIndex.enableRes());

                    //    6) 若请求和响应都不打印,跳过
                    if (enableReqLog || enableResLog) {
                        Map<String, Object> logMap = new HashMap<>(8);

                        //    7) 入参和出参日志打印
                        if (enableReqLog) {
                            Parameter[] methodParameters = targetMethod.getParameters();
                            Object req = getRequestParams(methodArgs, methodParameters);
                            logMap.put("req", req);
                        }
                        if (enableResLog) {
                            logMap.put("res", res);
                        }

                        //    8) 慢调用判定:耗时超过注解阈值则标记 timeOut
                        long cost = System.currentTimeMillis() - invokeStartMillis;
                        if (logIndex.estimateTime() > -1 && cost >= logIndex.estimateTime()) {
                            logMap.put("timeOut", true);
                        }
                        logMap.put("cost", cost + "ms");
                        //    9) 统一格式输出:[业务标识], 类名.方法名 -> [JSON]
                        log.info(" [{} {}]-> {}", logIndex.value(), methodKey, JsonUtils.toString(logMap));
                    }
                } catch (Exception e) {
                    // 3. 异常兜底:仅打印警告,不影响主流程日志输出
                    log.warn("LogAspect, error", e);
                }
            }
        }

        // 3. 透传原方法的返回值,保持业务无感
        return res;
    }

    /**
     * 将切面捕获到的原始参数数组与方法参数元数据,重构为可读性更强的日志结构。
     * <p>
     * 规则:
     * <ul>
     *     <li>HttpServletRequest / HttpServletResponse 用占位字符串替换,避免巨型对象污染日志。</li>
     *     <li>单参数(且非 Servlet 对象)时直接返回该参数本体,简化日志结构。</li>
     *     <li>多参数时返回 List<Map>,每个 Map 保留 paramName -> paramValue 的对应关系。</li>
     * </ul>
     *
     * @param args       方法实际传入的参数数组
     * @param parameters 方法签名上的参数元数据(用于提取参数名)
     * @return 适合 JSON 序列化打印的请求参数结构;当 args 为 null 时返回 null
     */
    public Object getRequestParams(Object[] args, Parameter[] parameters) {
        // 1. 入参前置校验
        //    1) 参数数组为 null,直接返回,避免后续 NPE
        if (Objects.isNull(args)) {
            return null;
        }
        //    2) 单参数捷径:非 Servlet 类型时直接返回参数本体
        if (args.length == 1 && !(args[0] instanceof HttpServletRequest) && !(args[0] instanceof HttpServletResponse)) {
            return args[0];
        }

        // 2. 多参数场景:逐个参数转换为可读结构
        List<Object> requestParamList = new ArrayList<>();
        try {
            for (int paramIndex = 0; paramIndex < args.length; paramIndex++) {
                Object paramValue = args[paramIndex];
                //    1) Servlet 请求对象用占位符替换,避免序列化体积过大
                if (paramValue instanceof HttpServletRequest) {
                    requestParamList.add("HttpServletRequest");
                    continue;
                }
                //    2) Servlet 响应对象用占位符替换
                if (paramValue instanceof HttpServletResponse) {
                    requestParamList.add("HttpServletResponse");
                    continue;
                }
                //    3) 普通参数:包装为 paramName -> paramValue 的单键 Map
                Map<Object, Object> paramEntry = new HashMap<>(2);
                String paramName = (paramIndex < parameters.length) ? parameters[paramIndex].getName() : "arg" + paramIndex;
                paramEntry.put(paramName, paramValue);
                requestParamList.add(paramEntry);
            }
        } catch (Exception ex) {
            // 3. 异常兜底:仅打印警告,不影响主流程日志输出
            log.warn("LogAspect getRequestParams error", ex);
        }
        return requestParamList;
    }


}

4)TraceIdAspect

@Aspect
@Component
@Slf4j
@Order(1)
public class TraceIdAspect {

    private static final String TRACE_ID_NAME = "REQ_UID";

    @Around("execution(* com.hlj.proj.controller.*Controller.*(..))")
    public Object traceIdSet(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        String traceId = MDC.get(TRACE_ID_NAME);
        if (StringUtils.isBlank(traceId)) {
            MDC.put(TRACE_ID_NAME, UUID.randomUUID().toString().replace("-", ""));
        }
        try {
            return proceedingJoinPoint.proceed();
        } catch (Exception e) {
            Signature sig = proceedingJoinPoint.getSignature();
            String className = sig.getDeclaringTypeName();
            String methodName = proceedingJoinPoint.getSignature().getName();
            log.error("[{}.{}] error:{} ", className, methodName, ExceptionUtils.getStackTrace(e));
            throw e;
        } finally {
            MDC.remove(TRACE_ID_NAME);
        }
    }
}

四、工具:第三方接口调用出入参日志打印

1、Function

1)ThirdInvokeLogAspect

package com.hlj.proj.service;


import com.hlj.proj.utils.JsonUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.springframework.boot.logging.LogLevel;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

/**
 * InvokeUtils
 *
 * @author healerjean
 * @date 2021/6/23  9:21 下午.
 */
public class InvokeUtils {


    /**
     * call
     *
     * @param method   method
     * @param req      req
     * @param function function
     * @param logger   logger
     * @return {@link Res}
     */
    public static <Req, Res> Res call(String method, Req req, Function<Req, Res> function, Logger logger) {
        return call(method, req, function, logger, null);
    }


    /**
     * call
     *
     * @param methodName methodName
     * @param req        req
     * @param function   function
     * @param logger     logger
     * @param level      level
     * @return {@link Res}
     */
    public static <Req, Res> Res call(String methodName, Req req, Function<Req, Res> function, Logger logger, LogLevel level) {
        Res res = null;
        String stackTrace = null;
        long startTime = System.currentTimeMillis();
        try {
            res = function.apply(req);
            level = level != null ? level : LogLevel.INFO;
        } catch (Exception throwable) {
            stackTrace = ExceptionUtils.getStackTrace(throwable);
            level = level != null ? level : LogLevel.ERROR;
        } finally {
            log(methodName, req, res, stackTrace, startTime, logger, level);
        }
        return res;
    }


    /**
     * log
     *
     * @param method        method
     * @param requestParam  requestParam
     * @param responseParam responseParam
     * @param stackTrace    stackTrace
     * @param startTime     startTime
     * @param log           log
     * @param level         level
     */
    private static void log(String method, Object requestParam, Object responseParam, String stackTrace, long startTime, Logger log, LogLevel level) {
        try {
            long timeCost = System.currentTimeMillis() - startTime;
            Map<String, Object> map = new HashMap<>();
            map.put("method", method);
            map.put("requestParams", requestParam);
            map.put("responseParams", responseParam);
            map.put("timeCost", timeCost + "ms");
            if (StringUtils.isNotBlank(stackTrace)) {
                map.put("stackTrace", stackTrace);
            }
            switch (level) {
                case DEBUG:
                    log.debug("InvokeUtils:{}", JsonUtils.toJsonString(map));
                    break;
                case INFO:
                    log.info("InvokeUtils:{}", JsonUtils.toJsonString(map));
                    break;
                case WARN:
                    log.warn("InvokeUtils:{}", JsonUtils.toJsonString(map));
                    break;
                case ERROR:
                    log.error("InvokeUtils:{}", JsonUtils.toJsonString(map));
                    break;
                default:
                    log.info("InvokeUtils:{}", JsonUtils.toJsonString(map));
                    break;
            }
        } catch (Exception e) {
            log.error("InvokeUtils {}", ExceptionUtils.getStackTrace(e));
        }
    }
}


2、验证

@Test
public void test() {
    LogCallTestService logCallTestService = new LogCallTestService();
    LogCallTestService.RequestDTO requestDTO = new LogCallTestService.RequestDTO();
    requestDTO.setName("healerjean");
    requestDTO.setAge(1L);
    LogCallTestService.ResponseDTO res = InvokeUtils.call("invokeMethod", requestDTO,
                                                          logCallTestService::invokeMethod, 
                                                          log);

    res = InvokeUtils.call("invokeMethod", requestDTO, logCallTestService::invokeMethod, log, 
                           LogLevel.DEBUG);
  
    res = InvokeUtils.call("invokeMethod", requestDTO, logCallTestService::invokeMethod, log, 
                           LogLevel.INFO);
  
  	res = InvokeUtils.call("invokeMethod", requestDTO, logCallTestService::invokeMethod, log, 
                           LogLevel.WARN);
  
    res = InvokeUtils.call("invokeMethod", requestDTO, logCallTestService::invokeMethod, log, 
                           LogLevel.ERROR);
}

五、日志唯一标识追踪

1、Controller 过滤器 Log4j2ReqUidFilter

package com.healerjean.proj.config.filter;


import org.slf4j.MDC;

import javax.servlet.*;
import java.io.IOException;
import java.util.UUID;

/**
 * @author HealerJean
 * @ClassName Log4j2Filter
 * @date 2020/6/15  20:12.
 * @Description
 */
public class Log4j2ReqUidFilter implements Filter {

    private static final String REQ_UID = "REQ_UID";
    private FilterConfig filterConfig;


    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        MDC.put(REQ_UID, UUID.randomUUID().toString().replace("-", ""));
        filterChain.doFilter(servletRequest, servletResponse);
        MDC.remove(REQ_UID);
    }

    @Override
    public void destroy() {
        this.filterConfig = null;
    }

}

@Bean
public FilterRegistrationBean<Log4j2ReqUidFilter> log4j2ReqUidFilter() {
    FilterRegistrationBean<Log4j2ReqUidFilter> fitler = new FilterRegistrationBean<>();
    fitler.setFilter(new Log4j2ReqUidFilter());
    fitler.addUrlPatterns("/*");
    fitler.setName("log4j2ReqUidFilter");
    fitler.setDispatcherTypes(DispatcherType.REQUEST);
    return fitler;
}

2、dubbo 日志追踪

1)服务提供者

a、aop 切面 pom依赖

<!-- aop 切面 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

b、ServiceLogAspect接口出入参日志打印

package com.healerjean.proj.config.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;


@Aspect
@Component
@Slf4j
@Order(1)
public class ServiceLogAspect {

    @Around("execution(* com.healerjean.proj.service.*Service.*(..))")
    public Object handleControllerLog(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Signature signature = proceedingJoinPoint.getSignature();
        String className = signature.getDeclaringTypeName();
        String methodName = signature.getName();
        Object[] args = proceedingJoinPoint.getArgs();
        long start = System.currentTimeMillis();
        try {
            log.info("请求开始:类名:【{}】,方法名:【{}】, 参数:【{}】", className, methodName, args);
            Object result = proceedingJoinPoint.proceed();
            long timeCost = System.currentTimeMillis() - start;
            log.info("请求结束:类名:【{}】, 方法名:【{}】, 参数:【{}】, 返回值:{}, 耗时:{}ms。", className, methodName, args, result, timeCost);
            return result;
        } catch (Exception e) {
            long timeCost = System.currentTimeMillis() - start;
            log.info("请求出错:类名:【{}】,方法名:【{}】, 参数:【{}】, 耗时:【{}】ms。", className, methodName, args, timeCost);
            throw e;
        }
    }
}

c、ProviderRpcTraceFilterdubbo日志追踪过滤器

package com.healerjean.proj.config.dubbo;

import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.rpc.*;
import com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;

import java.util.UUID;

@Activate(group = Constants.PROVIDER, order = 1)
public class ProviderRpcTraceFilter extends FutureFilter {


    private static final String DUBBO_REQ_UID = "REQ_UID";

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        String reqUid = RpcContext.getContext().getAttachment(DUBBO_REQ_UID);
        if (StringUtils.isBlank(reqUid)) {
            //传递丢失
            reqUid = "CUSTOM:" + UUID.randomUUID().toString().replace("-", "");
        }
        MDC.put(DUBBO_REQ_UID, reqUid);
        RpcContext.getContext().setAttachment(DUBBO_REQ_UID, reqUid);
        try {
            return invoker.invoke(invocation);
        } finally {
            MDC.remove(DUBBO_REQ_UID);
        }
    }
}

d、配置dubbo过滤器

![image-20200616112740541]https://raw.githubusercontent.com/HealerJean/HealerJean.github.io/master/blogImages/image-20200616112740541.png)

com.alibaba.dubbo.rpc.Filter

ProviderRpcTraceFilter=com.healerjean.proj.config.dubbo.ProviderRpcTraceFilter

2)服务消费者

a、aop 切面 pom 依赖

<!-- aop 切面 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

b、ControllerLogAspect controller出入参打印

package com.healerjean.proj.config.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;


@Aspect
@Component
@Slf4j
@Order(1)
public class ControllerLogAspect {

    @Around("execution(* com.healerjean.proj.controller.*Controller.*(..))")
    public Object handleControllerLog(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Signature signature = proceedingJoinPoint.getSignature();
        String className = signature.getDeclaringTypeName();
        String methodName = signature.getName();
        Object[] args = proceedingJoinPoint.getArgs();
        long start = System.currentTimeMillis();
        try {
            log.info("请求开始:类名:【{}】,方法名:【{}】, 参数:【{}】", className, methodName, args);
            Object result = proceedingJoinPoint.proceed();
            long timeCost = System.currentTimeMillis() - start;
            log.info("请求结束:类名:【{}】, 方法名:【{}】, 参数:【{}】, 返回值:{}, 耗时:{}ms。", className, methodName, args, result, timeCost);
            return result;
        } catch (Exception e) {
            long timeCost = System.currentTimeMillis() - start;
            log.info("请求出错:类名:【{}】,方法名:【{}】, 参数:【{}】, 耗时:【{}】ms。", className, methodName, args, timeCost);
            throw e;
        }
    }
}

c、日志追踪过滤器

package com.healerjean.proj.config.filter;


import org.slf4j.MDC;

import javax.servlet.*;
import java.io.IOException;
import java.util.UUID;

/**
 * @author HealerJean
 * @ClassName Log4j2Filter
 * @date 2020/6/15  20:12.
 * @Description
 */
public class Log4j2ReqUidFilter implements Filter {

    private static final String REQ_UID = "REQ_UID";
    private FilterConfig filterConfig;


    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        MDC.put(REQ_UID, UUID.randomUUID().toString().replace("-", ""));
        filterChain.doFilter(servletRequest, servletResponse);
        MDC.remove(REQ_UID);
    }

    @Override
    public void destroy() {
        this.filterConfig = null;
    }



}



@Configuration
public class InterceptorConfig extends WebMvcConfigurerAdapter {

    @Bean
    public FilterRegistrationBean log4j2ReqUidFilter() {
        FilterRegistrationBean fitler = new FilterRegistrationBean();
        fitler.setFilter(new Log4j2ReqUidFilter());
        fitler.addUrlPatterns("/*");
        fitler.setName("Log4j2ReqUidFilter");
        fitler.setDispatcherTypes(DispatcherType.REQUEST);
        return fitler;
    }
}

d、ConsumerRpcTraceFilterdubbo日志追踪过滤器

package com.healerjean.proj.config.dubbo;

import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.rpc.*;
import com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;

import java.util.UUID;

@Activate(group = Constants.CONSUMER, order = 1)
public class ConsumerRpcTraceFilter extends FutureFilter {


    private static final String DUBBO_REQ_UID = "REQ_UID";

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        RpcContext.getContext().setAttachment(DUBBO_REQ_UID, MDC.get(DUBBO_REQ_UID));
        return invoker.invoke(invocation);
    }
}

e、配置dubbo过滤器

![image-20200616113137513]https://raw.githubusercontent.com/HealerJean/HealerJean.github.io/master/blogImages/image-20200616113137513.png)

com.alibaba.dubbo.rpc.Filter

ConsumerRpcTraceFilter=com.healerjean.proj.config.dubbo.ConsumerRpcTraceFilter

ContactAuthor