preface

AOP is used to process the request log, record the request IP address, URL, request parameters, request results and other information, and asynchronously save the log information to facilitate the troubleshooting and analysis of online problems.

Train of thought

  • For @requestMapping, @getMapping and other annotations, @around notifies processing log information

  • Use Spring event publishing subscriptions to save log information with @async

implementation

section

  • Log Definition

    @Data
    public class RequestLog implements Serializable {
        // id
        private String id;
        / / request url
        private String url;
        // Request method
        private String method;
        // Client IP address
        private String ip;
        // User agent
        private String userAgent;
        // Request call class (which controller)
        private String module;
        // Request to call class methods (@requestMapping, etc.)
        private String operation;
        // Request parameters
        private String args;
        // Request the result
        private Object result;
        // Successful (Exception is false)
        private boolean success;
        // Execution time (ms)
        private long duration;
        / / founder
        private String createdBy;
        // Create time
        private LocalDateTime createdAt;
    }
    Copy the code
  • Section definition

    @Slf4j
    @Aspect
    @Component
    public class RequestLogAspect {
        @Autowired
        private ApplicationEventPublisher publisher;
    
        @Autowired
        private ObjectMapper objectMapper;
        
        // Maximum length of exception information
        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(a) {}@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(newRequestLogEvent(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];
                    // Request response parameters are not processed
                    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(a) {
            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:Get the user ID from the context
    
            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)) {
                        // Obtain the local IP address based on the network adapter
                        InetAddress inet = null;
                        try {
                            inet = InetAddress.getLocalHost();
                        } catch(UnknownHostException e) { e.printStackTrace(); } ipAddress = inet.getHostAddress(); }}// In the case of multiple proxies, the first IP address is the real IP address of the client, and multiple IP addresses are separated by ','
                if(ipAddress ! =null && ipAddress.length() > 15) {
                    / / = 15
                    if (ipAddress.indexOf(",") > 0) {
                        ipAddress = ipAddress.substring(0, ipAddress.indexOf(",")); }}}catch (Exception e) {
                ipAddress = "";
            }
            returnipAddress; }}Copy the code

Event subscription

  • An event definition

    @AllArgsConstructor
    @Data
    public class RequestLogEvent implements Serializable {
    
        private RequestLog requestLog;
    }
    Copy the code
  • Event subscription

    @Slf4j
    @Component
    public class RequestLogListener {
    
        // To use @async, enable the @enableAsync annotation
        @Async
        @EventListener(RequestLogEvent.class)
        public void logRequest(RequestLogEvent event) {
            RequestLog requestLog = event.getRequestLog();
            // TODO:It can be sent to message queues, saved to databases, and so on
            log.info("requestLog {}", requestLog); }}Copy the code

At the end

If this article is helpful to you, please like 👍🏻 to support it. If there is any mistake or better suggestion, welcome to correct, thank you.