日志切面可以记录操作名称、方法、结果和耗时。计时必须从业务方法调用前开始,并在异常时也记录结果。下面只写应用日志,先验证切面行为,再按需要加入审计存储。
运行条件
已有 Spring Boot 3.x Web 项目,加入 spring-boot-starter-aop。三个文件位于启动类扫描的 example 包中。
OperationLog.java:
package example;
import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface OperationLog { String value();}OperationLogAspect.java:
package example;
import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;
@Aspect@Componentpublic class OperationLogAspect { private static final Logger LOG = LoggerFactory.getLogger(OperationLogAspect.class);
@Around("@annotation(operation)") public Object record(ProceedingJoinPoint call, OperationLog operation) throws Throwable { long started = System.nanoTime(); boolean succeeded = false; String errorType = ""; try { Object result = call.proceed(); succeeded = true; return result; } catch (Throwable error) { errorType = error.getClass().getSimpleName(); throw error; } finally { long elapsedMs = (System.nanoTime() - started) / 1_000_000; LOG.info("operation={} method={} succeeded={} elapsedMs={} errorType={}", operation.value(), call.getSignature().toShortString(), succeeded, elapsedMs, errorType); } }}LogDemoController.java:
package example;
import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;
@RestControllerpublic class LogDemoController { @OperationLog("读取演示状态") @GetMapping("/log-demo") public String read() { return "ok"; }}启动应用并访问 http://localhost:8080/log-demo,检查应用日志中是否出现 operation、method 和 elapsedMs。日志描述的是一次方法调用,不代表数据库事务已提交或异步任务已完成。
接入业务审计时
- 操作者从已经认证的上下文获取;日志切面不重新解析并打印 Token。
- 请求参数、响应内容按字段白名单提取并限制长度,不直接序列化全部对象。
- X-Forwarded-For 等代理头只有来自可信代理时才可用于识别来源,不能直接信任任意客户端提供的值。
- 异步入库要有有界队列、失败处理和关闭流程;审计是否允许丢失由业务决定。
Spring AOP 依靠代理拦截调用,同一个对象里的 this.method() 通常不会再次经过代理。对异步、响应式方法计时,需要覆盖实际任务生命周期,不能仅测返回 Future 或 Flux 的速度。
参考:Spring AOP 代理机制。
评论