第5篇:日志处理器的核心逻辑 - 让日志更智能

前言

前面我们实现了AOP切面来拦截方法调用,本章将深入日志处理器的核心逻辑,学习如何将原始的方法调用信息转换为结构化、智能化的日志输出。这是框架价值体现的关键环节。

日志处理器的设计架构

核心处理流程

graph LR A[方法调用信息] --> B[LogMethodProcessor] B --> C[参数序列化] B --> D[返回值处理] B --> E[异常信息提取] B --> F[执行时间计算] C --> G[敏感信息脱敏] D --> G E --> G F --> G G --> H[消息格式化] H --> I[日志输出]

处理器的核心职责:

  • 🔄 数据转换:将Java对象转换为可读的日志格式
  • 🛡️ 安全处理:对敏感信息进行脱敏
  • ⏱️ 性能统计:精确计算方法执行时间
  • 📝 消息格式化:生成结构化的日志消息
  • 🎯 上下文管理:整合请求上下文信息

LogMethodProcessor - 核心处理器实现

java 复制代码
package com.simpleflow.log.processor;

import com.simpleflow.log.annotation.LogLevel;
import com.simpleflow.log.config.LogConfig;
import com.simpleflow.log.context.LogContext;
import com.simpleflow.log.context.ThreadLocalTraceHolder;
import com.simpleflow.log.formatter.LogFormatter;
import com.simpleflow.log.util.JsonUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
 * 日志方法处理器
 * 
 * 负责处理方法执行的日志记录,包括参数序列化、返回值处理、
 * 异常信息提取、执行时间计算等核心功能
 */
@Component
public class LogMethodProcessor {
    
    private static final Logger logger = LoggerFactory.getLogger(LogMethodProcessor.class);
    
    private final LogFormatter logFormatter;
    
    public LogMethodProcessor(LogFormatter logFormatter) {
        this.logFormatter = logFormatter;
    }
    
    /**
     * 处理方法执行的完整流程
     */
    public Object processMethodExecution(ProceedingJoinPoint joinPoint,
                                       Method method,
                                       Object[] args,
                                       LogConfig config) throws Throwable {
        
        String methodName = getMethodFullName(method);
        long startTime = System.currentTimeMillis();
        
        // 创建日志上下文
        LogContext logContext = createLogContext(method, args, config);
        
        try {
            // 记录方法开始日志
            logMethodStart(logContext, config);
            
            // 执行目标方法
            Object result = joinPoint.proceed();
            
            // 计算执行时间
            long executionTime = System.currentTimeMillis() - startTime;
            logContext.setExecutionTime(executionTime);
            logContext.setResult(result);
            
            // 记录方法成功日志
            logMethodSuccess(logContext, config);
            
            return result;
            
        } catch (Throwable throwable) {
            // 计算执行时间
            long executionTime = System.currentTimeMillis() - startTime;
            logContext.setExecutionTime(executionTime);
            logContext.setThrowable(throwable);
            
            // 记录方法异常日志
            logMethodError(logContext, config);
            
            // 重新抛出异常
            throw throwable;
        }
    }
    
    /**
     * 创建日志上下文
     */
    private LogContext createLogContext(Method method, Object[] args, LogConfig config) {
        LogContext context = new LogContext();
        
        // 基础信息
        context.setMethodName(getMethodFullName(method));
        context.setClassName(method.getDeclaringClass().getSimpleName());
        context.setStartTime(System.currentTimeMillis());
        
        // 处理方法参数
        if (config.getLogArgs() && args != null && args.length > 0) {
            Object[] processedArgs = processMethodArgs(args, config);
            context.setArgs(processedArgs);
        }
        
        // 整合请求上下文
        context.setTraceContext(ThreadLocalTraceHolder.getCurrentTrace());
        
        return context;
    }
    
