From 18e2230439cbc36c0009228520ad0a3f2a61aed1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E6=95=8F?= Date: Wed, 8 Jan 2025 16:58:06 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(common):=20=E6=96=B0=E8=84=9A=E6=89=8B?= =?UTF-8?q?=E6=9E=B6=EF=BC=8C=E5=85=BC=E5=AE=B9=E4=B8=80=E4=B8=8B=20=20ctx?= =?UTF-8?q?LogId=20=E8=BF=99=E4=B8=AAtraceId?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/cn/axzo/foundation/util/TraceUtils.java | 13 +++++++++++-- .../web/support/interceptors/TraceInterceptor.java | 4 +++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/common-lib/src/main/java/cn/axzo/foundation/util/TraceUtils.java b/common-lib/src/main/java/cn/axzo/foundation/util/TraceUtils.java index f48c07c..352bc11 100644 --- a/common-lib/src/main/java/cn/axzo/foundation/util/TraceUtils.java +++ b/common-lib/src/main/java/cn/axzo/foundation/util/TraceUtils.java @@ -2,6 +2,7 @@ package cn.axzo.foundation.util; import com.google.common.base.Strings; import lombok.experimental.UtilityClass; +import org.apache.commons.lang3.StringUtils; import org.slf4j.MDC; @UtilityClass @@ -12,27 +13,35 @@ public class TraceUtils { */ public static final String TRACE_ID_IN_MDC = "TraceId"; + /** + * 老服务用的是 ctxLogId,这里兼容一下老服务 + */ + public static final String TRACE_ID_CTX_LOG_ID = "ctxLogId"; + public String getOrCreateTraceId() { - String res = MDC.get(TRACE_ID); + String res = getTraceId(); if (Strings.isNullOrEmpty(res)) { res = UUIDBuilder.generateShortUuid(); MDC.put(TRACE_ID, res); MDC.put(TRACE_ID_IN_MDC, res); + MDC.put(TRACE_ID_CTX_LOG_ID, res); } return res; } public String getTraceId() { - return MDC.get(TRACE_ID); + return StringUtils.firstNonBlank(MDC.get(TRACE_ID_CTX_LOG_ID), MDC.get(TRACE_ID)); } public void putTraceId(String traceId) { MDC.put(TRACE_ID, traceId); MDC.put(TRACE_ID_IN_MDC, traceId); + MDC.put(TRACE_ID_CTX_LOG_ID, traceId); } public void removeTraceId() { MDC.remove(TRACE_ID); MDC.remove(TRACE_ID_IN_MDC); + MDC.remove(TRACE_ID_CTX_LOG_ID); } } diff --git a/web-support-lib/src/main/java/cn/axzo/foundation/web/support/interceptors/TraceInterceptor.java b/web-support-lib/src/main/java/cn/axzo/foundation/web/support/interceptors/TraceInterceptor.java index ce9142e..59302b8 100644 --- a/web-support-lib/src/main/java/cn/axzo/foundation/web/support/interceptors/TraceInterceptor.java +++ b/web-support-lib/src/main/java/cn/axzo/foundation/web/support/interceptors/TraceInterceptor.java @@ -2,6 +2,7 @@ package cn.axzo.foundation.web.support.interceptors; import cn.axzo.foundation.util.TraceUtils; import com.google.common.base.Strings; +import org.apache.commons.lang3.StringUtils; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; @@ -14,7 +15,7 @@ public class TraceInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - String upstreamTraceId = request.getHeader(TraceUtils.TRACE_ID); + String upstreamTraceId = StringUtils.firstNonBlank(request.getHeader(TraceUtils.TRACE_ID_CTX_LOG_ID), request.getHeader(TraceUtils.TRACE_ID)); if (!Strings.isNullOrEmpty(upstreamTraceId)) { // 优先使用上游的 traceId TraceUtils.putTraceId(upstreamTraceId); @@ -22,6 +23,7 @@ public class TraceInterceptor implements HandlerInterceptor { String traceId = TraceUtils.getOrCreateTraceId(); request.setAttribute(TraceUtils.TRACE_ID_IN_MDC, traceId); + request.setAttribute(TraceUtils.TRACE_ID_CTX_LOG_ID, traceId); return true; } From 0affbf379f3245e4b7e7a13796cd24463d76444f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E6=95=8F?= Date: Wed, 8 Jan 2025 18:01:52 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat(common):=20=E6=96=B0=E5=A2=9ERequestLo?= =?UTF-8?q?gV2Aspect=EF=BC=8C=E6=94=AF=E6=8C=81@RequestMapping=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E5=9C=A8api=E9=87=8C=E9=9D=A2=EF=BC=8Ccontroller?= =?UTF-8?q?=E7=BB=A7=E6=89=BFfeignApi=E7=9A=84=E6=83=85=E5=86=B5=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/support/log/RequestLogAspect.java | 6 +- .../web/support/log/RequestLogV2Aspect.java | 144 ++++++++++++++++++ 2 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 web-support-lib/src/main/java/cn/axzo/foundation/web/support/log/RequestLogV2Aspect.java diff --git a/web-support-lib/src/main/java/cn/axzo/foundation/web/support/log/RequestLogAspect.java b/web-support-lib/src/main/java/cn/axzo/foundation/web/support/log/RequestLogAspect.java index f53943e..666ea64 100644 --- a/web-support-lib/src/main/java/cn/axzo/foundation/web/support/log/RequestLogAspect.java +++ b/web-support-lib/src/main/java/cn/axzo/foundation/web/support/log/RequestLogAspect.java @@ -27,10 +27,14 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** - * 统一打印日志(支持忽略此aop, 采用@IgnoreRequestLog注解) + * 统一打印日志(支持忽略此aop, 采用@IgnoreRequestLog注解)
+ * @Deprecated 改类不会处理 @RequestMapping没有在controller上定义的情况。
+ * 建议使用{@link RequestLogV2Aspect} + * @see RequestLogV2Aspect **/ @Aspect @Slf4j +@Deprecated public class RequestLogAspect { private final static Integer DEFAULT_LOG_SIZE = 2048; private Integer logSize; diff --git a/web-support-lib/src/main/java/cn/axzo/foundation/web/support/log/RequestLogV2Aspect.java b/web-support-lib/src/main/java/cn/axzo/foundation/web/support/log/RequestLogV2Aspect.java new file mode 100644 index 0000000..d90fda4 --- /dev/null +++ b/web-support-lib/src/main/java/cn/axzo/foundation/web/support/log/RequestLogV2Aspect.java @@ -0,0 +1,144 @@ +package cn.axzo.foundation.web.support.log; + +import cn.axzo.foundation.exception.BusinessException; +import cn.axzo.foundation.result.ApiResult; +import com.alibaba.fastjson.JSONObject; +import com.google.common.base.Stopwatch; +import com.google.common.collect.ImmutableSet; +import lombok.Builder; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * 统一打印日志(支持忽略此aop, 采用@IgnoreRequestLog注解) + **/ +@Aspect +@Slf4j +public class RequestLogV2Aspect { + private final static Integer DEFAULT_LOG_SIZE = 2048; + private Integer logSize; + + @Builder + public RequestLogV2Aspect(Integer logSize) { + this.logSize = Optional.ofNullable(logSize).orElse(DEFAULT_LOG_SIZE); + } + + private static Set> EXCLUDE_CLASSES = ImmutableSet.of(ServletRequest.class, ServletResponse.class, + MultipartFile.class, BindingResult.class); + + @Around("@within(restController)||@annotation(restController)") + public Object doAround(ProceedingJoinPoint joinPoint, RestController restController) throws Throwable { + //避免拦截非http请求. 如feign + if (RequestContextHolder.getRequestAttributes() == null) { + return joinPoint.proceed(); + } + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + if (request != null) { + //用于在AbstractExceptionHandler中为dumpRequest, AbstractExceptionHandler无法直接获取body + request.setAttribute("params", this.getUserArgs(joinPoint.getArgs())); + } + + IgnoreRequestLog annotation = AnnotationUtils.findAnnotation(((MethodSignature) joinPoint.getSignature()).getMethod(), + IgnoreRequestLog.class); + + // 获取注解中的ignoreTypes + ImmutableSet ignoreTypes = Optional.ofNullable(annotation) + .map(a -> ImmutableSet.copyOf(a.types())).orElse(ImmutableSet.of()); + + Stopwatch stopwatch = Stopwatch.createStarted(); + String requestLog = buildRequestLog(joinPoint, ignoreTypes); + + String userSession = StringUtils.EMPTY; + String employeeSession = StringUtils.EMPTY; + + String appClient = StringUtils.EMPTY; + + + Object proceed; + try { + proceed = joinPoint.proceed(joinPoint.getArgs()); + } catch (BusinessException e) { + //stein会收集error级别的异常并告警. 对BizException. 不期望收到告警邮件. 因此将BizException对应的日志级别调整为warn + log.warn("api log, process error, caught BizException, url = {}, params = {}, userSession = {}, employeeSession = {}, appClient = {}, time cost = {} ms, BizException:", + request.getRequestURI(), requestLog, userSession, employeeSession, appClient, stopwatch.elapsed(TimeUnit.MILLISECONDS), e); + throw e; + } catch (Exception e) { + log.error("api log, process error, uncaught exception, url = {}, params = {}, userSession = {}, employeeSession = {}, appClient = {}, time cost = {} ms, exception:", + request.getRequestURI(), requestLog, userSession, employeeSession, appClient, stopwatch.elapsed(TimeUnit.MILLISECONDS), e); + throw e; + } catch (Error error) { + log.error("api log, process error, uncaught error, url = {}, params = {}, userSession = {}, employeeSession = {}, appClient = {}, time cost = {} ms, error:", + request.getRequestURI(), requestLog, userSession, employeeSession, appClient, stopwatch.elapsed(TimeUnit.MILLISECONDS), error); + throw error; + } + + // 如果该接口定义的类型不是SILENT的, 则打印日志 + if (!ignoreTypes.contains(IgnoreRequestLog.IgnoreType.ALL)) { + String responseLog; + try { + responseLog = buildResponseLog(proceed, ignoreTypes); + } catch (Exception ex) { + log.error("api log, process error, resp serialize error, url = {}, params = {}, userSession = {}, employeeSession = {}, appClient = {}, time cost = {} ms, error:", + request.getRequestURI(), requestLog, userSession, employeeSession, appClient, stopwatch.elapsed(TimeUnit.MILLISECONDS), ex); + throw ex; + } + log.info("api log, url = {}, params = {}, userSession = {}, employeeSession = {}, appClient = {}, result = {}, time cost = {} ms", + request.getRequestURI(), + StringUtils.left(requestLog, logSize), + userSession, + employeeSession, + appClient, + responseLog, + stopwatch.elapsed(TimeUnit.MILLISECONDS)); + } + + return proceed; + } + + private String buildRequestLog(ProceedingJoinPoint joinPoint, Set ignoreTypes) { + if (ignoreTypes.contains(IgnoreRequestLog.IgnoreType.REQUEST)) { + return "IGNORED"; + } + return JSONObject.toJSONString(this.getUserArgs(joinPoint.getArgs())); + } + + private String buildResponseLog(Object proceed, Set ignoreTypes) { + if (proceed == null || !(ApiResult.class.isAssignableFrom(proceed.getClass()))) { + return StringUtils.EMPTY; + } + if (ignoreTypes.contains(IgnoreRequestLog.IgnoreType.RESPONSE)) { + return "IGNORED"; + } + return StringUtils.left(JSONObject.toJSONString(proceed), logSize); + } + + protected List getUserArgs(Object[] args) { + return Arrays.stream(args) + .filter((p) -> + Objects.nonNull(p) && EXCLUDE_CLASSES.stream().noneMatch(clz -> clz.isAssignableFrom(p.getClass())) + + ).collect(Collectors.toList()); + } +} +