Spring Boot 基于AOP实现Controller接口统一日志打印
在日常后端开发中,接口问题排查效率低是最常见的痛点之一:很多项目没有统一的日志规范,Controller接口缺少请求URL、入参、响应结果、执行耗时等核心日志,导致线上报错、接口异常时无法快速定位问题;如果手动在每个接口中打印日志,又会造成代码冗余、维护成本高,还容易遗漏。
为了解决这个问题,本文基于Spring AOP实现一套无侵入、可配置、灵活扩展的Controller层统一日志打印方案,自动记录接口全量信息,同时支持慢接口监控告警,完全满足开发排查问题的核心需求。
一、方案核心优势
- 无侵入性:无需修改任何Controller业务代码,通过切面统一处理日志逻辑
- 全量信息:自动打印接口URL、请求方法、入参、响应结果、执行耗时
- 安全可靠:过滤Web核心对象,避免序列化异常
- 灵活配置:慢接口阈值可动态调整,支持按需开关/裁剪日志
- 慢接口监控:自动识别超时接口,以WARN级别告警,助力性能优化
- 统一规范:所有接口日志格式一致,便于日志检索和问题排查
二、环境依赖
项目基于Spring Boot开发,只需引入3个核心依赖(Maven):
<!-- Spring AOP 核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- FastJSON 用于参数序列化 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.32</version>
</dependency>
<!-- Lombok 简化日志开发 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
三、核心实现代码
直接创建AOP日志切面类,复制即用,适配绝大多数Spring Boot项目:
package com.xm.kite.ztc.common.aspect;
import com.alibaba.fastjson2.JSONObject;
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.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
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.ArrayList;
import java.util.List;
/**
* AOP统一日志处理切面
* 自动打印Controller层接口:URL、方法名、入参、响应结果、执行耗时
* 支持慢接口告警配置
*/
@Slf4j
@Aspect
@Component
public class LoggerHandler {
/**
* 慢接口阈值(单位:毫秒)
* 从配置文件读取,默认2000ms(2秒)
*/
@Value("${method-logger.elapsed-time:2000}")
private Long loggerHandlerElapsedTimeMs;
/**
* 切点:匹配指定包下所有Controller的所有方法
* 可根据项目包路径灵活修改!!!
*/
@Pointcut("execution(* com.xm.kite.ztc..controller..*.*(..))")
public void allControllerMethods() {
}
/**
* 环绕通知:包裹目标接口,执行前后统一处理日志
*/
@Around("allControllerMethods()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
// 1. 记录接口开始时间
long startTime = System.currentTimeMillis();
// 2. 获取Http请求对象,解析请求URL
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String requestUrl = request.getRequestURL().toString();
// 3. 获取当前接口的类、方法名
Logger currentLogger = LoggerFactory.getLogger(joinPoint.getSignature().getDeclaringType());
String methodName = joinPoint.getSignature().getName();
// 4. 安全处理入参:过滤HttpServletRequest/Response,避免序列化异常
List<Object> safeArgs = new ArrayList<>();
for (Object arg : joinPoint.getArgs()) {
if (arg instanceof HttpServletRequest || arg instanceof HttpServletResponse) {
safeArgs.add("[" + arg.getClass().getSimpleName() + "]");
} else {
safeArgs.add(arg);
}
}
// 入参序列化为JSON
String requestArgs = JSONObject.toJSONString(safeArgs);
// 5. 打印【请求入参】日志
currentLogger.info("请求开始 >>> url: {}, method: {}, args: {}", requestUrl, methodName, requestArgs);
// 6. 执行目标接口方法
Object result = joinPoint.proceed();
// 7. 计算接口耗时
long elapsedTime = System.currentTimeMillis() - startTime;
String responseResult = JSONObject.toJSONString(result);
// 8. 分级打印【响应结果】日志:慢接口WARN告警,正常接口INFO打印
if (elapsedTime > loggerHandlerElapsedTimeMs) {
currentLogger.warn("请求结束【慢接口告警】>>> 阈值:{}ms, url: {}, method: {}, args: {}, result: {}, 耗时:{}ms",
loggerHandlerElapsedTimeMs, requestUrl, methodName, requestArgs, responseResult, elapsedTime);
} else {
currentLogger.info("请求结束 >>> url: {}, method: {}, args: {}, result: {}, 耗时:{}ms",
requestUrl, methodName, requestArgs, responseResult, elapsedTime);
}
return result;
}
}
四、灵活配置(核心!支持自定义调整)
1. 配置慢接口阈值
在application.yml/application.properties中动态调整慢接口标准,无需修改代码:
# 日志配置:慢接口阈值(单位:毫秒)
method-logger:
elapsed-time: 1500 # 调整为1.5秒,根据项目需求修改
2. 灵活调整打印内容(按需改造)
针对不同开发场景,可快速裁剪/扩展日志打印内容,以下是常用扩展方案:
(1)添加请求IP、请求方式
// 获取请求方式
String requestMethod = request.getMethod();
// 获取客户端IP
String clientIp = request.getRemoteAddr();
// 日志中新增打印
currentLogger.info("url: {}, ip: {}, requestType: {}, method: {}, args: {}",
requestUrl, clientIp, requestMethod, methodName, requestArgs);
(2)过滤敏感参数(密码、手机号等)
// 改造入参过滤逻辑,隐藏敏感字段
for (Object arg : safeArgs) {
if (arg instanceof UserDTO) {
UserDTO user = (UserDTO) arg;
user.setPassword("***"); // 密码脱敏
}
}
(3)日志总开关(按需开启/关闭)
// 新增配置开关
@Value("${method-logger.enabled:true}")
private boolean loggerEnabled;
// 环绕通知中判断
@Around("allControllerMethods()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
if (!loggerEnabled) {
return joinPoint.proceed(); // 关闭日志,直接执行接口
}
// 原有日志逻辑...
}
(4)修改切点范围
// 示例1:只打印指定包下的接口
@Pointcut("execution(* com.xm.kite.ztc.user.controller..*.*(..))")
// 示例2:只打印带有@GetMapping注解的方法
@Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
(5)关闭响应结果打印(简化日志)
如果接口返回数据过大,可取消响应结果打印:
// 直接删除result参数,简化日志
currentLogger.info("请求结束 >>> url: {}, 耗时:{}ms", requestUrl, elapsedTime);
五、代码核心解析
- 切点定义:
@Pointcut精准匹配项目中所有Controller接口,是AOP日志的生效范围 - 环绕通知:
@Around是核心,在接口执行前后分别记录请求、响应信息 - 参数安全处理:过滤
HttpServletRequest/Response对象,避免JSON序列化失败 - 日志分级:正常接口用
INFO级别,慢接口用WARN级别告警,便于日志筛选 - 动态配置:通过
@Value注入配置参数,支持环境差异化配置
六、日志效果展示
启动项目后,调用任意Controller接口,控制台会打印标准化日志:
# 正常接口
INFO c.x.k.z.t.controller.UserController - 请求开始 >>> url: http://localhost:8080/user/get, method: getUser, args: {"id":1}
INFO c.x.k.z.t.controller.UserController - 请求结束 >>> url: http://localhost:8080/user/get, method: getUser, args: {"id":1}, result: {"code":200,"data":{"name":"张三"}}, 耗时:12ms
# 慢接口告警
WARN c.x.k.z.t.controller.OrderController - 请求结束【慢接口告警】>>> 阈值:2000ms, url: http://localhost:8080/order/list, method: getOrderList, args: {"page":1}, result: {...}, 耗时:2560ms
示例:
七、注意事项
- AOP生效:Spring Boot 2.x+引入
spring-boot-starter-aop后,无需额外配置@EnableAspectJAutoProxy,自动开启AOP - 序列化问题:如果实体类存在循环引用,FastJSON会自动忽略,无需担心报错
- 性能影响:日志打印为轻量级操作,对接口性能无明显损耗
- 包路径修改:必须将切点中的包路径
com.xm.kite.ztc改为自己项目的实际包路径,否则日志不生效
总结
这套基于Spring AOP的统一日志方案,彻底解决了接口排查无日志、手动打印日志冗余的问题,同时具备极高的灵活性:开发人员可根据需求自由调整打印内容、开关日志、配置慢接口阈值。
方案无侵入、易扩展、标准化,完全适配企业级Spring Boot项目,大幅提升线上问题排查效率和接口性能监控能力。