【问题标题】:java (log4j) logging filter by object typejava(log4j)按对象类型记录过滤器
【发布时间】:2018-01-05 00:47:46
【问题描述】:

我目前有一个使用 log4j 实现的日志记录语句:

log.info("Failed to create message for {}", CustomerData);

这将在CustomerData 中记录一些敏感数据。

有没有办法阻止记录CustomerData 的任何实例?也许在 log4j 配置中或通过自定义过滤器?

如果不能使用 log4j,那么其他日志框架呢?

【问题讨论】:

  • 不清楚你在问什么。
  • CustomerData.toString() 方法是什么样的?
  • @stdunbar CustomerData.toString 将被记录。例如,“客户姓名:Jon Smith,客户性别:男性,客户年龄:25,客户余额:100 美元”。如您所见,这包含敏感数据,我想知道是否有任何方法可以设置过滤器来阻止记录CustomerData 类型的任何对象的可能性。
  • @RomanC 您有什么澄清或具体疑问?我认为这是一个简单而直接的问题。我在询问排除或阻止某种类型/类CustomerData 的任何对象的可能性以及尽可能做到这一点的方法。
  • @JesseZhuang 我相信这个问题有点混乱。当您键入 log.info("abcdefgh {}", someStringHere) 时,它将记录字符串 "abcdefgh " + 无论 someStringHere 的值是什么。因此,在您的情况下,无论 CustomerData.toString() 的结果是什么,都将在记录之前连接到前一个字符串的末尾。如果您有 CustomerData 的敏感数据,为什么要记录它?

标签: java logging filter log4j


【解决方案1】:

Log4j2 提供了多种方法来实现这一点:

  • 过滤器
  • 重写日志事件

过滤器

Log4j2 允许您在特定记录器、特定附加程序或全局(因此过滤器适用于所有日志事件)上配置 filters。过滤器为您提供的是强制接受日志事件或强制拒绝日志事件或成为“中性”的能力。在您的情况下,您可能希望拒绝记录包含敏感数据的事件。

您可以创建一个custom Filter implementation(请参阅plugin docs 了解如何安装您的自定义过滤器),或者您可以使用一些内置过滤器。内置的 RegexFilterScriptFilter 应该足以满足您的目的。

正则表达式过滤器示例

假设这是一个包含敏感数据的类:

public class Customer {
    public String name;
    public String password;

    @Override
    public String toString() {
        return "Customer[name=" + name + ", password=" + password + "]";
    }
}

您的应用程序日志如下所示:

public class CustomerLoggingApp {
    public static void main(String[] args) {
        Logger log = LogManager.getLogger();

        Customer customer = new Customer();
        customer.name = "Jesse Zhuang";
        customer.password = "secret123";

        log.info("This is sensitive and should not be logged: {}", customer);
        log.info("But this message should be logged.");
    }
}

您可以配置一个正则表达式过滤器来查看已格式化(或未格式化)的消息,并拒绝任何包含“Customer”后跟“, password=”的日志消息:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
  <Appenders>
    <Console name="Console" target="SYSTEM_OUT">
      <RegexFilter regex=".*Customer.*, password=.*" onMatch="DENY" onMismatch="NEUTRAL"/>
      <PatternLayout>
        <pattern>%d %level %c %m%n</pattern>
      </PatternLayout>
    </Console>
  </Appenders>
  <Loggers>
    <Root level="debug">
      <AppenderRef ref="Console" />
    </Root>
  </Loggers>
</Configuration>

脚本过滤器示例

另一个非常灵活的过滤器是ScriptFilter。下面的示例使用 Groovy,但您也可以使用 JavaScript 或您的 Java 安装中可用的任何其他脚本语言。

鉴于上述应用程序类,以下log4j2.xml 配置将过滤掉包含完全限定类名称为Customer 的任何参数的任何日志事件:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
  <ScriptFilter onMatch="DENY" onMisMatch="NEUTRAL">
    <Script name="DropSensitiveObjects" language="groovy"><![CDATA[
                parameters.any { p ->
                    // DENY log messages with Customer parameters
                    p.class.name == "Customer"
                }
              ]]>
    </Script>
  </ScriptFilter>
  <Appenders>
    <Console name="Console" target="SYSTEM_OUT">
      <PatternLayout>
        <pattern>%d %level %c %m%n</pattern>
      </PatternLayout>
    </Console>
  </Appenders>
  <Loggers>
    <Root level="debug">
      <AppenderRef ref="Console" />
    </Root>
  </Loggers>
