xiejun
2024-11-01 80b6cbfc9c861469146318d0b3dd5f8b8b525b8a
Source/BladeX-Tool/blade-starter-log/src/main/java/org/springblade/core/log/aspect/RequestLogAspect.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,260 @@
/*
 *      Copyright (c) 2018-2028, DreamLu All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: DreamLu å¢æ˜¥æ¢¦ (596392912@qq.com)
 */
package org.springblade.core.log.aspect;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.springblade.core.launch.log.BladeLogLevel;
import org.springblade.core.log.props.BladeRequestLogProperties;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springblade.core.tool.utils.ClassUtil;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.core.tool.utils.WebUtil;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.core.MethodParameter;
import org.springframework.core.io.InputStreamSource;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
 * Spring boot æŽ§åˆ¶å™¨ è¯·æ±‚日志,方便代码调试
 *
 * @author L.cm
 */
@Slf4j
@Aspect
@AutoConfiguration
@AllArgsConstructor
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnProperty(value = BladeLogLevel.REQ_LOG_PROPS_PREFIX + ".enabled", havingValue = "true", matchIfMissing = true)
public class RequestLogAspect {
   private final BladeRequestLogProperties properties;
   /**
    * AOP çŽ¯åˆ‡ æŽ§åˆ¶å™¨ R è¿”回值
    *
    * @param point JoinPoint
    * @return Object
    * @throws Throwable å¼‚常
    */
   @Around(
      "execution(!static org.springblade.core.tool.api.R *(..)) && " +
         "(@within(org.springframework.stereotype.Controller) || " +
         "@within(org.springframework.web.bind.annotation.RestController))"
   )
   public Object aroundApi(ProceedingJoinPoint point) throws Throwable {
      BladeLogLevel level = properties.getLevel();
      // ä¸æ‰“印日志,直接返回
      if (BladeLogLevel.NONE == level) {
         return point.proceed();
      }
      HttpServletRequest request = WebUtil.getRequest();
      String requestUrl = Objects.requireNonNull(request).getRequestURI();
      String requestMethod = request.getMethod();
      // æž„建成一条长 æ—¥å¿—,避免并发下日志错乱
      StringBuilder beforeReqLog = new StringBuilder(300);
      // æ—¥å¿—参数
      List<Object> beforeReqArgs = new ArrayList<>();
      beforeReqLog.append("\n\n================  Request Start  ================\n");
      // æ‰“印路由
      beforeReqLog.append("===> {}: {}");
      beforeReqArgs.add(requestMethod);
      beforeReqArgs.add(requestUrl);
      // æ‰“印请求参数
      logIngArgs(point, beforeReqLog, beforeReqArgs);
      // æ‰“印请求 headers
      logIngHeaders(request, level, beforeReqLog, beforeReqArgs);
      beforeReqLog.append("================   Request End   ================\n");
      // æ‰“印执行时间
      long startNs = System.nanoTime();
      log.info(beforeReqLog.toString(), beforeReqArgs.toArray());
      // aop æ‰§è¡ŒåŽçš„æ—¥å¿—
      StringBuilder afterReqLog = new StringBuilder(200);
      // æ—¥å¿—参数
      List<Object> afterReqArgs = new ArrayList<>();
      afterReqLog.append("\n\n===============  Response Start  ================\n");
      try {
         Object result = point.proceed();
         // æ‰“印返回结构体
         if (BladeLogLevel.BODY.lte(level)) {
            afterReqLog.append("===Result===  {}\n");
            afterReqArgs.add(JsonUtil.toJson(result));
         }
         return result;
      } finally {
         long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
         afterReqLog.append("<=== {}: {} ({} ms)\n");
         afterReqArgs.add(requestMethod);
         afterReqArgs.add(requestUrl);
         afterReqArgs.add(tookMs);
         afterReqLog.append("===============   Response End   ================\n");
         log.info(afterReqLog.toString(), afterReqArgs.toArray());
      }
   }
   /**
    * æ¿€åŠ±è¯·æ±‚å‚æ•°
    *
    * @param point         ProceedingJoinPoint
    * @param beforeReqLog  StringBuilder
    * @param beforeReqArgs beforeReqArgs
    */
   public void logIngArgs(ProceedingJoinPoint point, StringBuilder beforeReqLog, List<Object> beforeReqArgs) {
      MethodSignature ms = (MethodSignature) point.getSignature();
      Method method = ms.getMethod();
      Object[] args = point.getArgs();
      // è¯·æ±‚参数处理
      final Map<String, Object> paraMap = new HashMap<>(16);
      // ä¸€æ¬¡è¯·æ±‚只能有一个 request body
      Object requestBodyValue = null;
      for (int i = 0; i < args.length; i++) {
         // è¯»å–方法参数
         MethodParameter methodParam = ClassUtil.getMethodParameter(method, i);
         // PathVariable å‚数跳过
         PathVariable pathVariable = methodParam.getParameterAnnotation(PathVariable.class);
         if (pathVariable != null) {
            continue;
         }
         RequestBody requestBody = methodParam.getParameterAnnotation(RequestBody.class);
         String parameterName = methodParam.getParameterName();
         Object value = args[i];
         // å¦‚果是body的json则是对象
         if (requestBody != null) {
            requestBodyValue = value;
            continue;
         }
         // å¤„理 å‚æ•°
         if (value instanceof HttpServletRequest) {
            paraMap.putAll(((HttpServletRequest) value).getParameterMap());
            continue;
         } else if (value instanceof WebRequest) {
            paraMap.putAll(((WebRequest) value).getParameterMap());
            continue;
         } else if (value instanceof HttpServletResponse) {
            continue;
         } else if (value instanceof MultipartFile) {
            MultipartFile multipartFile = (MultipartFile) value;
            String name = multipartFile.getName();
            String fileName = multipartFile.getOriginalFilename();
            paraMap.put(name, fileName);
            continue;
         } else if (value instanceof MultipartFile[]) {
            MultipartFile[] arr = (MultipartFile[]) value;
            if (arr.length == 0) {
               continue;
            }
            String name = arr[0].getName();
            StringBuilder sb = new StringBuilder(arr.length);
            for (MultipartFile multipartFile : arr) {
               sb.append(multipartFile.getOriginalFilename());
               sb.append(StringPool.COMMA);
            }
            paraMap.put(name, StringUtil.removeSuffix(sb.toString(), StringPool.COMMA));
            continue;
         } else if (value instanceof List) {
            List<?> list = (List<?>) value;
            AtomicBoolean isSkip = new AtomicBoolean(false);
            for (Object o : list) {
               if ("StandardMultipartFile".equalsIgnoreCase(o.getClass().getSimpleName())) {
                  isSkip.set(true);
                  break;
               }
            }
            if (isSkip.get()) {
               paraMap.put(parameterName, "此参数不能序列化为json");
               continue;
            }
         }
         // å‚数名
         RequestParam requestParam = methodParam.getParameterAnnotation(RequestParam.class);
         String paraName = parameterName;
         if (requestParam != null && StringUtil.isNotBlank(requestParam.value())) {
            paraName = requestParam.value();
         }
         if (value == null) {
            paraMap.put(paraName, null);
         } else if (ClassUtil.isPrimitiveOrWrapper(value.getClass())) {
            paraMap.put(paraName, value);
         } else if (value instanceof InputStream) {
            paraMap.put(paraName, "InputStream");
         } else if (value instanceof InputStreamSource) {
            paraMap.put(paraName, "InputStreamSource");
         } else if (JsonUtil.canSerialize(value)) {
            // åˆ¤æ–­æ¨¡åž‹èƒ½è¢« json åºåˆ—化,则添加
            paraMap.put(paraName, value);
         } else {
            paraMap.put(paraName, "此参数不能序列化为json");
         }
      }
      // è¯·æ±‚参数
      if (paraMap.isEmpty()) {
         beforeReqLog.append("\n");
      } else {
         beforeReqLog.append(" Parameters: {}\n");
         beforeReqArgs.add(JsonUtil.toJson(paraMap));
      }
      if (requestBodyValue != null) {
         beforeReqLog.append("====Body=====  {}\n");
         beforeReqArgs.add(JsonUtil.toJson(requestBodyValue));
      }
   }
   /**
    * è®°å½•请求头
    *
    * @param request       HttpServletRequest
    * @param level         æ—¥å¿—级别
    * @param beforeReqLog  StringBuilder
    * @param beforeReqArgs beforeReqArgs
    */
   public void logIngHeaders(HttpServletRequest request, BladeLogLevel level,
                       StringBuilder beforeReqLog, List<Object> beforeReqArgs) {
      // æ‰“印请求头
      if (BladeLogLevel.HEADERS.lte(level)) {
         Enumeration<String> headers = request.getHeaderNames();
         while (headers.hasMoreElements()) {
            String headerName = headers.nextElement();
            String headerValue = request.getHeader(headerName);
            beforeReqLog.append("===Headers===  {}: {}\n");
            beforeReqArgs.add(headerName);
            beforeReqArgs.add(headerValue);
         }
      }
   }
}