    /**
     * 处理方法参数
     */
    private Object[] processMethodArgs(Object[] args, LogConfig config) {
        if (args == null || args.length == 0) {
            return new Object[0];
        }
        
        Object[] processedArgs = new Object[args.length];
        Set<Integer> excludeIndexes = getExcludeIndexes(config);
        
        for (int i = 0; i < args.length; i++) {
            if (excludeIndexes.contains(i)) {
                processedArgs[i] = "***EXCLUDED***";
            } else {
                processedArgs[i] = processArgument(args[i], config);
            }
        }
        
        return processedArgs;
    }
    
    /**
     * 处理单个参数
     */
    private Object processArgument(Object arg, LogConfig config) {
        if (arg == null) {
            return null;
        }
        
        try {
            // 对于简单类型,直接返回
            if (isSimpleType(arg)) {
                return arg;
            }
            
            // 对于复杂对象,序列化为JSON并进行脱敏
            String jsonStr = JsonUtils.toJson(arg);
            return applySensitiveFieldsMasking(jsonStr, config);
            
        } catch (Exception e) {
            logger.warn("处理参数时发生异常: {}", e.getMessage());
            return arg.toString();
        }
    }
    
    /**
     * 记录方法开始日志
     */
    private void logMethodStart(LogContext context, LogConfig config) {
        if (!isLogEnabled(config.getLevel())) {
            return;
        }
        
        try {
            String message = logFormatter.formatStartMessage(context, config);
            writeLog(config.getLevel(), message);
        } catch (Exception e) {
            logger.warn("记录方法开始日志失败: {}", e.getMessage());
        }
    }
    
    /**
     * 记录方法成功日志
     */
    private void logMethodSuccess(LogContext context, LogConfig config) {
        if (!isLogEnabled(config.getLevel())) {
            return;
        }
        
        try {
            String message = logFormatter.formatSuccessMessage(context, config);
            writeLog(config.getLevel(), message);
        } catch (Exception e) {
            logger.warn("记录方法成功日志失败: {}", e.getMessage());
        }
    }
    
    /**
     * 记录方法异常日志
     */
    private void logMethodError(LogContext context, LogConfig config) {
        // 异常日志使用ERROR级别
        LogLevel logLevel = config.getLogException() ? LogLevel.ERROR : config.getLevel();
        
        if (!isLogEnabled(logLevel)) {
            return;
        }
        
        try {
            String message = logFormatter.formatErrorMessage(context, config);
            writeLog(logLevel, message, context.getThrowable());
        } catch (Exception e) {
            logger.warn("记录方法异常日志失败: {}", e.getMessage());
        }
    }
    
    // ========== 工具方法 ==========
    
    /**
     * 获取方法全名
     */
    private String getMethodFullName(Method method) {
        return method.getDeclaringClass().getSimpleName() + "." + method.getName();
    }
    
    /**
     * 获取排除的参数索引
     */
    private Set<Integer> getExcludeIndexes(LogConfig config) {
        Set<Integer> excludeIndexes = new HashSet<>();
        if (config.getExcludeArgs() != null) {
            for (int index : config.getExcludeArgs()) {
                excludeIndexes.add(index);
            }
        }
        return excludeIndexes;
    }
    
    /**
     * 判断是否为简单类型
     */
    private boolean isSimpleType(Object obj) {
        return obj instanceof String ||
               obj instanceof Number ||
               obj instanceof Boolean ||
               obj instanceof Character ||
               obj.getClass().isPrimitive();
    }
    
    /**
     * 应用敏感字段脱敏
     */
    private String applySensitiveFieldsMasking(String jsonStr, LogConfig config) {
        if (config.getSensitiveFields() == null || config.getSensitiveFields().length == 0) {
            return jsonStr;
        }
        
        String maskedJson = jsonStr;
        for (String sensitiveField : config.getSensitiveFields()) {
            maskedJson = maskSensitiveField(maskedJson, sensitiveField);
        }
        
        return maskedJson;
    }
    
