前言
使用AOP处理请求日志,记录请求ip、url、请求参数、请求结果等信息,并异步保存日志信息方便对线上问题进行排查分析。
思路
-
对
@RequestMapping、@GetMapping等注解切入,@Around环绕通知处理日志信息 -
使用
Spring事件发布订阅与@Async保存日志信息
实现
切面
-
日志信息定义
@Data public class RequestLog implements Serializable { // id private String id; // 请求url private String url; // 请求方法 private String method; // 客户端ip private String ip; // 用户代理 private String userAgent; // 请求调用类(哪个controller) private String module; // 请求调用类方法(@RequestMapping等注解的方法名) private String operation; // 请求参数 private String args; // 请求结果 private Object result; // 是否成功(发生Exception为false) private boolean success; // 执行时长(单位:ms) private long duration; // 创建人 private String createdBy; // 创建时间 private LocalDateTime createdAt; } -
切面定义
@Slf4j @Aspect @Component public class RequestLogAspect { @Autowired private ApplicationEventPublisher publisher; @Autowired private ObjectMapper objectMapper; // 异常信息最大长度 private static final int MAX_EXCEPTION_LEN = 2048; @Pointcut( "@annotation(org.springframework.web.bind.annotation.RequestMapping) ||" + "@annotation(org.springframework.web.bind.annotation.GetMapping) || " + "@annotation(org.springframework.web.bind.annotation.PostMapping) ||" + "@annotation(org.springframework.web.bind.annotation.PutMapping) || " + "@annotation(org.springframework.web.bind.annotation.DeleteMapping) ") public void pointCut() { } @Around("pointCut()") public Object logRequest(ProceedingJoinPoint joinPoint) throws Throwable { StopWatch stopWatch = new StopWatch(); stopWatch.start(UUID.randomUUID().toString()); MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); Method method = methodSignature.getMethod(); RequestLog requestLog = initLog(); requestLog.setModule(method.getDeclaringClass().getName()); requestLog.setOperation(method.getName()); requestLog.setArgs(getArgs(joinPoint)); Object result = null; try { result = joinPoint.proceed(); return result; } catch (Exception exception) { requestLog.setSuccess(false); String exceptionStr = exception.toString(); result = exceptionStr.length() > MAX_EXCEPTION_LEN ? exceptionStr.substring(0, MAX_EXCEPTION_LEN) : exceptionStr; throw exception; } finally { stopWatch.stop(); requestLog.setDuration(stopWatch.getTotalTimeMillis()); requestLog.setResult(result); publisher.publishEvent(new RequestLogEvent(requestLog)); } } private String getArgs(ProceedingJoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); String[] names = ((MethodSignature) joinPoint.getSignature()).getParameterNames(); Map<String, Object> argMap = new HashMap<>(); if (Objects.nonNull(args) && args.length > 0) { for (int i = 0, len = args.length; i < len; i++) { Object arg = args[i]; // 请求响应等参数不处理 if (arg instanceof ModelAndView || arg instanceof HttpServletRequest || arg instanceof HttpServletResponse || (arg instanceof MultipartFile) || (arg instanceof MultipartFile[])) { continue; } argMap.put(names[i], arg); } } String argStr = ""; try { if (argMap.size() > 0) { argStr = objectMapper.writeValueAsString(argMap); } } catch (Exception ex) { } return argStr; } private RequestLog initLog() { RequestLog requestLog = new RequestLog(); requestLog.setId(UUID.randomUUID().toString()); requestLog.setCreatedAt(LocalDateTime.now()); requestLog.setSuccess(true); HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); requestLog.setUserAgent(request.getHeader("user-agent")); requestLog.setIp(getIp(request)); requestLog.setUrl(request.getRequestURI()); requestLog.setMethod(request.getMethod()); // log.setCreatedBy(); TODO: 从上下文获取用户id return requestLog; } private String getIp(HttpServletRequest request) { String ipAddress = null; try { ipAddress = request.getHeader("x-forwarded-for"); if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("WL-Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getRemoteAddr(); if (Objects.equals("127.0.0.1", ipAddress)) { // 根据网卡取本机配置的IP InetAddress inet = null; try { inet = InetAddress.getLocalHost(); } catch (UnknownHostException e) { e.printStackTrace(); } ipAddress = inet.getHostAddress(); } } // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 if (ipAddress != null && ipAddress.length() > 15) { // = 15 if (ipAddress.indexOf(",") > 0) { ipAddress = ipAddress.substring(0, ipAddress.indexOf(",")); } } } catch (Exception e) { ipAddress = ""; } return ipAddress; } }
事件订阅
-
事件定义
@AllArgsConstructor @Data public class RequestLogEvent implements Serializable { private RequestLog requestLog; } -
事件订阅
@Slf4j @Component public class RequestLogListener { // 使用 @Async 需先启用 @EnableAsync 注解 @Async @EventListener(RequestLogEvent.class) public void logRequest(RequestLogEvent event) { RequestLog requestLog = event.getRequestLog(); // TODO: 可发送到消息队列、保存到数据库等 log.info("requestLog {}", requestLog); } }
结尾
如果文章对你有帮助,请点 赞👍🏻 支持一下。若有错误之处或有更好的建议,欢迎指正,谢谢。