基于AOP实现智能日志打印:告别繁琐的手动日志记录

传统日志记录的痛点

在我们的日常开发工作中,经常会遇到这样的场景:

  • 每个方法都要手动添加日志记录,代码冗余严重
  • 方法入参、出参、执行时间都需要手动记录
  • 日志格式不统一,排查问题困难
  • 敏感信息(如密码、身份证号)不小心被记录到日志中
  • 临时调试日志忘记删除,污染日志文件

传统的手动日志记录方式不仅效率低下,还容易出错。今天我们就来聊聊如何用AOP实现智能日志打印。

AOP智能日志的优势

相比传统的手动日志记录,AOP方式有以下优势:

  • 统一管理:日志逻辑集中处理,便于维护
  • 无侵入性:业务代码不受日志代码干扰
  • 灵活配置:可通过注解灵活控制日志行为
  • 安全性:自动脱敏敏感信息,避免信息泄露
  • 性能优化:按需记录,减少不必要的日志输出

解决方案思路

今天我们要解决的,就是如何用AOP实现智能日志打印。

核心思路是:

  1. 自定义注解:标记需要记录日志的方法
  2. 切面编程:拦截目标方法,统一处理日志逻辑
  3. 智能脱敏:自动识别和脱敏敏感字段
  4. 性能监控:记录方法执行时间和性能指标

自定义日志注解

1. 日志注解定义

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SmartLog {
    /**
     * 日志描述
     */
    String value() default "";
    
    /**
     * 是否记录入参
     */
    boolean logArgs() default true;
    
    /**
     * 是否记录返回值
     */
    boolean logReturn() default true;
    
    /**
     * 是否记录执行时间
     */
    boolean logTime() default true;
    
    /**
     * 是否脱敏敏感信息
     */
    boolean autoMask() default true;
    
    /**
     * 需要脱敏的字段
     */
    String[] maskFields() default {};
    
    /**
     * 日志级别
     */
    LogLevel level() default LogLevel.INFO;
    
    enum LogLevel {
        DEBUG, INFO, WARN, ERROR
    }
}

AOP切面实现

1. 日志切面类

@Aspect
@Component
@Slf4j
public class SmartLogAspect {
    
    private static final Logger logger = LoggerFactory.getLogger(SmartLogAspect.class);
    
    @Around("@annotation(smartLog)")
    public Object around(ProceedingJoinPoint joinPoint, SmartLog smartLog) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        
        // 记录方法开始
        logMethodStart(joinPoint, smartLog, methodName, className);
        
        long startTime = System.currentTimeMillis();
        Object result = null;
        Throwable throwable = null;
        
        try {
            result = joinPoint.proceed();
            return result;
        } catch (Throwable t) {
            throwable = t;
            throw t;
        } finally {
            long executionTime = System.currentTimeMillis() - startTime;
            
            // 记录方法结束
            logMethodEnd(joinPoint, smartLog, methodName, className, result, 
                        throwable, executionTime);
        }
    }
    
    private void logMethodStart(ProceedingJoinPoint joinPoint, SmartLog smartLog, 
                               String methodName, String className) {
        if (smartLog.logArgs()) {
            Object[] args = joinPoint.getArgs();
            String maskedArgs = maskSensitiveData(args, smartLog);
            
            String logMessage = String.format("方法开始执行 - %s.%s(), 参数: %s", 
                                            className, methodName, maskedArgs);
            logWithLevel(logger, smartLog.level(), logMessage);
        } else {
            String logMessage = String.format("方法开始执行 - %s.%s()", className, methodName);
            logWithLevel(logger, smartLog.level(), logMessage);
        }
    }
    
    private void logMethodEnd(ProceedingJoinPoint joinPoint, SmartLog smartLog, 
                             String methodName, String className, Object result, 
                             Throwable throwable, long executionTime) {
        StringBuilder sb = new StringBuilder();
        sb.append(String.format("方法执行结束 - %s.%s()", className, methodName));
        
        if (smartLog.logTime()) {
            sb.append(String.format(", 执行时间: %dms", executionTime));
        }
        
        if (smartLog.logReturn() && result != null) {
            String maskedResult = smartLog.autoMask() ? 
                maskSensitiveData(result, smartLog) : 
                String.valueOf(result);
            sb.append(String.format(", 返回值: %s", maskedResult));
        }
        
        if (throwable != null) {
            sb.append(String.format(", 异常: %s", throwable.getMessage()));
            logWithLevel(logger, LogLevel.ERROR, sb.toString(), throwable);
        } else {
            logWithLevel(logger, smartLog.level(), sb.toString());
        }
    }
}

2. 敏感数据脱敏工具

@Component
public class SensitiveDataMasker {
    