    /**
     * 脱敏单个敏感字段
     */
    private String maskSensitiveField(String jsonStr, String fieldName) {
        // 使用正则表达式匹配并替换敏感字段值
        String pattern = "\"" + fieldName + "\"\\s*:\\s*\"[^\"]*\"";
        String replacement = "\"" + fieldName + "\":\"******\"";
        return jsonStr.replaceAll(pattern, replacement);
    }
    
    /**
     * 检查日志级别是否启用
     */
    private boolean isLogEnabled(LogLevel level) {
        switch (level) {
            case TRACE: return logger.isTraceEnabled();
            case DEBUG: return logger.isDebugEnabled();
            case INFO: return logger.isInfoEnabled();
            case WARN: return logger.isWarnEnabled();
            case ERROR: return logger.isErrorEnabled();
            default: return false;
        }
    }
    
    /**
     * 写入日志
     */
    private void writeLog(LogLevel level, String message) {
        writeLog(level, message, null);
    }
    
    /**
     * 写入日志(带异常)
     */
    private void writeLog(LogLevel level, String message, Throwable throwable) {
        switch (level) {
            case TRACE:
                if (throwable != null) {
                    logger.trace(message, throwable);
                } else {
                    logger.trace(message);
                }
                break;
            case DEBUG:
                if (throwable != null) {
                    logger.debug(message, throwable);
                } else {
                    logger.debug(message);
                }
                break;
            case INFO:
                if (throwable != null) {
                    logger.info(message, throwable);
                } else {
                    logger.info(message);
                }
                break;
            case WARN:
                if (throwable != null) {
                    logger.warn(message, throwable);
                } else {
                    logger.warn(message);
                }
                break;
            case ERROR:
                if (throwable != null) {
                    logger.error(message, throwable);
                } else {
                    logger.error(message);
                }
                break;
        }
    }
}

JsonUtils - JSON序列化工具

java 复制代码
package com.simpleflow.log.util;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * JSON工具类
 * 
 * 提供对象序列化和反序列化功能,专门优化用于日志记录场景
 */
public class JsonUtils {
    
    private static final Logger logger = LoggerFactory.getLogger(JsonUtils.class);
    
    private static final ObjectMapper OBJECT_MAPPER;
    
    static {
        OBJECT_MAPPER = new ObjectMapper();
        
        // 配置序列化选项
        OBJECT_MAPPER.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        OBJECT_MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        
        // 注册Java 8时间模块
        OBJECT_MAPPER.registerModule(new JavaTimeModule());
    }
    
    /**
     * 对象转JSON字符串
     */
    public static String toJson(Object obj) {
        if (obj == null) {
            return "null";
        }
        
        try {
            return OBJECT_MAPPER.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            logger.warn("对象序列化为JSON失败: {}", e.getMessage());
            return obj.toString();
        } catch (Exception e) {
            logger.warn("对象序列化时发生未知异常: {}", e.getMessage());
            return "\"<序列化失败>\"";
        }
    }
    
    /**
     * 对象转格式化的JSON字符串(用于调试)
     */
    public static String toPrettyJson(Object obj) {
        if (obj == null) {
            return "null";
        }
        
        try {
            return OBJECT_MAPPER.writerWithDefaultPrettyPrinter()
                               .writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            logger.warn("对象序列化为格式化JSON失败: {}", e.getMessage());
            return toJson(obj);
        }
    }
    
    /**
     * JSON字符串转对象
     */
    public static <T> T fromJson(String json, Class<T> clazz) {
        if (json == null || json.trim().isEmpty()) {
            return null;
        }
        
        try {
            return OBJECT_MAPPER.readValue(json, clazz);
        } catch (JsonProcessingException e) {
            logger.warn("JSON反序列化失败: {}", e.getMessage());
            return null;
        }
    }
    
    /**
     * 安全的JSON序列化,限制输出长度
     */
    public static String toJsonSafely(Object obj, int maxLength) {
        String json = toJson(obj);
        if (json.length() > maxLength) {
            return json.substring(0, maxLength - 3) + "...";
        }
        return json;
    }
}

