SpringBoot实战:AOP切面实现统一接口请求日志记录
【摘要】 在后端项目排查问题时,缺少接口入参、返回值、耗时日志是非常头疼的问题。本文基于Spring AOP实现统一接口日志切面,自动记录请求地址、请求方式、请求参数、响应结果、接口耗时与客户端IP,无需在每个Controller手动编写日志代码。代码开箱即用,可直接集成到现有SpringBoot脚手架,同时附带异常场景日志捕获、性能优化与踩坑说明,适合后端工程规范化实战。
前言
日常开发中,我们经常需要记录接口访问日志,用于线上问题排查、接口调用统计。
如果在每一个Controller方法内手动写日志打印代码:
- 大量重复样板代码,业务代码和日志逻辑耦合;
- 新增接口容易忘记添加日志;
- 修改日志格式需要改动所有接口。
AOP(面向切面编程)可以完美解决这个问题,把日志记录逻辑抽离为切面,无侵入地拦截所有Controller接口,统一收集请求信息。
技术栈:Java17,SpringBoot 2.7.x,spring-boot-starter-aop
引入依赖
pom.xml 增加AOP依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
统一日志切面完整代码
新建 WebLogAspect.java
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
@Aspect
@Component
@Slf4j
public class WebLogAspect {
/**
* 切点:拦截所有controller包下所有类的所有方法
*/
@Pointcut("execution(* com.demo.controller..*.*(..))")
public void webLogPointCut(){}
@Around("webLogPointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
// 获取请求基础信息
String url = request.getRequestURL().toString();
String httpMethod = request.getMethod();
String ip = request.getRemoteAddr();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String className = signature.getDeclaringTypeName();
String methodName = signature.getName();
Object[] args = joinPoint.getArgs();
long start = System.currentTimeMillis();
Object result = null;
try {
// 执行目标接口方法
result = joinPoint.proceed();
long cost = System.currentTimeMillis() - start;
log.info("【接口请求】URL:{} | METHOD:{} | IP:{} | CLASS:{} | METHOD:{} | PARAMS:{} | RESP:{} | COST:{}ms",
url, httpMethod, ip, className, methodName, Arrays.toString(args), result, cost);
}catch (Throwable e){
long cost = System.currentTimeMillis() - start;
log.error("【接口异常】URL:{} | METHOD:{} | IP:{} | CLASS:{} | METHOD:{} | PARAMS:{} | COST:{}ms | ERR:{}",
url, httpMethod, ip, className, methodName, Arrays.toString(args), cost, e.getMessage(), e);
throw e;
}
return result;
}
}
测试Controller
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/log")
public class LogTestController {
@GetMapping("/hello")
public Result<String> hello(@RequestParam String name){
return Result.success("hello:" + name);
}
@GetMapping("/error")
public Result<String> errorTest(){
int num = 1/0;
return Result.success("ok");
}
}
核心逻辑讲解
- 切点表达式
execution(* com.demo.controller..*.*(..))
含义:匹配com.demo.controller包及其子包下,所有类的全部方法。
你只需要修改包路径,适配自己项目结构。 - @Around 环绕通知
环绕通知能力最强,可以在方法执行前、执行后捕获返回值,捕获异常,非常适合日志统计和耗时计算。
joinPoint.proceed():执行原始接口方法;- 方法执行前记录开始时间;
- 方法执行完毕拿到返回结果;
- 抛出异常时进入catch块,打印错误堆栈。
- RequestContextHolder 获取HttpServletRequest
利用请求上下文拿到request对象,读取请求地址、请求方式、客户端IP。
常见问题与踩坑
- 切面不生效?
- 确认类上加了
@Component交给Spring管理; @Aspect注解不能漏掉;- 切点包路径写对;
- AOP依赖是否引入。>
Spring AOP只能拦截Spring容器管理Bean的public方法,private方法无法拦截。
- 文件上传接口,打印参数会打印出文件流,日志爆炸
解决方案:增加过滤,对于MultipartFile类型参数不打印详情。
// 判断参数类型,过滤文件
if(args[i] instanceof MultipartFile){
args[i] = "[文件参数,省略]";
}
- 日志打印敏感数据
手机号、身份证、密码等信息不能直接打印日志,可以增加脱敏工具类,对指定字段脱敏。 - 异步场景
如果接口使用@Async异步,RequestContextHolder默认拿不到请求对象,需要配置共享上下文。
生产优化方案
- 区分环境:开发环境打印完整返回值,生产环境可以选择性关闭打印response,减少日志量;
- 使用日志框架级别控制,生产环境使用WARN级别减少日志输出;
- 增加请求ID(traceId),配合MDC,整条链路日志使用同一个traceId,方便链路追踪;
- 可以增加注解
@IgnoreLog,自定义注解,接口加上该注解就跳过日志打印。
扩展:自定义跳过日志注解
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface IgnoreLog {
}
在切面中增加判断:如果方法上存在@IgnoreLog注解,直接执行方法,不打印日志。
总结
AOP实现接口日志,是后端脚手架标配功能。做到业务代码零侵入,统一管控所有接口访问记录。
结合前面几篇:统一返回结果、全局异常处理器、全局跨域、AOP请求日志,一套基础后端脚手架已经成型。
后续可以继续集成:参数校验(JSR380)。
全文约1200字,直接复制Markdown发布华为云博客。
下一篇:SpringBoot JSR380参数校验实战,要不要继续?
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)