    private static final Set<String> SENSITIVE_FIELDS = new HashSet<>();
    private static final Set<String> SENSITIVE_KEYWORDS = new HashSet<>();
    
    static {
        // 常见敏感字段
        SENSITIVE_FIELDS.addAll(Arrays.asList(
            "password", "pwd", "pass", "secret", "token", "key",
            "phone", "mobile", "tel", "email", "id_card", "card_number",
            "account", "bank_account", "credit_card", "ssn"
        ));
        
        // 敏感关键词
        SENSITIVE_KEYWORDS.addAll(Arrays.asList(
            "password", "token", "secret", "key", "pwd", "pass"
        ));
    }
    
    public String maskSensitiveData(Object obj, SmartLog smartLog) {
        if (obj == null) {
            return "null";
        }
        
        try {
            // 如果指定了特定脱敏字段,优先使用
            if (smartLog.maskFields().length > 0) {
                return maskSpecificFields(obj, smartLog.maskFields());
            }
            
            // 自动脱敏
            return autoMaskObject(obj);
        } catch (Exception e) {
            log.warn("脱敏处理异常", e);
            return obj.toString();
        }
    }
    
    private String maskSpecificFields(Object obj, String[] maskFields) {
        if (obj instanceof String) {
            return maskString((String) obj);
        }
        
        // 对于复杂对象,使用JSON序列化后脱敏
        String jsonStr = JSON.toJSONString(obj);
        for (String field : maskFields) {
            jsonStr = jsonStr.replaceAll(
                "(\"" + field + "\":\")[^\"]*?(\")", 
                "$1***$2"
            );
        }
        return jsonStr;
    }
    
    private String autoMaskObject(Object obj) {
        if (obj instanceof String) {
            return maskString((String) obj);
        }
        
        // 使用Jackson进行深度脱敏
        ObjectMapper mapper = new ObjectMapper();
        JsonNode node = mapper.valueToTree(obj);
        maskJsonNode(node);
        
        try {
            return mapper.writeValueAsString(node);
        } catch (JsonProcessingException e) {
            log.warn("JSON序列化异常", e);
            return obj.toString();
        }
    }
    
    private void maskJsonNode(JsonNode node) {
        if (node.isObject()) {
            Iterator<Map.Entry<String, JsonNode>> fields = node.fields();
            while (fields.hasNext()) {
                Map.Entry<String, JsonNode> field = fields.next();
                String fieldName = field.getKey().toLowerCase();
                
                if (isSensitiveField(fieldName)) {
                    ((ObjectNode) node).put(field.getKey(), "***");
                } else {
                    maskJsonNode(field.getValue());
                }
            }
        } else if (node.isArray()) {
            for (JsonNode element : node) {
                maskJsonNode(element);
            }
        }
    }
    
    private boolean isSensitiveField(String fieldName) {
        return SENSITIVE_FIELDS.contains(fieldName) ||
               SENSITIVE_KEYWORDS.stream().anyMatch(fieldName::contains);
    }
    
    private String maskString(String str) {
        if (str == null || str.length() <= 4) {
            return "***";
        }
        
        // 手机号脱敏:138****8888
        if (str.matches("\\d{11}")) {
            return str.substring(0, 3) + "****" + str.substring(7);
        }
        
        // 邮箱脱敏:zhang***@example.com
        if (str.contains("@")) {
            int atIndex = str.indexOf("@");
            String prefix = str.substring(0, Math.min(3, atIndex));
            return prefix + "***" + str.substring(atIndex);
        }
        
        // 身份证脱敏:110***1990********12
        if (str.matches("\\d{18}|\\d{17}[Xx]")) {
            return str.substring(0, 3) + "********" + str.substring(14);
        }
        
        // 一般字符串脱敏:保留前2位和后2位
        if (str.length() > 4) {
            return str.substring(0, 2) + "***" + str.substring(str.length() - 2);
        }
        
        return "***";
    }
}

日志级别控制

1. 日志级别管理

@Component
public class LogLevelManager {
    
    private final Map<String, SmartLog.LogLevel> methodLogLevels = new ConcurrentHashMap<>();
    
    public void setLogLevel(String methodSignature, SmartLog.LogLevel level) {
        methodLogLevels.put(methodSignature, level);
    }
    
    public SmartLog.LogLevel getLogLevel(String methodSignature) {
        return methodLogLevels.getOrDefault(methodSignature, SmartLog.LogLevel.INFO);
    }
    
    private void logWithLevel(Logger logger, SmartLog.LogLevel level, String message) {
        switch (level) {
            case DEBUG:
                logger.debug(message);
                break;
            case INFO:
                logger.info(message);
                break;
            case WARN:
                logger.warn(message);
                break;
            case ERROR:
                logger.error(message);
                break;
        }
    }
    
