我目前使用log4j实现了一个日志语句:
log.info("Failed to create message for {}", CustomerData);
这将在CustomerData
中记录一些敏感数据。
有没有办法阻止CustomerData
的任何实例的记录?可能在log4j配置中或通过自定义过滤器?
如果log4j不可能,其他日志框架怎么样?
答案 0 :(得分:3)
Log4j2提供了许多方法来实现这一目标:
Log4j2允许您在特定记录器或特定appender上或全局配置filters(因此过滤器适用于所有日志事件)。什么过滤器给你的能力是强制接受日志事件,或强制 - 拒绝日志事件,或者是"中性"。在您的情况下,您可能希望DENY记录包含敏感数据的事件。
您可以创建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,但您也可以使用Java或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>
另一个有趣的选择是重写日志事件,以便消息不会被完全过滤掉,而只是屏蔽敏感数据。例如,用&#34; ***&#34;替换密码字符串。在日志中。
为此,您需要创建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使用可重用对象来处理消息和日志事件。这些不是 适合重写。 (这在用户中没有很好地记录 手动。)如果你想使用重写appender,你需要 通过设置系统属性来部分关闭无垃圾日志记录
log4j2.enable.threadlocals
至false
。
使用自定义MaskSensitiveDataPolicy
重写策略配置重写appender。要让Log4j2了解您的插件,请在packages
的{{1}}属性中指定插件包的名称:
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>
答案 1 :(得分:2)
在Log4j / SLF4j /任何Logging框架中都没有提供(也可能永远不会)。
为了满足您的特定需求,最简单的方法是拥有自己的Logger装饰器。
它可以是SLF4J或Log4j2的自定义日志记录实现。或者只是Logger
的某种工厂(例如SLF4j中的LoggerFactory,或Log4j2的Logger.getLogger()
)
它可以在内部创建您的自定义Logger实现,该实现委派给真实的记录器,并在日志记录中进行额外的检查。
e.g。 (伪码)
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时,它会在内部创建一个日志事件,其中包含日志消息和参数。因此,不是实际格式化appender(或编码器等)中的日志消息,而是执行参数检查,如果看起来不正确则抛出异常。
注意这种方法:
- 仅在启用日志级别时才会到达Appender。因此,如果在配置中将日志级别设置为WARN,那么您将无法捕获logger.info("message {}", senstive);
完成的日志消息
- 它更多地与您正在使用的日志记录实现的内部实现有关,这意味着更难切换到其他实现(这在现实生活中很少见,我相信)
优点是,如果您还没有自己的日志记录API,则可以保存代码更改,因为您可以坚持使用SLF4J / Log4j2 API
编辑:
只需检入Log4j2,它就可以替换MessageFactory(用于根据message + params构造消息字符串)。
https://logging.apache.org/log4j/2.x/manual/extending.html
的MessageFactory
MessageFactory用于生成Message对象。 应用程序可以替换标准的ParameterizedMessageFactory(或 ReusableMessageFactory在无垃圾模式下)通过设置值 系统属性log4j2.messageFactory为自定义名称 MessageFactory类。
Logger.entry()和Logger.exit()方法的流消息有一个 单独的FlowMessageFactory。应用程序可能会取代 DefaultFlowMessageFactory通过设置系统属性的值 log4j2.flowMessageFactory为自定义FlowMessageFactory的名称 类。
因此,与上述方法类似,您可以创建自己的MessageFactory
进行额外的参数检查
答案 2 :(得分:0)