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()); + } +} +