    private void logWithLevel(Logger logger, SmartLog.LogLevel level, 
                             String message, Throwable throwable) {
        switch (level) {
            case DEBUG:
                logger.debug(message, throwable);
                break;
            case INFO:
                logger.info(message, throwable);
                break;
            case WARN:
                logger.warn(message, throwable);
                break;
            case ERROR:
                logger.error(message, throwable);
                break;
        }
    }
}

实际应用示例

1. 业务方法使用

@Service
@Slf4j
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    @SmartLog(value = "用户登录", logArgs = true, logReturn = true, autoMask = true)
    public LoginResult login(String username, String password) {
        // 登录逻辑
        User user = userRepository.findByUsername(username);
        if (user == null) {
            return LoginResult.failure("用户不存在");
        }
        
        if (!BCrypt.checkpw(password, user.getPassword())) {
            return LoginResult.failure("密码错误");
        }
        
        return LoginResult.success(generateToken(user));
    }
    
    @SmartLog(value = "创建用户", maskFields = {"password", "idCard"})
    public User createUser(CreateUserRequest request) {
        User user = new User();
        user.setUsername(request.getUsername());
        user.setPassword(BCrypt.hashpw(request.getPassword(), BCrypt.gensalt()));
        user.setIdCard(request.getIdCard());
        user.setEmail(request.getEmail());
        
        return userRepository.save(user);
    }
    
    @SmartLog(value = "获取用户信息", logTime = true)
    public UserInfo getUserInfo(Long userId) {
        User user = userRepository.findById(userId);
        return convertToUserInfo(user);
    }
}

2. 控制器使用

@RestController
@RequestMapping("/api/user")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @PostMapping("/login")
    @SmartLog(value = "用户登录接口", level = SmartLog.LogLevel.INFO)
    public Result<LoginResult> login(@RequestBody LoginRequest request) {
        LoginResult result = userService.login(request.getUsername(), request.getPassword());
        return Result.success(result);
    }
    
    @PostMapping("/register")
    @SmartLog(value = "用户注册接口", logArgs = false, logReturn = false)
    public Result<User> register(@RequestBody CreateUserRequest request) {
        User user = userService.createUser(request);
        return Result.success(user);
    }
}

性能优化

1. 日志开关控制

@Component
public class LogSwitchManager {
    
    @Value("${app.log.enabled:true}")
    private boolean logEnabled;
    
    @Value("${app.log.performance-threshold:1000}") // 1秒
    private long performanceThreshold;
    
    public boolean isLogEnabled() {
        return logEnabled;
    }
    
    public boolean shouldLogPerformance(long executionTime) {
        return executionTime > performanceThreshold;
    }
}

2. 异步日志处理

@Component
public class AsyncLogProcessor {
    
    private final ExecutorService logExecutor = 
        Executors.newSingleThreadExecutor(r -> {
            Thread t = new Thread(r, "async-log-thread");
            t.setDaemon(true);
            return t;
        });
    
    public void asyncLog(String message, SmartLog.LogLevel level) {
        if (LogSwitchManager.isLogEnabled()) {
            logExecutor.submit(() -> {
                // 异步处理日志
                doLog(message, level);
            });
        }
    }
    
    private void doLog(String message, SmartLog.LogLevel level) {
        // 实际的日志记录逻辑
        switch (level) {
            case DEBUG:
                log.debug(message);
                break;
            case INFO:
                log.info(message);
                break;
            case WARN:
                log.warn(message);
                break;
            case ERROR:
                log.error(message);
                break;
        }
    }
}

配置管理

1. 配置文件

app:
  log:
    enabled: true
    performance-threshold: 1000
    sensitive-fields:
      - password
      - phone
      - id-card
      - email
    mask-patterns:
      phone: "(\d{3})\d{4}(\d{4})"
      id-card: "(\d{6})\d{8}(\w{4})"

注意事项

在使用AOP智能日志时,需要注意以下几点:

  1. 性能影响:日志记录本身也会消耗性能,要注意平衡
  2. 循环引用:避免在日志记录中引入循环依赖
  3. 异常处理:日志处理异常不应影响业务逻辑
  4. 配置管理:不同环境应有不同的日志配置
  5. 安全考虑:确保敏感信息真正被脱敏

总结

通过AOP实现的智能日志打印,不仅能大幅减少手动日志记录的工作量,还能提供统一、安全、高效的日志记录能力。这种方案特别适合在大型项目中统一日志规范,提升开发效率。

希望这篇文章对你有所帮助!如果你觉得有用,欢迎关注【服务端技术精选】公众号,获取更多后端技术干货。


原文首发于 www.jiangyi.space

转载请注明出处


标题:基于AOP实现智能日志打印:告别繁琐的手动日志记录
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/01/21/1768975140971.html

    0 评论
avatar