Log打印和追踪
前言
Github:https://github.com/HealerJean
一、日志格式
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


