我有一个安全工具,可以通过电子邮件向用户发送他们的新密码。当阈值为 VERBOSE 时,生产电子邮件模块(我不拥有也不想更改)将使用 Log4Net 记录整个 html 电子邮件正文。由于电子邮件包含明文形式的域用户密码,我想在密码到达附加程序之前从日志消息中删除密码。
有没有办法让我临时将一个对象插入到 Log4Net 堆栈中,让我可以搜索 LoggingEvent 消息并对其进行更改以屏蔽我找到的任何密码?我想插入对象,调用电子邮件模块,然后删除对象。
我有一个安全工具,可以通过电子邮件向用户发送他们的新密码。当阈值为 VERBOSE 时,生产电子邮件模块(我不拥有也不想更改)将使用 Log4Net 记录整个 html 电子邮件正文。由于电子邮件包含明文形式的域用户密码,我想在密码到达附加程序之前从日志消息中删除密码。
有没有办法让我临时将一个对象插入到 Log4Net 堆栈中,让我可以搜索 LoggingEvent 消息并对其进行更改以屏蔽我找到的任何密码?我想插入对象,调用电子邮件模块,然后删除对象。
我有一个类似的问题,我通过继承ForwardingAppender
然后修改LoggingEvent
(使用反射)在传递它之前解决了它。
using System.Reflection;
using log4net.Appender;
using log4net.Core;
class MessageModifyingForwardingAppender : ForwardingAppender
{
private static FieldInfo _loggingEventm_dataFieldInfo;
public MessageModifyingForwardingAppender()
{
_loggingEventm_dataFieldInfo = typeof(LoggingEvent).GetField("m_data", BindingFlags.Instance | BindingFlags.NonPublic);
}
protected override void Append(LoggingEvent loggingEvent)
{
var originalRenderedMessage = loggingEvent.RenderedMessage;
var newMessage = GetModifiedMessage(originalRenderedMessage);
if (originalRenderedMessage != newMessage)
SetMessageOnLoggingEvent(loggingEvent, newMessage);
base.Append(loggingEvent);
}
/// <summary>
/// I couldn't figure out how to 'naturally' change the log message, so use reflection to change the underlying storage of the message data
/// </summary>
private static void SetMessageOnLoggingEvent(LoggingEvent loggingEvent, string newMessage)
{
var loggingEventData = (LoggingEventData)_loggingEventm_dataFieldInfo.GetValue(loggingEvent);
loggingEventData.Message = newMessage;
_loggingEventm_dataFieldInfo.SetValue(loggingEvent, loggingEventData);
}
private static string GetModifiedMessage(string originalMessage)
{
// TODO modification implementation
return originalMessage;
}
}
它不是很漂亮,但它确实有效。
然后你需要一个看起来像这样的 log4net 配置
<log4net>
<appender name="ModifyingAppender" type="Your.Lib.Log4Net.MessageModifyingForwardingAppender,Your.Lib">
<appender-ref ref="ConsoleAppender" />
</appender>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %-5level [%thread] %logger: %message%newline"/>
</layout>
</appender>
<root>
<level value="INFO"/>
<appender-ref ref="ModifyingAppender"/>
</root>
</log4net>
并且GetModifiedMessage()
满足您的需求的实施,您就走了!
Chris Priest 解决方案的一个小改进:不是从 ForwardingAppender 继承您的 appender,而是从基类 AppenderSkeleton 继承。它使配置更简单 - 您不需要从您的附加器中引用其他附加器,现在可以轻松地将其应用于不同的记录器
public class PasswordObfuscationAppender : AppenderSkeleton
{
private static readonly FieldInfo LoggingEventmDataFieldInfo = typeof(LoggingEvent).GetField(
"m_data",
BindingFlags.Instance | BindingFlags.NonPublic);
protected override void Append(LoggingEvent loggingEvent)
{
var originalRenderedMessage = loggingEvent.RenderedMessage;
var newMessage = GetModifiedMessage(originalRenderedMessage);
if (originalRenderedMessage != newMessage)
SetMessageOnLoggingEvent(loggingEvent, newMessage);
}
/// <summary>
/// I couldn't figure out how to 'naturally' change the log message, so use reflection to change the underlying storage of the message data
/// </summary>
private static void SetMessageOnLoggingEvent(LoggingEvent loggingEvent, string newMessage)
{
var loggingEventData = (LoggingEventData)LoggingEventmDataFieldInfo.GetValue(loggingEvent);
loggingEventData.Message = newMessage;
LoggingEventmDataFieldInfo.SetValue(loggingEvent, loggingEventData);
}
private static string GetModifiedMessage(string originalMessage)
{
// TODO modification implementation
return originalMessage;
}
}
用法
<appender name="PasswordObfuscationAppender" type="Foundation.PasswordObfuscationAppender,Foundation" />
<appender name="MainAppender" type="log4net.Appender.RollingFileAppender">
<file value="..\Logs\File.log" />
</appender>
<root>
<level value="DEBUG" />
<appender-ref ref="PasswordObfuscationAppender" />
<appender-ref ref="MainAppender" />
</root>
我可能会写一个模式转换器。你可以在这里找到一个例子。你的实现可能是这样的:
protected override void Convert(TextWriter writer, LoggingEvent loggingEvent)
{
string msg = loggingEvent.RenderedMessage;
// remove the password if there is any
writer.Write(msg);
}
这改进了@jeremy-fizames,您无需担心在调用ILoggerFactory
any 之前是否设置了LogManager.GetLogger()
。您可以使用 log4net 的插件框架来确保InterceptLoggerFactory
在分配任何根记录器之前进行设置。该[assembly:]
属性确保 log4net 在设置任何日志记录之前找到并加载插件。
此解决方案无需修改现有附加程序的配置方式即可工作,无论您是从 XML 加载 log4net 配置和/或在运行时以编程方式加载,它都可以工作。
// Register intercept as a log4net plugin
[assembly: log4net.Config.Plugin(typeof(InterceptPlugin))]
public class InterceptPlugin : log4net.Plugin.PluginSkeleton
{
public InterceptPlugin() : base("Intercept") {}
public override void Attach(ILoggerRepository repository)
{
base.Attach(repository);
((Hierarchy)repository).LoggerFactory = new InterceptLoggerFactory();
}
}
// @jeremy-fizames's ILoggerFactory
public class InterceptLoggerFactory : ILoggerFactory
{
public Logger CreateLogger(ILoggerRepository repository, string name)
{
if (name == null) return new InterceptRootLogger(repository.LevelMap.LookupWithDefault(Level.Debug));
return new InterceptLogger(name);
}
class InterceptLogger : Logger
{
public InterceptLogger(string name) : base(name) {}
protected override void CallAppenders(LoggingEvent loggingEvent)
{
// Implement interception of property on loggingEvent before any call to any appender (execution is sync).
base.CallAppenders(loggingEvent);
}
}
class InterceptRootLogger : RootLogger
{
public InterceptRootLogger(Level level) : base(level) {}
protected override void CallAppenders(LoggingEvent loggingEvent)
{
// Implement interception of property on loggingEvent before any call to any appender (execution is sync).
base.CallAppenders(loggingEvent);
}
}
}
另一种解决方案是在直接从 Logger 到达任何 appender 之前拦截 LoggingEvent。一个先决条件是能够在创建任何 Logger 之前修改 Root Hierarchy。
在下面的示例中,我们只是重新创建了一个新的 LoggingEvent,但如果您关心密集的内存复制,则没有必要,通过反射您可以访问底层的 LoggingEventData(is struct) 并直接为字段设置新值。
您只需要在任何 LogManager.GetLogger() 之前调用 InterceptLoggerFactory.Apply()。
public class InterceptLoggerFactory : ILoggerFactory
{
public static void Apply() => Apply((Hierarchy)LogManager.GetRepository());
public static void Apply(Hierarchy h) => h.LoggerFactory = new InterceptLoggerFactory();
public Logger CreateLogger(ILoggerRepository repository, string name)
{
if (name == null) return new InterceptRootLogger(repository.LevelMap.LookupWithDefault(Level.Debug));
return new InterceptLogger(name);
}
class InterceptLogger : Logger
{
public InterceptLogger(string name) : base(name)
{
}
protected override void CallAppenders(LoggingEvent loggingEvent)
{
// Implement interception of property on loggingEvent before any call to any appender (execution is sync).
/*
* var loggingEventData = loggingEvent.GetLoggingEventData();
* loggingEventData.Message = [EncryptMessage](loggingEventData.Message);
* var newLoggingEvent = new LoggingEvent(loggingEventData);
* base.CallAppenders(newLoggingEvent);
* */
base.CallAppenders(loggingEvent);
}
}
class InterceptRootLogger : RootLogger
{
public InterceptRootLogger(Level level) : base(level)
{
}
protected override void CallAppenders(LoggingEvent loggingEvent)
{
// Implement interception of property on loggingEvent before any call to any appender (execution is sync).
base.CallAppenders(loggingEvent);
}
}
}
您可以尝试使用Unity Application Block 方法拦截器拦截对 log4net 的调用。或者您可以编写一个自定义 log4net 附加程序。
log4net 是开源的,你可以修改它。