Spring Boot 接口请求日志(基于AOP和自定义注解)

一、需求

在Spring Boot应用中,实现接口请求日志记录功能,要求能够记录包括请求方法、接口路径及请求参数等核心信息,并提供灵活的开关配置。

二、方案概述

采用AOP(面向切面编程)结合自定义注解的方式实现。

具体步骤如下:

  1. 创建自定义注解@ApiLog,标记需要记录日志的接口。
  2. 通过AOP实现一个切面,对被@ApiLog注解修饰的方法进行前置处理,记录其请求相关信息。
  3. 提供配置项开关,控制是否开启接口日志记录。
  4. 推荐使用消息队列(例如RocketMQ)异步处理接口日志,以提升性能,但本示例仅展示简单的日志打印。使用消息队列的方法是:将接口的请求日志发送到消息队列里,由专门的日志记录服务器去处理,比如写入专门的数据库。这样可以减少接口的同步处理的时间,避免客户端等待时间过长,提升总体性能。

三、核心代码

自定义注解:@ApiLog

package com.example.core.log.annotation;import java.lang.annotation.*;/*** 接口日志注解*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiLog {
}

切面类:ApiLogAspect

package com.example.core.log.aspect;import com.example.core.property.BaseFrameworkConfigProperties;
import com.example.core.util.JsonUtil;
import io.swagger.v3.oas.annotations.Operation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;@Slf4j
@Aspect
@Order(20)
@Component
public class ApiLogAspect {@Value("${spring.application.name:}")private String applicationName;private final BaseFrameworkConfigProperties properties;public ApiLogAspect(BaseFrameworkConfigProperties properties) {this.properties = properties;}// 定义一个切点:所有被 ApiLog 注解修饰的方法会织入advice@Pointcut("@annotation(com.example.core.log.annotation.ApiLog)")private void pointcut() {}// Before表示 advice() 将在目标方法执行前执行@Before("pointcut()")public void advice(JoinPoint joinPoint) {if (!properties.getApiLog().isEnabled()) {return;}log.info("\n-------------------- 接口日志,开始 --------------------");log.info("applicationName:{}", applicationName);// 获取请求信息ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();if (attributes != null) {HttpServletRequest request = attributes.getRequest();// 用户IPString clientIp = request.getRemoteAddr();log.info("clientIp:{}", clientIp);// URLString requestURL = request.getRequestURL().toString();log.info("url:{}", requestURL);// 请求方法String requestMethod = request.getMethod();log.info("requestMethod:{}", requestMethod);// 接口路径String path = request.getServletPath();log.info("path:{}", path);}// 控制器方法参数列表Object[] args = joinPoint.getArgs();// 获取有效的控制器方法参数列表List<Object> validArgs = getValidArguments(args);log.info("args:{}", JsonUtil.toJson(validArgs));// 方法签名MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();log.info("methodSignature:{}", methodSignature);// 方法参数名称列表String[] parameterNames = methodSignature.getParameterNames();log.info("parameterNames:{}", JsonUtil.toJson(parameterNames));// 获取接口的注解Operation operation = methodSignature.getMethod().getAnnotation(Operation.class);if (operation != null) {// 接口概述String summary = operation.summary();log.info("summary:{}", summary);// 接口描述String description = operation.description();log.info("description:{}", description);}log.info("\n-------------------- 接口日志,结束 --------------------\n");}/*** 获取有效的控制器方法参数列表* <p>* 排除 HttpServletRequest 和 HttpServletResponse 参数。* <p>* HttpServletRequest 参数,会阻塞线程,抛出异常 NestedServletException-OutOfMemoryError。* <p>* HttpServletResponse 参数,会抛出异常 NestedServletException-StackOverflowError。*/private List<Object> getValidArguments(Object[] args) {return Stream.of(args).filter(this::isValidArgument).collect(Collectors.toList());}private Boolean isValidArgument(Object arg) {return isNotHttpServletRequest(arg) && isNotHttpServletResponse(arg);}/*** 不是 HttpServletRequest* <p>* HttpServletRequest 参数,会阻塞线程,会抛出如下异常:* org.springframework.web.util.NestedServletException:* Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: Java heap space*/private Boolean isNotHttpServletRequest(Object arg) {return !(arg instanceof HttpServletRequest);}/*** 不是 HttpServletResponse* <p>* HttpServletResponse 参数,会抛出如下异常:* org.springframework.web.util.NestedServletException:* Handler dispatch failed; nested exception is java.lang.StackOverflowError*/private Boolean isNotHttpServletResponse(Object arg) {return !(arg instanceof HttpServletResponse);}}

日志开关配置

配置类:BaseFrameworkConfigProperties


package com.example.core.property;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;/*** BaseFramework 配置文件** @author songguanxun* 2019/08/27 15:40* @since 1.0.0*/
@Data
@Component
@ConfigurationProperties(prefix = "base-framework")
public class BaseFrameworkConfigProperties {/*** 接口日志配置*/private ApiLog apiLog = new ApiLog();/*** 接口日志配置*/@Datapublic static class ApiLog {/*** 是否开启接口日志*/private boolean enabled = false;}}

