SpringBoot实战:AOP切面实现统一接口请求日志记录

举报
JiHua 发表于 2026/10/17 11:07:00 2026/10/17
【摘要】 在后端项目排查问题时,缺少接口入参、返回值、耗时日志是非常头疼的问题。本文基于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");
    }
}

核心逻辑讲解

  1. 切点表达式execution(* com.demo.controller..*.*(..))
    含义:匹配com.demo.controller包及其子包下,所有类的全部方法。
    你只需要修改包路径,适配自己项目结构。
  2. @Around 环绕通知
    环绕通知能力最强,可以在方法执行前、执行后捕获返回值,捕获异常,非常适合日志统计和耗时计算。
  • joinPoint.proceed():执行原始接口方法;
  • 方法执行前记录开始时间;
  • 方法执行完毕拿到返回结果;
  • 抛出异常时进入catch块,打印错误堆栈。
  1. RequestContextHolder 获取HttpServletRequest
    利用请求上下文拿到request对象,读取请求地址、请求方式、客户端IP。

常见问题与踩坑

  1. 切面不生效?
  • 确认类上加了@Component交给Spring管理;
  • @Aspect注解不能漏掉;
  • 切点包路径写对;
  • AOP依赖是否引入。>

Spring AOP只能拦截Spring容器管理Bean的public方法,private方法无法拦截。

  1. 文件上传接口,打印参数会打印出文件流,日志爆炸
    解决方案:增加过滤,对于MultipartFile类型参数不打印详情。
// 判断参数类型,过滤文件
if(args[i] instanceof MultipartFile){
    args[i] = "[文件参数,省略]";
}
  1. 日志打印敏感数据
    手机号、身份证、密码等信息不能直接打印日志,可以增加脱敏工具类,对指定字段脱敏。
  2. 异步场景
    如果接口使用@Async异步,RequestContextHolder默认拿不到请求对象,需要配置共享上下文。

生产优化方案

  1. 区分环境:开发环境打印完整返回值,生产环境可以选择性关闭打印response,减少日志量;
  2. 使用日志框架级别控制,生产环境使用WARN级别减少日志输出;
  3. 增加请求ID(traceId),配合MDC,整条链路日志使用同一个traceId,方便链路追踪;
  4. 可以增加注解@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)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。