LogContext - 日志上下文

java 复制代码
package com.simpleflow.log.context;

import com.simpleflow.log.context.TraceContext;

/**
 * 日志上下文
 * 
 * 封装单次方法调用的所有日志相关信息
 */
public class LogContext {
    
    /**
     * 方法名(包含类名)
     */
    private String methodName;
    
    /**
     * 类名
     */
    private String className;
    
    /**
     * 方法参数
     */
    private Object[] args;
    
    /**
     * 方法返回值
     */
    private Object result;
    
    /**
     * 异常信息
     */
    private Throwable throwable;
    
    /**
     * 开始时间
     */
    private long startTime;
    
    /**
     * 执行时间(毫秒)
     */
    private long executionTime;
    
    /**
     * 请求追踪上下文
     */
    private TraceContext traceContext;
    
    // ========== 构造方法 ==========
    
    public LogContext() {
    }
    
    public LogContext(String methodName, Object[] args) {
        this.methodName = methodName;
        this.args = args;
        this.startTime = System.currentTimeMillis();
    }
    
    // ========== 便利方法 ==========
    
    /**
     * 是否有参数
     */
    public boolean hasArgs() {
        return args != null && args.length > 0;
    }
    
    /**
     * 是否有返回值
     */
    public boolean hasResult() {
        return result != null;
    }
    
    /**
     * 是否有异常
     */
    public boolean hasException() {
        return throwable != null;
    }
    
    /**
     * 获取请求ID
     */
    public String getRequestId() {
        return traceContext != null ? traceContext.getRequestId() : null;
    }
    
    /**
     * 获取用户ID
     */
    public String getUserId() {
        return traceContext != null ? traceContext.getUserId() : null;
    }
    
    // ========== Getters and Setters ==========
    
    public String getMethodName() {
        return methodName;
    }
    
    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }
    
    public String getClassName() {
        return className;
    }
    
    public void setClassName(String className) {
        this.className = className;
    }
    
    public Object[] getArgs() {
        return args;
    }
    
    public void setArgs(Object[] args) {
        this.args = args;
    }
    
    public Object getResult() {
        return result;
    }
    
    public void setResult(Object result) {
        this.result = result;
    }
    
    public Throwable getThrowable() {
        return throwable;
    }
    
    public void setThrowable(Throwable throwable) {
        this.throwable = throwable;
    }
    
    public long getStartTime() {
        return startTime;
    }
    
    public void setStartTime(long startTime) {
        this.startTime = startTime;
    }
    
    public long getExecutionTime() {
        return executionTime;
    }
    
    public void setExecutionTime(long executionTime) {
        this.executionTime = executionTime;
    }
    
    public TraceContext getTraceContext() {
        return traceContext;
    }
    
    public void setTraceContext(TraceContext traceContext) {
        this.traceContext = traceContext;
    }
    
    @Override
    public String toString() {
        return "LogContext{" +
                "methodName='" + methodName + '\'' +
                ", className='" + className + '\'' +
                ", executionTime=" + executionTime +
                ", hasException=" + hasException() +
                '}';
    }
}

敏感信息脱敏策略

1. 字段级脱敏

java 复制代码
/**
 * 高级敏感信息脱敏处理器
 */
@Component
public class SensitiveDataMasker {
    
    private static final Logger logger = LoggerFactory.getLogger(SensitiveDataMasker.class);
    
    // 预定义的敏感字段模式
    private static final Set<String> DEFAULT_SENSITIVE_PATTERNS = Set.of(
        "password", "pwd", "secret", "token", "key",
        "idCard", "phone", "mobile", "email", "address"
    );
    