</Configuration>

重写日志事件

另一个有趣的选择是重写日志事件,这样消息就不会被完全过滤掉,而是您只需屏蔽敏感数据。例如,您将日志中的密码字符串替换为“***”。

为此,您需要创建一个RewriteAppender。来自手册:

RewriteAppender 允许在 LogEvent 被操作之前对其进行操作 由另一个 Appender 处理。这可以用来屏蔽敏感 密码等信息或将信息注入每个 事件。

示例重写策略:

package com.jesse.zhuang;

import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.ObjectMessage;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.logging.log4j.message.ReusableMessage;

@Plugin(name = "MaskSensitiveDataPolicy", category = Core.CATEGORY_NAME, 
        elementType = "rewritePolicy", printObject = true)
public class MaskSensitiveDataPolicy implements RewritePolicy {

    private String[] sensitiveClasses;

    @PluginFactory
    public static MaskSensitiveDataPolicy createPolicy(
            @PluginElement("sensitive") final String[] sensitiveClasses) {
        return new MaskSensitiveDataPolicy(sensitiveClasses);
    }

    private MaskSensitiveDataPolicy(String[] sensitiveClasses) {
        super();
        this.sensitiveClasses = sensitiveClasses;
    }

    @Override
    public LogEvent rewrite(LogEvent event) {
        Message rewritten = rewriteIfSensitive(event.getMessage());
        if (rewritten != event.getMessage()) {
            return new Log4jLogEvent.Builder(event).setMessage(rewritten).build();
        }
        return event;
    }

    private Message rewriteIfSensitive(Message message) {
        // Make sure to switch off garbage-free logging
        // by setting system property `log4j2.enable.threadlocals` to `false`.
        // Otherwise you may get ReusableObjectMessage, ReusableParameterizedMessage
        // or MutableLogEvent messages here which may not be rewritable...
        if (message instanceof ObjectMessage) {
            return rewriteObjectMessage((ObjectMessage) message);
        }
        if (message instanceof ParameterizedMessage) {
            return rewriteParameterizedMessage((ParameterizedMessage) message);
        }
        return message;
    }

    private Message rewriteObjectMessage(ObjectMessage message) {
        if (isSensitive(message.getParameter())) {
            return new ObjectMessage(maskSensitive(message.getParameter()));
        }
        return message;
    }

    private Message rewriteParameterizedMessage(ParameterizedMessage message) {
        Object[] params = message.getParameters();
        boolean changed = rewriteSensitiveParameters(params);
        return changed ? new ParameterizedMessage(message.getFormat(), params) : message;
    }

    private boolean rewriteSensitiveParameters(Object[] params) {
        boolean changed = false;
        for (int i = 0; i < params.length; i++) {
            if (isSensitive(params[i])) {
                params[i] = maskSensitive(params[i]);
                changed = true;
            }
        }
        return changed;
    }

    private boolean isSensitive(Object parameter) {
        return parameter instanceof Customer;
    }

    private Object maskSensitive(Object parameter) {
        Customer result = new Customer();
        result.name = ((Customer) parameter).name;
        result.password = "***";
        return result;
    }
}

注意:在无垃圾模式(默认)下运行时,Log4j2 使用可重用对象来存储消息和日志事件。这些不是 适合重写。 (这在用户中没有很好地记录 手册。)如果你想使用 rewrite appender,你需要 通过设置系统属性部分关闭无垃圾日志记录 log4j2.enable.threadlocalsfalse

使用您的自定义 MaskSensitiveDataPolicy 重写策略配置您的重写附加程序。要让 Log4j2 知道您的插件,请在 Configurationpackages 属性中指定插件包的名称:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" packages="com.jesse.zhuang">
  <Appenders>
    <Console name="Console" target="SYSTEM_OUT">
      <PatternLayout>
        <pattern>%d %level %c %m%n</pattern>
      </PatternLayout>
    </Console>

    <Rewrite name="obfuscateSensitiveData">
      <AppenderRef ref="Console"/>
      <MaskSensitiveDataPolicy />
    </Rewrite>

  </Appenders>
  <Loggers>
    <Root level="debug">
      <AppenderRef ref="obfuscateSensitiveData"/>
    </Root>
  </Loggers>
</Configuration>

