java (log4j) 按对象类型记录过滤器

标签 java logging filter log4j

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

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

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

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

如果 log4j 无法实现,那么其他日志框架怎么样?

最佳答案

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 。来自手册:

The RewriteAppender allows the LogEvent to manipulated before it is processed by another Appender. This can be used to mask sensitive information such as passwords or to inject information into each event.

重写策略示例:

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;
    }
}

CAUTION: When running in garbage-free mode (the default), Log4j2 uses reusable objects for messages and log events. These are not suitable for rewriting. (This is not documented well in the user manual.) If you want to use the rewrite appender, you need to partially switch off garbage-free logging by setting system property log4j2.enable.threadlocals to false.

使用自定义 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.

关于java (log4j) 按对象类型记录过滤器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48105704/

相关文章:

logging - 如何使用 Deno 记录到文件

javascript - 上传文件扩展名过滤器选项不起作用?

python - Pandas:使用 groupby 计算添加标志

java - 从另一个 Activity 返回后更新 fragment

java - JUnit:比较两个无序文本文件的内容

java - 如何过滤Eureka服务器中的Eureka客户端?

java - 为什么此标记生成器返回不正确的值?

logging - 为什么 lagom 在生产中使用异步附加器

python - 为什么控制台记录器与打印不同步?

image-processing - 如何获取 CNN 中的输入和输出 channel ?