配置文件:application.yml

# 自定义配置
base-framework:api-log:enabled: false

四、测试案例一:查询用户列表

4.1 测试代码

package com.example.web.user.controller;import com.example.core.log.annotation.ApiLog;
import com.example.core.model.PageQuery;
import com.example.web.model.query.UserQuery;
import com.example.web.model.vo.UserVO;
import com.example.web.user.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.validation.Valid;
import java.util.List;@Slf4j
@RestController
@RequestMapping("users")
@Tag(name = "用户管理")
public class UserController {private final UserService userService;public UserController(UserService userService) {this.userService = userService;}@ApiLog@GetMapping@Operation(summary = "查询用户列表", description = "支持通过”姓名“和”手机号码“筛选用户")public List<UserVO> listUsers(@Valid UserQuery userQuery, PageQuery pageQuery) {log.info("查询用户列表。userQuery={},pageQuery={}", userQuery, pageQuery);return userService.listUsers(userQuery);}}
package com.example.web.model.query;import com.example.core.constant.RegexConstant;
import com.example.core.validation.phone.query.MobilePhoneQuery;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springdoc.api.annotations.ParameterObject;@Data
@ParameterObject
@Schema(name = "用户Query")
public class UserQuery {@Schema(description = "姓名", example = "张三")private String name;@MobilePhoneQuery@Schema(description = "手机号码", example = "18612345678", pattern = RegexConstant.NUMBERS, maxLength = 11)private String mobilePhone;}
package com.example.core.model;import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.FieldNameConstants;
import org.springdoc.api.annotations.ParameterObject;@Data
@FieldNameConstants
@ParameterObject
@Schema(name = "分页参数Query")
public class PageQuery {@Schema(description = "当前页码", type = "Integer", defaultValue = "1", example = "1", minimum = "1")private Integer pageNumber = 1;@Schema(description = "每 1 页的数据量", type = "Integer", defaultValue = "10", example = "10", minimum = "1", maximum = "100")private Integer pageSize = 10;}

4.2 接口调用效果

在这里插入图片描述

4.3 控制台日志

在这里插入图片描述

五、排除HttpServletRequest和HttpServletResponse参数

测试 HttpServletRequest、HttpServletResponse 和 HttpSession,是否在接口日志处理时堵塞线程或抛出异常?

5.1 原因

获取有效的控制器方法参数列表时,需要排除 HttpServletRequest 和 HttpServletResponse 参数。原因如下:

  1. 打印 HttpServletRequest 参数,会阻塞线程,抛出异常 NestedServletException-OutOfMemoryError。
  2. 打印 HttpServletResponse 参数,会抛出异常 NestedServletException-StackOverflowError。

HttpSession能够正常获取并打印日志,不需要特殊处理。

5.2 核心代码示例

下面图片中圈中的部分,就是排除HttpServletRequest和HttpServletResponse参数的核心代码。
在这里插入图片描述

5.3 测试代码

package com.example.web.api.log.controller;import com.example.core.log.annotation.ApiLog;
import com.example.core.model.PageQuery;
import com.example.web.model.query.UserQuery;
import com.example.web.model.vo.UserVO;
import com.example.web.user.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;
import java.util.List;@Slf4j
@RestController
@RequestMapping("/api/log")
@Tag(name = "接口日志")
public class ApiLogController {private final UserService userService;public ApiLogController(UserService userService) {this.userService = userService;}@ApiLog@GetMapping(path = "users")@Operation(summary = "查询用户列表", description = "测试 HttpServletRequest、HttpServletResponse 和 HttpSession,是否在接口日志处理时堵塞线程或抛出异常")public List<UserVO> listUsers(@Valid UserQuery userQuery, PageQuery pageQuery,HttpServletRequest request, HttpServletResponse response, HttpSession session) {log.info("查询用户列表。userQuery={},pageQuery={}", userQuery, pageQuery);return userService.listUsers(userQuery);}}

5.4 正常调用效果

在这里插入图片描述

5.5 打印HttpServletRequest参数,会阻塞线程,抛出异常

测试不排除控制器方法中的HttpServletRequest参数,直接打印的效果

打印HttpServletRequest 参数,会阻塞线程很长一段时间,大约几十秒,然后会抛出如下异常:

org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: Java heap space

接口阻塞

在这里插入图片描述

抛出异常NestedServletException-OutOfMemoryError

在这里插入图片描述

接口响应

异常统一处理后,响应给前端,耗时50多秒。

在这里插入图片描述

5.6 打印HttpServletResponse参数,会抛出异常

测试不排除控制器方法中的HttpServletResponse参数,直接打印的效果

打印 HttpServletResponse 参数,会抛出如下异常:

org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.StackOverflowError

抛出异常NestedServletException-StackOverflowError

在这里插入图片描述

