java(log4j)按对象类型

时间:2018-01-05 00:47:46

标签: java logging filter log4j

我目前使用log4j实现了一个日志语句:

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

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

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

如果log4j不可能,其他日志框架怎么样?

3 个答案:

答案 0 :(得分:3)

Log4j2提供了许多方法来实现这一目标:

过滤器

Log4j2允许您在特定记录器或特定appender上或全局配置filters(因此过滤器适用于所有日志事件)。什么过滤器给你的能力是强制接受日志事件,或强制 - 拒绝日志事件,或者是"中性"。在您的情况下,您可能希望DENY记录包含敏感数据的事件。

您可以创建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,但您也可以使用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.threadlocalsfalse

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

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

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

Relevant