    /**
     * 智能脱敏处理
     */
    public String maskSensitiveData(String jsonStr, String[] configuredFields) {
        if (jsonStr == null || jsonStr.isEmpty()) {
            return jsonStr;
        }
        
        Set<String> sensitiveFields = new HashSet<>();
        
        // 添加配置的敏感字段
        if (configuredFields != null) {
            sensitiveFields.addAll(Arrays.asList(configuredFields));
        }
        
        // 添加默认敏感字段模式
        sensitiveFields.addAll(DEFAULT_SENSITIVE_PATTERNS);
        
        String maskedJson = jsonStr;
        for (String field : sensitiveFields) {
            maskedJson = maskField(maskedJson, field);
        }
        
        return maskedJson;
    }
    
    /**
     * 脱敏单个字段
     */
    private String maskField(String jsonStr, String fieldName) {
        // 匹配不同格式的JSON字段
        String[] patterns = {
            // 字符串值: "field":"value"
            "\"" + fieldName + "\"\\s*:\\s*\"[^\"]*\"",
            // 数字值: "field":123
            "\"" + fieldName + "\"\\s*:\\s*\\d+",
            // 布尔值: "field":true
            "\"" + fieldName + "\"\\s*:\\s*(true|false)"
        };
        
        String result = jsonStr;
        for (String pattern : patterns) {
            String replacement = "\"" + fieldName + "\":\"******\"";
            result = result.replaceAll(pattern, replacement);
        }
        
        return result;
    }
    
    /**
     * 根据字段类型进行不同的脱敏策略
     */
    public String maskByType(String value, String fieldName) {
        if (value == null || value.isEmpty()) {
            return value;
        }
        
        // 手机号脱敏:保留前3位和后4位
        if (fieldName.toLowerCase().contains("phone") || 
            fieldName.toLowerCase().contains("mobile")) {
            return maskPhone(value);
        }
        
        // 身份证脱敏:保留前6位和后4位
        if (fieldName.toLowerCase().contains("idcard")) {
            return maskIdCard(value);
        }
        
        // 邮箱脱敏:保留@前的第一个字符和@后的域名
        if (fieldName.toLowerCase().contains("email")) {
            return maskEmail(value);
        }
        
        // 默认脱敏:全部替换为星号
        return "******";
    }
    
    private String maskPhone(String phone) {
        if (phone.length() <= 7) {
            return "******";
        }
        return phone.substring(0, 3) + "****" + phone.substring(phone.length() - 4);
    }
    
    private String maskIdCard(String idCard) {
        if (idCard.length() <= 10) {
            return "******";
        }
        return idCard.substring(0, 6) + "********" + idCard.substring(idCard.length() - 4);
    }
    
    private String maskEmail(String email) {
        int atIndex = email.indexOf("@");
        if (atIndex <= 0) {
            return "******";
        }
        return email.charAt(0) + "****" + email.substring(atIndex);
    }
}

2. 脱敏效果示例

java 复制代码
// 原始数据
{
  "username": "zhangsan",
  "password": "123456",
  "phone": "13812345678",
  "email": "zhangsan@example.com",
  "idCard": "110101199001011234"
}

// 脱敏后
{
  "username": "zhangsan",
  "password": "******",
  "phone": "138****5678",
  "email": "z****@example.com",
  "idCard": "110101********1234"
}

性能优化技巧

1. 延迟序列化

java 复制代码
/**
 * 延迟序列化参数处理器
 */
public class LazyArgProcessor {
    
    /**
     * 创建延迟序列化的参数包装器
     */
    public Object[] createLazyArgs(Object[] args, LogConfig config) {
        if (!config.getLogArgs() || args == null) {
            return null;
        }
        
        Object[] lazyArgs = new Object[args.length];
        for (int i = 0; i < args.length; i++) {
            lazyArgs[i] = new LazySerializableArg(args[i], config);
        }
        
        return lazyArgs;
    }
    
    /**
     * 延迟序列化的参数包装器
     */
    private static class LazySerializableArg {
        private final Object arg;
        private final LogConfig config;
        private String serialized;
        
        public LazySerializableArg(Object arg, LogConfig config) {
            this.arg = arg;
            this.config = config;
        }
        
