feat(common): 新增RequestLogV2Aspect,支持@RequestMapping定义在api里面,controller继承feignApi的情况。

This commit is contained in:
周敏 2025-01-08 18:01:52 +08:00
parent 18e2230439
commit 0affbf379f
2 changed files with 149 additions and 1 deletions

View File

@ -27,10 +27,14 @@ import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 统一打印日志(支持忽略此aop, 采用@IgnoreRequestLog注解)
* 统一打印日志(支持忽略此aop, 采用@IgnoreRequestLog注解)<br>
* @Deprecated 改类不会处理 @RequestMapping没有在controller上定义的情况<br>
* 建议使用{@link RequestLogV2Aspect}
* @see RequestLogV2Aspect
**/
@Aspect
@Slf4j
@Deprecated
public class RequestLogAspect {
private final static Integer DEFAULT_LOG_SIZE = 2048;
private Integer logSize;

View File

@ -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<Class<?>> 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<IgnoreRequestLog.IgnoreType> 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<IgnoreRequestLog.IgnoreType> ignoreTypes) {
if (ignoreTypes.contains(IgnoreRequestLog.IgnoreType.REQUEST)) {
return "IGNORED";
}
return JSONObject.toJSONString(this.getUserArgs(joinPoint.getArgs()));
}
private String buildResponseLog(Object proceed, Set<IgnoreRequestLog.IgnoreType> 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<Object> getUserArgs(Object[] args) {
return Arrays.stream(args)
.filter((p) ->
Objects.nonNull(p) && EXCLUDE_CLASSES.stream().noneMatch(clz -> clz.isAssignableFrom(p.getClass()))
).collect(Collectors.toList());
}
}