Log4j2 提供了多种方法来实现此目的:
Filters
Log4j2允许您配置filters在特定记录器上,或在特定附加程序上,或在全局上(因此过滤器适用于所有日志事件)。过滤器为您提供的是强制接受日志事件、强制拒绝日志事件或保持“中立”的能力。在您的情况下,您可能希望拒绝包含敏感数据的日志事件。
您可以创建一个自定义过滤器实现(参见插件文档了解如何安装自定义过滤器),或者您可以使用一些内置过滤器。要么是内置的正则表达式过滤器 or a 脚本过滤器应该足以满足您的目的。
正则表达式过滤器示例
假设这是一个包含敏感数据的类:
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>
脚本过滤器示例
另一个非常灵活的过滤器是脚本过滤器。下面的示例使用 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>
重写日志事件
另一个有趣的选择是重写日志事件,以便消息不会被完全过滤掉,而只是屏蔽敏感数据。例如,您将日志中的密码字符串替换为“***”。
为此,您创建一个重写Appender。从手册中:
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;
}
}
CAUTION:当以无垃圾模式(默认)运行时,Log4j2 使用可重用对象来存储消息和日志事件。这些不是
适合重写。 (这在用户中没有很好地记录
手册。)如果您想使用重写附加器,你需要
部分关闭无垃圾日志记录通过设置系统属性log4j2.enable.threadlocals
to false
.
使用您的自定义配置您的重写附加程序MaskSensitiveDataPolicy
重写政策。为了让 Log4j2 了解您的插件,请在packages
的属性Configuration
:
<?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.