第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管理、跨线程传递等核心技术。


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

相关推荐
猿java10 分钟前
Java String.replace()原理,你真的了解吗?
java·面试·架构
SimonKing19 分钟前
优雅地实现ChatGPT式的打字机效果:Spring流式响应
java·后端·程序员
long31621 分钟前
状态设计模式
java·学习·程序人生·设计模式·状态模式·state-pattern
一根会骑马的Banana34 分钟前
关于DTO、DO、BO、VO
java
cur1es37 分钟前
数据结构Java--8
java·数据结构·算法·散列表
简单点了2 小时前
SM4加密算法
java·开发语言
用户3721574261352 小时前
Java 实现HTML转Word:从HTML文件与字符串到可编辑Word文档
java
yvya_2 小时前
Mybatis总结
java·spring·mybatis
姜太小白2 小时前
【VSCode】VSCode为Java C/S项目添加图形用户界面
java·c语言·vscode
一路向北North2 小时前
java将doc文件转pdf
java·pdf