        @Override
        public String toString() {
            if (serialized == null) {
                // 只有在真正需要时才进行序列化
                if (arg == null) {
                    serialized = "null";
                } else if (isSimpleType(arg)) {
                    serialized = arg.toString();
                } else {
                    serialized = JsonUtils.toJsonSafely(arg, 1000);
                    // 应用脱敏
                    serialized = applySensitiveFieldsMasking(serialized, config);
                }
            }
            return serialized;
        }
        
        private boolean isSimpleType(Object obj) {
            return obj instanceof String || obj instanceof Number || 
                   obj instanceof Boolean || obj instanceof Character;
        }
    }
}

2. 异步日志处理

java 复制代码
/**
 * 异步日志处理器
 */
@Component
public class AsyncLogProcessor {
    
    private final ExecutorService logExecutor;
    private final BlockingQueue<LogTask> logQueue;
    
    public AsyncLogProcessor() {
        this.logQueue = new LinkedBlockingQueue<>(1000);
        this.logExecutor = Executors.newFixedThreadPool(2, 
            new ThreadFactoryBuilder()
                .setNameFormat("async-log-%d")
                .setDaemon(true)
                .build());
        
        // 启动日志处理线程
        startLogProcessor();
    }
    
    /**
     * 异步处理日志
     */
    public void processAsync(LogContext context, LogConfig config, LogType logType) {
        LogTask task = new LogTask(context, config, logType);
        
        if (!logQueue.offer(task)) {
            // 队列满了,同步处理
            processLogSync(task);
        }
    }
    
    private void startLogProcessor() {
        logExecutor.submit(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    LogTask task = logQueue.take();
                    processLogSync(task);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                } catch (Exception e) {
                    logger.error("异步日志处理异常", e);
                }
            }
        });
    }
    
    private void processLogSync(LogTask task) {
        // 具体的日志处理逻辑
    }
    
    enum LogType {
        START, SUCCESS, ERROR
    }
    
    static class LogTask {
        final LogContext context;
        final LogConfig config;
        final LogType logType;
        
        LogTask(LogContext context, LogConfig config, LogType logType) {
            this.context = context;
            this.config = config;
            this.logType = logType;
        }
    }
}

本章小结

✅ 完成的任务

  1. 核心处理器:实现了LogMethodProcessor的完整逻辑
  2. JSON序列化:创建了优化的JsonUtils工具类
  3. 敏感信息脱敏:实现了智能的脱敏策略
  4. 日志上下文:设计了LogContext承载调用信息
  5. 性能优化:实现了延迟序列化和异步处理

🎯 学习要点

  • 数据转换的安全性和性能考虑
  • 敏感信息脱敏的多种策略
  • 异步处理在日志场景中的应用
  • 上下文管理的重要性
  • 错误处理不能影响业务逻辑

💡 思考题

  1. 如何平衡日志详细程度和性能开销?
  2. 敏感信息脱敏还有哪些改进空间?
  3. 异步日志处理的风险和应对策略?

🚀 下章预告

下一章我们将实现分布式链路追踪系统,学习如何在分布式环境下跟踪请求的完整生命周期,包括TraceContext设计、ThreadLocal管理、跨线程传递等核心技术。


💡 设计原则 : 日志处理器是框架的核心大脑,需要在功能完整性、性能效率、数据安全之间找到最佳平衡点。

相关推荐
考虑考虑12 小时前
Jpa使用union all
java·spring boot·后端
用户37215742613512 小时前
Java 实现 Excel 与 TXT 文本高效互转
java
浮游本尊13 小时前
Java学习第22天 - 云原生与容器化
java
渣哥15 小时前
原来 Java 里线程安全集合有这么多种
java
间彧15 小时前
Spring Boot集成Spring Security完整指南
java
间彧15 小时前
Spring Secutiy基本原理及工作流程
java
Java水解16 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆19 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学19 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole19 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端