通过 Spring AOP 从日志记录中排除一些 fields/parameters

exclusion of some fields/parameters from logging via Spring AOP

在我的 spring 项目中,我有这样一个方面 class 用于日志记录

    @Aspect
@Component
public class BaseLoggingAspect {

    private static final Logger logger = LoggerFactory.getLogger(BaseLoggingAspect.class);

    @Target({ ElementType.FIELD, ElementType.PARAMETER })
    public @interface NonLoggingField {
        
    }
    
    @Pointcut("execution(public * *(..))")
    private void allPublicMethods() {

    }

    @Pointcut("within(img.imaginary.service.*)")
    private void inServices() {

    }

    @Pointcut("within(img.imaginary.dao.*)")
    private void inDao() {

    }

    @Before("allPublicMethods() && inServices() || inDao()")
    public void logBeforeCall(JoinPoint joinPoint) {
        if (logger.isDebugEnabled()) {
            logger.debug("begin method {} in {} class with arguments: {}", joinPoint.getSignature().getName(),
                    joinPoint.getTarget().getClass().getSimpleName(), joinPoint.getArgs());
          
        }
    }
}

这方面简单的捕获service和dao层的所有public方法,并在开始执行时输出到log中方法名,class,以及方法参数值的大小

在这方面,我创建了一个 NonLoggingField 注释,我想将其应用到那些可以传递给这些记录方法的参数的对象的 classes 的某些字段,例如:

public class User {
 @NonLoggingField
 public String userEmail;
 public name;
 
 public User(String userEmail, String name) {
    this.userEmail = userEmail;
    this.name= name;
 }
 
 public String tiString() {
    return String.format("user name: %s and his email: %s", name, userEmail);
 }

}

事实是,此类对象将通过其 toString 方法写入日志,但有必要使用 notLoggingField 注释以某种方式使电子邮件不进入日志,同时我脑子里有一些想法要做通过反射,但不清楚如何在没有使用反射的复杂代码的情况下做到这一点,特别是考虑到对象内部可能有其他类型的对象,这些对象可能具有带注释的相同字段或具有此类字段的对象的集合。也许 AspectJ 库可以提供帮助,但我在其中找不到这样的机制。请帮我想出点什么

在运行时,方法参数只是一个值。此时 JVM 不知道调用者是使用常量、文字、字段还是其他方法调用的结果来调用该方法。那种信息,你只能在源代码中看到。在字节代码中,确定参数值(或对相应对象的引用)所需的任何取消引用操作或计算都在 调用方法之前完成。所以跟字段注解没有联系。

注释方法参数是您的替代方案吗?

如果您的要求非常具体,例如拦截来自 toString 方法和 return 虚拟值的字段访问,如果该字段被注释,那将是可能的。但这不会是 fool-proof。例如,假设 toString 调用 getter 方法而不是直接访问该字段,或者 toString 以外的方法记录该字段。您并不总是希望在读取访问时验证字段值,因为应用程序的其他部分可能依赖于它的正常工作。并非每次 toString 调用都是为了记录某些内容。

我认为你应该用另一种方式解决问题,例如通过为您使用的日志记录工具应用过滤规则。或者如果你真的想在应用程序级别解决它,你可以创建一个像

这样的接口
public interface PrivacyLogger {
  String toStringSensitive();
}

并使每个包含敏感信息的 class 实现该接口。然后,日志记录方面可以为每个打印的对象确定它是否是 instanceof toStringSensitive()。如果是这样,它将记录 toStringSensitive() 而不是 toString() 的结果,即在最简单的情况下类似于

Object toBeLogged = whatever();
logger.log(
  toBeLogged instanceof PrivacyLogger
    ? ((PrivacyLogger) toBeLogged).toStringSensitive()
    : toBeLogged
);

当然,您需要遍历 getArgs() 并为每个对象确定正确的日志字符串。可能您想为整个参数数组编写一个实用程序方法。

此外,在复杂的 class 中,toStringSensitive() 实现当然也应该检查它自己的字段是否是 PrivacyLogger 实例,在这种情况下折叠它们的响应值 toStringSensitive() 方法到它自己,以便它递归工作。

很抱歉,我没有更好的消息告诉您,但隐私也是需要从头开始构建到应用程序中的东西。没有简单的 fool-proof 方法可以从一个小方面做到这一点。切面可以利用现有的应用基础设施,避免分散和纠结,但它不能自己决定什么需要禁止记录,什么不可以。