接口响应

异常统一处理后,响应给前端。
在这里插入图片描述

六、总结

本文实现了基于Spring Boot的接口请求日志记录方案,通过AOP与自定义注解相结合,为指定接口提供了灵活的日志记录能力,并通过配置项支持日志记录的开启与关闭,优化了系统性能。实际生产环境中,建议采用异步方式(如消息队列)处理接口日志。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/491789.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

springboot自定义注解实战

基础知识概述 aop 面向切面编程 1、切面&#xff08;aspect&#xff09;散落在系统各处的通用的业务逻辑代码&#xff0c;如日志模块&#xff0c;权限模块&#xff0c;事务模块等&#xff0c;切面用来装载pointcut和advice2、通知&#xff08;advice&#xff09;所谓通知指的…

AI时代 编程高手的秘密武器:世界顶级大学推荐的计算机教材

文章目录 01 《深入理解计算机系统》02 《算法导论》03 《计算机程序的构造和解释》04 《数据库系统概念》05 《计算机组成与设计&#xff1a;硬件/软件接口》06 《离散数学及其应用》07 《组合数学》08《斯坦福算法博弈论二十讲》 清华、北大、MIT、CMU、斯坦福的学霸们在新学…

【Leetcode】235. 二叉搜索树的最近公共祖先

文章目录 题目思路代码结果 题目 题目链接 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个结点 p、q&#xff0c;最近公共祖先表示为一个结点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度…

深度学习手写字符识别:推理过程

说明 本篇博客主要是跟着B站中国计量大学杨老师的视频实战深度学习手写字符识别。 第一个深度学习实例手写字符识别 深度学习环境配置 可以参考下篇博客&#xff0c;网上也有很多教程&#xff0c;很容易搭建好深度学习的环境。 Windows11搭建GPU版本PyTorch环境详细过程 数…

抖音视频下载软件的优势|视频批量下载

抖音视频下载软件具有以下优势&#xff1a; 多种搜索方式&#xff1a;用户可以通过输入关键词进行批量视频提取&#xff0c;也可以通过分享链接进行单个视频的提取和下载。这样用户可以根据自己的需求选择最合适的搜索方式。 直观的界面设计&#xff1a;软件的界面分为五大块&…

分布式知识整理

分布式锁 以商场系统超卖现象举例 超卖现象一 现象&#xff1a; 商品卖出数量超出了库存数量。 产生原因&#xff1a; 扣减库存的动作在程序中进行&#xff0c;在程序中计算剩余库存&#xff0c;在并发场景下&#xff0c;导致库存计算错误。 代码复现 es.shutdown(); cycl…

ardupilot如何从四元数转旋转矢量

目录 文章目录 目录具体转换过程 本节主要记录四元数如何转成旋转矢量的推导过程&#xff0c;欢迎批评指正&#xff01; 四元数可以方便地转换为旋转矢量&#xff0c;旋转矢量是一个三维向量&#xff0c;描述了旋转的方向和大小。在将四元数转换为旋转矢量时&#xff0c;我们首…

Linux之项目部署与发布

目录 一、Nginx配置安装&#xff08;自启动&#xff09; 1.一键安装4个依赖 2. 下载并解压安装包 3. 安装Nginx 4. 启动 nginx 服务 5. 对外开放端口 6. 配置开机自启动 7.修改/etc/rc.d/rc.local的权限 二、后端部署tomcat负载均衡 1. 准备2个tomcat 2. 修改端口 3…

Elasticsearch:了解人工智能搜索算法

作者&#xff1a;来自 Elastic Jessica Taylor, Aditya Tripathi 人工智能工具无处不在&#xff0c;其原因并不神秘。 他们可以执行各种各样的任务并找到许多日常问题的解决方案。 但这些应用程序的好坏取决于它们的人工智能搜索算法。 简单来说&#xff0c;人工智能搜索算法是…

打印水仙花数---c语言刷题

欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎指出~ 题述 求出0&#xff5e;100000之间的所有“水仙花数”并输出。 “水仙花数”是指一个n位数&#xff0c;其各位数字的n次方之和确好等于该数本身&#xff0c;如:153&#…

分布式架构(分布式ID+分布式事务)

分布式架构 分布式事务产生的场景&#xff1a; 跨JVM进程产生的分布式事务 单体系统访问多个数据库实例 多服务访问同一个数据库实例 CAP理论 C&#xff1a;一致性&#xff0c;指写操作后的读操作可以读取到最新的数据状态&#xff0c;当数据分布在多个节点上&#xff0…

第三节:Vben Admin登录对接后端login接口

系列文章目录 第一节&#xff1a;Vben Admin介绍和初次运行 第二节&#xff1a;Vben Admin 登录逻辑梳理和对接后端准备 文章目录 系列文章目录前言一、Flask项目介绍二、使用步骤1.User模型创建2.迁移模型3. Token创建4. 编写蓝图5. 注册蓝图 三. 测试登录总结 前言 上一节&…