You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Java切面中实现条件化深浅日志记录

基于Spring AOP实现可配置深浅模式的日志切面

需求说明

  • 浅日志模式:记录方法参数和返回值的第一层字段,集合/Map仅打印类型(如ArrayList),不展示内容
  • 深日志模式:记录方法参数和返回值的嵌套对象(到指定深度),集合/Map展示元素的表层内容(如[valueA, valueB]),但不深入嵌套集合的内部

当前问题:直接打印对象会导致日志过于冗长,包含嵌套集合的全部内容,无法按配置的深浅模式输出。

现有代码实现

日志注解Log.java

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
    Depth depth() default Depth.SHALLOW;
}

日志深度枚举Depth.java

public enum Depth {
    SHALLOW(1),
    DEEP(3);

    private final int level;

    Depth(int level) {
        this.level = level;
    }

    public int getLevel() {
        return level;
    }
}

日志切面LoggingAspect.java

@Aspect
@Component
public class LoggingAspect {

    @SuppressWarnings({"PMD.DataflowAnomalyAnalysis","PMD.AvoidCatchingThrowable"})
    @Around("@annotation(log) && execution(* *(..))")
    public Object logMethod(ProceedingJoinPoint joinPoint, Log log) throws Throwable {
        CustomLogger logger = CustomLogger.getLogger(joinPoint.getSignature().getDeclaringType());
        String methodName = joinPoint.getSignature().getName();
        Class<?> returnType = ((MethodSignature) joinPoint.getSignature()).getReturnType();
        Map<String, Object> parameters = obtainParameters(joinPoint);

        int depth = log.depth().getLevel();
        // 待实现:按深度序列化参数
        logger.info("{} started with parameters: {}", methodName, parameters);

        Object proceed;
        try {
            proceed = joinPoint.proceed();
        } catch (Throwable e) {
            logger.info("{} failed with exception: {}, message: {}", methodName, e.toString(), e.getMessage());
            throw e;
        }
        if (returnType == Void.TYPE) {
            logger.info("{} finished without return value.", methodName);
        } else {
            // 待实现:按深度序列化返回值
            logger.info("{} finished with return value: {}", methodName, proceed);
        }
        return proceed;
    }

    private Map<String, Object> obtainParameters(ProceedingJoinPoint joinPoint) {
        Map<String, Object> parameters = new HashMap<>();
        String[] parameterNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
        Object[] parameterValues = joinPoint.getArgs();
        for (int i=0; i < parameterNames.length && i < parameterValues.length; i++) {
            parameters.put(parameterNames[i], parameterValues[i]);
        }
        return parameters;
    }
}

依赖配置

// aop 
implementation 'org.aspectj:aspectjrt:1.9.8' 
implementation 'org.aspectj:aspectjweaver:1.9.8' 
// Spring Boot 
plugins { id 'org.springframework.boot' version '2.2.9.RELEASE' }

解决方案实现

核心思路是自定义对象序列化工具,根据指定深度控制对象的序列化范围,同时特殊处理集合/Map类型:

1. 自定义序列化工具类LogObjectSerializer.java

这个类负责根据深度参数,将对象序列化为符合要求的字符串:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

import java.util.Collection;
import java.util.Map;

public class LogObjectSerializer {
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    static {
        // 关闭默认空值序列化,减少日志冗余
        OBJECT_MAPPER.disable(SerializationFeature.WRITE_NULL_MAP_VALUES);
        OBJECT_MAPPER.disable(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS);
    }

