[Java实战]Spring Boot切面编程实现日志记录(三十六)

发布于:2025-05-27 ⋅ 阅读:(19) ⋅ 点赞:(0)

[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 性能调优策略

  1. 采样率控制:配置日志记录比例(如10%)

    @Value("${log.sampleRate:100}") 
    private int sampleRate;
    
    if (ThreadLocalRandom.current().nextInt(100) < sampleRate) {
        // 记录日志
    }
    
  2. 日志分级存储:ERROR日志单独文件存储

6.2 安全增强方案

  1. IP白名单过滤:仅记录指定IP的详细日志
  2. 日志加密存储:敏感字段加密处理

七、常见问题排查

7.1 切面不生效排查步骤

  1. 检查@EnableAspectJAutoProxy是否启用
  2. 确认切点表达式匹配目标方法
  3. 查看Bean是否被Spring管理

7.2 日志丢失问题

  1. 检查线程池队列容量
  2. 增加日志缓冲机制
  3. 监控磁盘空间

结语

通过合理的AOP日志切面设计,可以实现业务逻辑与日志记录的完美解耦。建议结合ELK等日志分析系统,构建完整的可观测性体系。

扩展阅读

希望本教程对您有帮助,请点赞❤️收藏⭐关注支持!欢迎在评论区留言交流技术细节!