这将使上述示例程序产生以下输出。请注意,密码被屏蔽,但敏感对象的其他属性被保留:

2018-01-09 22:18:30,561 INFO CustomerLoggingApp This is sensitive and should not be logged: Customer[name=Jesse Zhuang, password=***]
2018-01-09 22:18:30,569 INFO CustomerLoggingApp But this message should be logged.

【讨论】:

  • 只是想知道,快速浏览一下 Log4j2 代码,在 Logger 中构造 logEvent 时似乎消息格式化已经完成,所以到达 appender 的 logEvent 应该已经是最终消息了?我想我可能错过了记录器逻辑中的某些内容
  • 在无垃圾模式(默认)下,可能是这样。您可能需要通过将系统属性 log4j2.enable.threadlocals 设置为 false 来部分关闭无垃圾日志记录。
【解决方案2】:

Log4j/SLF4j/whatever Logging 框架中没有(也可能永远不会)提供。

为了满足你的特定需求,最简单的方法是拥有自己的Logger装饰器。

它可以是 SLF4J 或 Log4j2 的自定义日志记录实现。或者只是 Logger 的某种工厂(例如 SLF4j 中的 LoggerFactory,或 Log4j2 中的 Logger.getLogger()

它可以在内部创建您的自定义 Logger 实现,该实现委托给真正的记录器,并且您在日志实现中进行额外检查。

例如(伪代码)

SensitiveDataCheckingLogger implements Logger {
    private Logger delegate;
    public SensitiveDataCheckingLogger(Logger delegate) {
        this.delegate = delegate;
    }

    @Override
    public void info (String message, Object... args) {
        if (delegate.infoEnabled()) {
            for (Object arg : args) {
                // or whatever way you want to check, e.g. by annotation 
                if (arg instanceof SenstiveData) {  
                    throw newOhNoSensitiveDataRuntimeException();
                }
            }

            delegate.info(message, args);
        }
    }
    // bunch of all other method implementations
}

public class MyLoggerFactory {
    Logger getLogger(Class<?> clazz) {
        return new SensitiveDataCheckingLogger(LoggerFactory.getLogger(clazz));
    }
}

所以你只需像以前一样使用它

Logger logger = MyLoggerFactory.getLogger(Foo.class);
...
logger.info("bablabla {}", sensitiveData);

但正如您所见,存在很多缺点,例如性能下降。


如果你正在使用 Logback(我相信你也可以为 Log4j2 做类似的事情),你可以实现自己的 Appender 或 Encoder 等。

当您登录 Logback 时,它会在内部创建一个日志事件,其中包含日志消息和参数。因此,无需在附加程序(或编码器等)中实际格式化日志消息,您只需进行参数检查并在看起来不正确时抛出异常。

这种方法的注意事项: - 仅当启用日志级别时才达到附加程序。因此,如果在配置中将日志级别设置为 WARN,那么您将无法捕获由 logger.info("message {}", senstive); 完成的日志消息 - 它与您正在使用的日志记录实现的内部实现有关,这意味着更难切换到其他实现(我相信这在现实生活中很少见)

优势在于,如果您还没有自己的日志记录 API,它有助于节省代码更改,因为您可以坚持使用 SLF4J / Log4j2 API


编辑:

刚刚签入Log4j2,它允许你替换MessageFactory(用于基于message + params构造消息字符串)。

https://logging.apache.org/log4j/2.x/manual/extending.html

消息工厂

MessageFactory 用于生成 Message 对象。 应用程序可以替换标准的 ParameterizedMessageFactory(或 ReusableMessageFactory 在无垃圾模式)通过设置值 系统属性 log4j2.messageFactory 到自定义的名称 MessageFactory 类。

Logger.entry() 和 Logger.exit() 方法的流消息有一个 单独的 FlowMessageFactory。应用程序可能会取代 DefaultFlowMessageFactory 通过设置系统属性的值 log4j2.flowMessageFactory 为自定义 FlowMessageFactory 的名称 类。

因此,与上述方法类似,您可以创建自己的MessageFactory 来进行额外的参数检查

【讨论】:

    【解决方案3】:

    防止这种情况的最简单方法是写入/覆盖您的CustomerData.toString() 方法。

    除此之外,您可以扩展 slf4j,但不要问我该怎么做。

    Relevant

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-04
      • 2020-07-21
      相关资源
      最近更新 更多