    public static String serialize(Object obj, int maxDepth) {
        if (obj == null) {
            return "null";
        }
        // 处理集合类型:浅模式打印类型,深模式打印元素表层内容
        if (obj instanceof Collection) {
            Collection<?> collection = (Collection<?>) obj;
            if (maxDepth <= 1) {
                return obj.getClass().getSimpleName();
            } else {
                return collection.stream()
                        .map(item -> serialize(item, maxDepth - 1))
                        .toList().toString();
            }
        }
        // 处理Map类型:逻辑同集合
        if (obj instanceof Map) {
            Map<?, ?> map = (Map<?, ?>) obj;
            if (maxDepth <= 1) {
                return obj.getClass().getSimpleName();
            } else {
                return map.entrySet().stream()
                        .map(entry -> entry.getKey() + "=" + serialize(entry.getValue(), maxDepth - 1))
                        .toList().toString().replace("[", "{").replace("]", "}");
            }
        }
        // 基本类型/字符串直接返回toString
        if (obj.getClass().isPrimitive() || obj instanceof String) {
            return obj.toString();
        }
        // 自定义对象:浅模式仅打印类名,深模式递归序列化字段
        if (maxDepth <= 1) {
            return obj.getClass().getSimpleName() + "(...)";
        }
        try {
            String json = OBJECT_MAPPER.writeValueAsString(obj);
            // 转换为类似Java对象toString的格式
            return json.replace("{", obj.getClass().getSimpleName() + "(")
                    .replace("}", ")")
                    .replace("\"", "");
        } catch (JsonProcessingException e) {
            // 序列化失败时返回默认toString
            return obj.toString();
        }
    }
}

2. 修改日志切面,使用序列化工具处理参数和返回值

更新LoggingAspect.java中的日志打印逻辑:

@Aspect
@Component
public class LoggingAspect {

    @SuppressWarnings({"PMD.DataflowAnomalyAnalysis","PMD.AvoidCatchingThrowable"})
    @Around("@annotation(log) && execution(* *(..))")
    public Object logMethod(ProceedingJoinPoint joinPoint, Log log) throws Throwable {
        CustomLogger logger = CustomLogger.getLogger(joinPoint.getSignature().getDeclaringType());
        String methodName = joinPoint.getSignature().getName();
        Class<?> returnType = ((MethodSignature) joinPoint.getSignature()).getReturnType();
        Map<String, Object> parameters = obtainParameters(joinPoint);

        int depth = log.depth().getLevel();
        // 序列化参数
        String serializedParams = parameters.entrySet().stream()
                .map(entry -> entry.getKey() + "=" + LogObjectSerializer.serialize(entry.getValue(), depth))
                .toList().toString().replace("[", "{").replace("]", "}");
        logger.info("{} started with parameters: {}", methodName, serializedParams);

        Object proceed;
        try {
            proceed = joinPoint.proceed();
        } catch (Throwable e) {
            logger.info("{} failed with exception: {}, message: {}", methodName, e.toString(), e.getMessage());
            throw e;
        }
        if (returnType == Void.TYPE) {
            logger.info("{} finished without return value.", methodName);
        } else {
            // 序列化返回值
            String serializedResult = LogObjectSerializer.serialize(proceed, depth);
            logger.info("{} finished with return value: {}", methodName, serializedResult);
        }
        return proceed;
    }

    private Map<String, Object> obtainParameters(ProceedingJoinPoint joinPoint) {
        Map<String, Object> parameters = new HashMap<>();
        String[] parameterNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
        Object[] parameterValues = joinPoint.getArgs();
        for (int i=0; i < parameterNames.length && i < parameterValues.length; i++) {
            parameters.put(parameterNames[i], parameterValues[i]);
        }
        return parameters;
    }
}

3. 补充Jackson依赖(若项目未引入)

在build.gradle中添加Jackson依赖:

implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.4'

效果验证

假设存在测试类:

public class SomeClass {
    private String field1;
    private List<String> field2;
    // 构造器、getter、setter省略
}
  • 浅模式(@Log(depth = Depth.SHALLOW)):
    日志输出:methodName started with parameters: { param1=SomeClass(field1=value1, field2=ArrayList) }
  • 深模式(@Log(depth = Depth.DEEP)):
    日志输出:methodName started with parameters: { param1=SomeClass(field1=value1, field2=[valueA, valueB, valueC]) }

注意事项

  • 可根据需求调整LogObjectSerializer的序列化格式,比如保留JSON风格或自定义toString格式
  • 若需排除敏感字段,可在序列化工具中添加字段过滤逻辑
  • 项目中若使用Gson等其他序列化工具,可替换为对应实现

内容的提问来源于stack exchange,提问作者elgunssh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 06:50:54