Log4j2 提供了多种方法来实现这一点:
过滤器
Log4j2 允许您在特定记录器、特定附加程序或全局(因此过滤器适用于所有日志事件)上配置 filters。过滤器为您提供的是强制接受日志事件或强制拒绝日志事件或成为“中性”的能力。在您的情况下,您可能希望拒绝记录包含敏感数据的事件。
您可以创建一个custom Filter implementation(请参阅plugin docs 了解如何安装您的自定义过滤器),或者您可以使用一些内置过滤器。内置的 RegexFilter 或 ScriptFilter 应该足以满足您的目的。
正则表达式过滤器示例
假设这是一个包含敏感数据的类:
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.threadlocals 到 false。
使用您的自定义 MaskSensitiveDataPolicy 重写策略配置您的重写附加程序。要让 Log4j2 知道您的插件,请在 Configuration 的 packages 属性中指定插件包的名称:
<?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.