如何在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
相关产品推荐
相关产品推荐

