[Java实战]Spring Boot切面编程实现日志记录(三十六)
一、AOP日志记录核心原理
1.1 AOP技术体系
Spring AOP基于代理模式实现,关键组件:
- JoinPoint:程序执行点(方法调用/异常抛出)
- Pointcut:切点表达式(定义拦截规则)
- Advice:增强逻辑(前置/环绕/异常通知)
- Weaving:将切面织入目标对象的过程
1.2 日志切面设计要点
- 精准定位:通过切点表达式筛选需要监控的方法
- 低侵入性:业务代码零修改
- 性能优化:异步记录/采样率控制
- 安全处理:敏感数据脱敏
二、企业级日志切面实现
2.1 环境准备(pom.xml)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.9</version>
</dependency>
2.2 智能日志切面实现
@Aspect
@Component
@Slf4j
public class SmartLogAspect {
private static final Gson GSON = new GsonBuilder().create();
private static final int MAX_PARAM_LENGTH = 1000;
// 定义Controller层切点
@Pointcut("execution(* com.example..controller..*.*(..))")
public void controllerPointcut() {}
// 环绕通知
@Around("controllerPointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
String traceId = MDC.get("traceId");
Object result = null;
Throwable exception = null;
try {
// 执行前日志
logRequest(joinPoint, traceId);
// 执行目标方法
result = joinPoint.proceed();
return result;
} catch (Throwable ex) {
exception = ex;
throw ex;
} finally {
// 执行后日志
long costTime = System.currentTimeMillis() - startTime;
logResponse(joinPoint, traceId, result, exception, costTime);
}
}
private void logRequest(ProceedingJoinPoint joinPoint, String traceId) {
if (log.isInfoEnabled()) {
Object[] args = joinPoint.getArgs();
String params = truncateParam(GSON.toJson(args));
log.info("[请求入口] traceId:{} | method:{} | params:{}",
traceId,
getMethodSignature(joinPoint),
params);
}
}
private void logResponse(ProceedingJoinPoint joinPoint, String traceId,
Object result, Throwable ex, long costTime) {
String logLevel = ex == null ? "INFO" : "ERROR";
String response = ex != null ? ex.getMessage() : truncateParam(GSON.toJson(result));
if (ex != null) {
log.error("[请求异常] traceId:{} | method:{} | cost:{}ms | error:{}",
traceId,
getMethodSignature(joinPoint),
costTime,
response,
ex);
} else if (log.isInfoEnabled()) {
log.info("[请求完成] traceId:{} | method:{} | cost:{}ms | response:{}",
traceId,
getMethodSignature(joinPoint),
costTime,
response);
}
}
private String truncateParam(String param) {
return param.length() > MAX_PARAM_LENGTH ?
param.substring(0, MAX_PARAM_LENGTH) + "..." : param;
}
private String getMethodSignature(ProceedingJoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
return signature.getDeclaringType().getSimpleName() + "#" + signature.getName();
}
}
三、日志切面功能增强
3.1 敏感数据脱敏处理
// 在logRequest方法中添加
private String maskSensitiveData(String original) {
return original.replaceAll("(\"password\":\")(.*?)(\")", "$1****$3")
.replaceAll("(\"idCard\":\")(\\d{4})\\d+(\\d{4})", "$1$2****$3");
}
3.2 异步日志记录
@Bean
public ThreadPoolTaskExecutor logTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(1000);
executor.setThreadNamePrefix("log-async-");
return executor;
}
@Async("logTaskExecutor")
public void asyncLog(Runnable logTask) {
logTask.run();
}
四、测试验证方案
4.1 测试接口开发
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping("/create")
public User createUser(@RequestBody User user) {
if (user.getAge() < 18) {
throw new IllegalArgumentException("未成年用户禁止注册");
}
return user;
}
}
4.2 测试用例设计
测试场景 | 输入参数 | 预期日志输出 |
---|---|---|
正常请求 | {name: “张三”, age: 20} | 包含请求/响应日志 |
异常请求 | {name: “李四”, age: 16} | 包含异常堆栈信息 |
大参数请求 | 长JSON数据 | 参数截断显示 |
4.3 使用Postman测试
# 正常请求
POST http://localhost:8080/user/create
Content-Type: application/json
{
"name": "张三",
"password": "123456",
"age": 20,
"idCard": "310115199001011234"
}
# 异常请求
POST http://localhost:8080/user/create
{
"name": "李四",
"password": "654321",
"age": 16,
"idCard": "310115201001011234"
}
五、日志输出示例分析
5.1 正常请求日志
2023-03-01 14:30:22 [INFO ] [log-async-1] c.e.aop.SmartLogAspect
[请求入口] traceId:3e9b1f | method:UserController#createUser | params:[{"name":"张三","password":"****","age":20...}]
2023-03-01 14:30:22 [INFO ] [log-async-2] c.e.aop.SmartLogAspect
[请求完成] traceId:3e9b1f | method:UserController#createUser | cost:45ms | response:{"name":"张三","age":20...}
5.2 异常请求日志
2023-03-01 14:35:18 [ERROR] [log-async-3] c.e.aop.SmartLogAspect
[请求异常] traceId:8a7c6d | method:UserController#createUser | cost:12ms | error:未成年用户禁止注册
java.lang.IllegalArgumentException: 未成年用户禁止注册
at com.example.controller.UserController.createUser(UserController.java:15)
...
六、生产环境优化建议
6.1 性能调优策略
采样率控制:配置日志记录比例(如10%)
@Value("${log.sampleRate:100}") private int sampleRate; if (ThreadLocalRandom.current().nextInt(100) < sampleRate) { // 记录日志 }
日志分级存储:ERROR日志单独文件存储
6.2 安全增强方案
- IP白名单过滤:仅记录指定IP的详细日志
- 日志加密存储:敏感字段加密处理
七、常见问题排查
7.1 切面不生效排查步骤
- 检查
@EnableAspectJAutoProxy
是否启用 - 确认切点表达式匹配目标方法
- 查看Bean是否被Spring管理
7.2 日志丢失问题
- 检查线程池队列容量
- 增加日志缓冲机制
- 监控磁盘空间
结语
通过合理的AOP日志切面设计,可以实现业务逻辑与日志记录的完美解耦。建议结合ELK等日志分析系统,构建完整的可观测性体系。
扩展阅读:
希望本教程对您有帮助,请点赞❤️收藏⭐关注支持!欢迎在评论区留言交流技术细节!