当前位置: 首页 > article >正文

【开源宝藏】blade-tool AOP请求日志打印

一、先看看日志输出效果

可以看到,每个对于每个请求,开始与结束一目了然,并且打印了以下参数:

URL: 请求接口地址;

HTTP Method: 请求的方法,是 POST, GET, 还是 DELETE 等;

Class Method: 对应 Controller 的全路径以及调用的哪个方法;

IP: 请求 IP 地址;

Request Args: 请求入参,以 JSON 格式输出;

Response Args: 响应出参,以 JSON 格式输出;

Time-Consuming: 请求耗时;

效果应该还不错吧!接下来就让我们一步一步去实现该功能, 首先,新建一个 Spring Boot Web 项目。

二、添加 Maven 依赖
在项目 pom.xml 文件中添加依赖:

spring-boot-starter-aop

com.google.code.gson

spring-boot-starter-aop

三、配置 AOP 切面
在配置 AOP 切面之前,我们需要了解下 aspectj 相关注解的作用:

@Aspect:声明该类为一个注解类;

@Pointcut:定义一个切点,后面跟随一个表达式,表达式可以定义为某个 package 下的方法,也可以是自定义注解等;

切点定义好后,就是围绕这个切点做文章了:

@Before: 在切点之前,织入相关代码;

@After: 在切点之后,织入相关代码;

@AfterReturning: 在切点返回内容后,织入相关代码,一般用于对返回值做些加工处理的场景;

@AfterThrowing: 用来处理当织入的代码抛出异常后的逻辑处理;

@Around: 在切入点前后织入代码,并且可以自由的控制何时执行切点;

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.constant.AppConstant;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springblade.core.tool.utils.BeanUtil;
import org.springblade.core.tool.utils.ClassUtil;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.core.tool.utils.WebUtil;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Profile;
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 jakarta.servlet.http.HttpServletRequest;
import jakarta.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
@Profile({AppConstant.DEV_CODE, AppConstant.TEST_CODE})
public class RequestLogAspect {

	/**
	 * 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 {
		MethodSignature ms = (MethodSignature) point.getSignature();
		Method method = ms.getMethod();
		Object[] args = point.getArgs();
		// 请求参数处理
		final Map<String, Object> paraMap = new HashMap<>(16);
		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 && value != null) {
				paraMap.putAll(BeanUtil.toMap(value));
				continue;
			}
			// 处理 List
			if (value instanceof List) {
				value = ((List) value).get(0);
			}
			// 处理 参数
			if (value instanceof HttpServletRequest) {
				paraMap.putAll(((HttpServletRequest) value).getParameterMap());
			} else if (value instanceof WebRequest) {
				paraMap.putAll(((WebRequest) value).getParameterMap());
			} else if (value instanceof MultipartFile) {
				MultipartFile multipartFile = (MultipartFile) value;
				String name = multipartFile.getName();
				String fileName = multipartFile.getOriginalFilename();
				paraMap.put(name, fileName);
			} else if (value instanceof HttpServletResponse) {
			} else if (value instanceof InputStream) {
			} else if (value instanceof InputStreamSource) {
			} 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;
				}
			} else {
				// 参数名
				RequestParam requestParam = methodParam.getParameterAnnotation(RequestParam.class);
				String paraName;
				if (requestParam != null && StringUtil.isNotBlank(requestParam.value())) {
					paraName = requestParam.value();
				} else {
					paraName = methodParam.getParameterName();
				}
				paraMap.put(paraName, value);
			}
		}
		HttpServletRequest request = WebUtil.getRequest();
		String requestURI = 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(requestURI);
		// 请求参数
		if (paraMap.isEmpty()) {
			beforeReqLog.append("\n");
		} else {
			beforeReqLog.append(" Parameters: {}\n");
			beforeReqArgs.add(JsonUtil.toJson(paraMap));
		}
		// 打印请求头
		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);
		}
		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();
			// 打印返回结构体
			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(requestURI);
			afterReqArgs.add(tookMs);
			afterReqLog.append("=================  Response End   ================\n");
			log.info(afterReqLog.toString(), afterReqArgs.toArray());
		}
	}

}


http://www.kler.cn/a/504949.html

相关文章:

  • MC1.12.2 macOS高清修复OptiFine运行崩溃
  • mybatisPlus(条件构造器API)
  • Redis是单线程还是多线程?
  • 蓝牙BT04-A的使用与相关AT指令
  • Ubuntu中双击自动运行shell脚本
  • 【华为OD-E卷 - 求字符串中所有整数的最小和 100分(python、java、c++、js、c)】
  • 电脑玩游戏出现彩色斑点怎么回事,如何解决
  • 业务幂等性技术架构体系之消息幂等深入剖析
  • flutter 安卓端打包
  • Java 如何只测试某个类或方法:Maven与IntelliJ IDEA的不同方法及注意事项
  • iOS - TLS(线程本地存储)
  • 40,【5】CTFHUB WEB SQL 时间盲注
  • 跨境電商防關聯指紋流覽器Linken Sphere使用教程
  • vscode配置opencv4.8环境
  • Open FPV VTX开源之嵌入式OSD配置
  • extends配置项详解
  • 深度学习中的模块复用原则(定义一次还是多次)
  • C语言数据结构编程练习-用指针创建顺序表,进行创销和增删改查操作
  • 屏幕轻触间:触摸交互从 “感知” 到 “智算” 的隐秘路径
  • 爬虫案例:python爬取京东商品数据||京东商品详情SKU价格
  • OpenSeaOtter使用手册-项目简介
  • # MyBatis 基础了解
  • camera 配置预览和拍照streams上报的可用尺寸列表
  • DevOps实用场景:在哪些业务中应用DevOps最有效
  • selenium操作指南,2万字总结
  • 【力扣Hot100】双指针