我有一個安全工具,通過電子郵件向用戶發送新密碼。當閾值爲VERBOSE時,生產電子郵件模塊(我不擁有並且不想更改)將使用Log4Net記錄整個HTML電子郵件消息體。由於電子郵件以明文形式包含域用戶的密碼,因此我想在日誌消息到達appender之前從其中刪除密碼。在到達appenders之前編輯Log4Net消息
有沒有辦法讓我臨時插入一個對象到Log4Net堆棧中,這將允許我搜索LoggingEvent消息並修改它以屏蔽掉我找到的任何密碼?我想插入該對象,調用電子郵件模塊,然後刪除該對象。
我有一個安全工具,通過電子郵件向用戶發送新密碼。當閾值爲VERBOSE時,生產電子郵件模塊(我不擁有並且不想更改)將使用Log4Net記錄整個HTML電子郵件消息體。由於電子郵件以明文形式包含域用戶的密碼,因此我想在日誌消息到達appender之前從其中刪除密碼。在到達appenders之前編輯Log4Net消息
有沒有辦法讓我臨時插入一個對象到Log4Net堆棧中,這將允許我搜索LoggingEvent消息並修改它以屏蔽掉我找到的任何密碼?我想插入該對象,調用電子郵件模塊,然後刪除該對象。
我可能會寫一個模式轉換器。你可以找到一個例子here。您的實現可能是這樣的:
protected override void Convert(TextWriter writer, LoggingEvent loggingEvent)
{
string msg = loggingEvent.RenderedMessage;
// remove the password if there is any
writer.Write(msg);
}
log4net是開源的,你可以修改它。
您可以嘗試使用Unity Application Block method interceptor攔截對log4net的呼叫。或者你可以寫一個自定義的log4net appender。
我也有類似的問題,我從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()
的實現,你不在!
克里斯牧師的解決方案的一個小改進:繼承你的appender不是從ForwardingAppender,而是從基類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>
另一種解決方案是攔截LoggerEvent在直接從記錄器到達任何appender之前。一個先決條件是能夠在創建任何記錄器之前修改根層次結構。
在下面的示例中,我們只是重新創建了一個新的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);
}
}
}
你能提供一個web.config部分的例子,看起來像AppenderSkeleton vs ForwardingAppender? – 2017-07-10 18:32:09
@ChrisBohatka改進的答案與配置示例 – mikka 2017-07-11 17:41:50
優秀!謝謝! – 2017-07-13 13:15:16