【发布时间】:2015-07-18 18:42:21
【问题描述】:
我正在考虑使用 log4net 作为即将开始的新项目的日志记录框架。我在原型设计过程中遇到的一个我无法找到明确答案的问题是如何以可配置和整洁的方式清理或屏蔽消息内容。
假设我想让几个清洁工投入工作,但我也想遵循单一责任原则。一些更简洁的例子:
- 卡号/PAN 清洁器
- 密码清理器
- 私有数据清理器
我知道您永远不应该以纯文本形式记录此类信息,并且执行日志的代码永远不会故意这样做。我希望有最后一层保护,但是以防数据格式不正确并且敏感数据不知何故滑入不应该的地方;日志是最坏的情况。
选项 1:
我发现这篇 StackOverflow 文章详细介绍了一种可能的解决方案,但它涉及使用反射。这对于性能来说是不可取的,但操纵内部存储机制似乎也很棘手。 Editing-log4net-messages-before-they-reach-the-appenders
选项 2:
对同一问题的建议答案建议使用 PatternLayoutConverter。这对于单个清理器操作很好,但您无法使用多个操作,例如以下:
public class CardNumberCleanerLayoutConverter : PatternLayoutConverter
{
protected override void Convert(TextWriter writer, LoggingEvent loggingEvent)
{
string message = loggingEvent.RenderedMessage;
// TODO: Replace with real card number detection and masking.
writer.Write(message.Replace("9", "*"));
}
}
<layout type="log4net.Layout.PatternLayout">
<converter>
<name value="cleanedMessage" />
<type value="Log4NetPrototype.CardNumberCleanerLayoutConverter, Log4NetPrototype" />
</converter>
<converter>
<name value="cleanedMessage" />
<type value="Log4NetPrototype.PasswordCleanerLayoutConverter, Log4NetPrototype" />
</converter>
<conversionPattern value="%cleanedMessage" />
</layout>
在上面演示的命名冲突的情况下,最后加载的转换器将是被操作的转换器。使用上面的示例,这意味着将清除密码但不会清除卡号。
选项 3:
我尝试过的第三个选项是使用链式 ForwarderAppender 实例,但这很快会使配置复杂化,我认为它不是一个理想的解决方案。因为 LoggingEvent 类有一个不可变的 RenderedMessage 属性,我们无法在不创建 LoggingEvent 类的新实例并通过它的情况下更改它,如下所示:
public class CardNumberCleanerForwarder : ForwardingAppender
{
protected override void Append(LoggingEvent loggingEvent)
{
// TODO: Replace this with real card number detection and masking.
string newMessage = loggingEvent.RenderedMessage.Replace("9", "*");
// What context data are we losing by doing this?
LoggingEventData eventData = new LoggingEventData()
{
Domain = loggingEvent.Domain,
Identity = loggingEvent.Identity,
Level = loggingEvent.Level,
LocationInfo = loggingEvent.LocationInformation,
LoggerName = loggingEvent.LoggerName,
ExceptionString = loggingEvent.GetExceptionString(),
TimeStamp = loggingEvent.TimeStamp,
Message = newMessage,
Properties = loggingEvent.Properties,
ThreadName = loggingEvent.ThreadName,
UserName = loggingEvent.UserName
};
base.Append(new LoggingEvent(eventData));
}
}
public class PasswordCleanerForwarder : ForwardingAppender
{
protected override void Append(LoggingEvent loggingEvent)
{
// TODO: Replace this with real password detection and masking.
string newMessage = loggingEvent.RenderedMessage.Replace("4", "*");
// What context data are we losing by doing this?
LoggingEventData eventData = new LoggingEventData()
{
Domain = loggingEvent.Domain,
Identity = loggingEvent.Identity,
Level = loggingEvent.Level,
LocationInfo = loggingEvent.LocationInformation,
LoggerName = loggingEvent.LoggerName,
ExceptionString = loggingEvent.GetExceptionString(),
TimeStamp = loggingEvent.TimeStamp,
Message = newMessage,
Properties = loggingEvent.Properties,
ThreadName = loggingEvent.ThreadName,
UserName = loggingEvent.UserName
};
base.Append(new LoggingEvent(eventData));
}
}
匹配的配置(很难理解):
<log4net>
<appender name="LocatedAsyncForwardingAppender" type="Log4NetPrototype.LocatedAsyncForwardingAppender, Log4NetPrototype">
<appender-ref ref="CardNumberCleanerForwarder" />
</appender>
<appender name="CardNumberCleanerForwarder" type="Log4NetPrototype.CardNumberCleanerForwarder, Log4NetPrototype">
<appender-ref ref="PasswordCleanerForwarder" />
</appender>
<appender name="PasswordCleanerForwarder" type="Log4NetPrototype.PasswordCleanerForwarder, Log4NetPrototype">
<appender-ref ref="LogFileAppender" />
</appender>
<appender name="LogFileAppender" type="Log4NetPrototype.LogFileAppender, Log4NetPrototype">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%m" />
</layout>
</appender>
<root>
<level value="DEBUG" />
<appender-ref ref="LocatedAsyncForwardingAppender" />
</root>
</log4net>
对于如何在理论上以性能为代价配置 n 个清洁器的情况下如何实现这一点,是否有人有其他建议?
【问题讨论】: