【问题标题】:Editing Log4Net messages before they reach the appenders在到达附加程序之前编辑 Log4Net 消息
【发布时间】:2011-04-21 10:52:50
【问题描述】:

我有一个安全工具,可以通过电子邮件向用户发送他们的新密码。当阈值为 VERBOSE 时,生产电子邮件模块(我不拥有也不想更改)将使用 Log4Net 记录整个 html 电子邮件正文。由于电子邮件包含明文形式的域用户密码,我想在密码到达附加程序之前从日志消息中删除密码。

有没有办法让我临时将一个对象插入到 Log4Net 堆栈中,让我可以搜索 LoggingEvent 消息并对其进行更改以屏蔽我找到的任何密码?我想插入对象,调用电子邮件模块,然后删除对象。

【问题讨论】:

    标签: c# logging log4net log4net-configuration appender


    【解决方案1】:

    log4net 是开源的,你可以修改它。

    【讨论】:

      【解决方案2】:

      您可以尝试使用Unity Application Block method interceptor 拦截对 log4net 的调用。或者你可以编写一个自定义 log4net appender。

      【讨论】:

        【解决方案3】:

        我可能会写一个模式转换器。你可以找到一个例子here。你的实现可能是这样的:

        protected override void Convert(TextWriter writer, LoggingEvent loggingEvent)
        {
            string msg = loggingEvent.RenderedMessage;
            // remove the password if there is any
            writer.Write(msg);
        }
        

        【讨论】:

          【解决方案4】:

          我遇到了类似的问题,我通过继承 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() 的实现,然后您就离开了!

          【讨论】:

            【解决方案5】:

            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>
            

            【讨论】:

            • 您能否提供一个示例,说明使用 AppenderSkeleton 与 ForwardingAppender 时 web.config 部分的外观?
            • @ChrisBohatka 通过配置示例改进答案
            【解决方案6】:

            另一种解决方案是在直接从 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);
                    }
                }
            }
            

            【讨论】:

            • 与添加容器 AppenderSkeleton 来拦截事件相比,这种方法的主要好处是 (a) 这不需要或在 log4net 配置文件中公开此拦截(例如,如果此拦截不应该是用户可配置的),并且(b)当您在运行时通过配置和以编程方式添加混合附加程序时,此方法有效。
            【解决方案7】:

            这改进了@jeremy-fizames,您无需担心在调用任何LogManager.GetLogger() 之前是否设置了ILoggerFactory。您可以使用 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);
                    }
                }
            }
            

            【讨论】:

            • 这看起来很有希望,但它是针对一个 9 岁的问题和 3 个工作之前的问题。我没有代码来测试它了。
            • 我正在尝试使用它,但似乎应用程序无法加载或使用它,我在CallAppenders 函数中设置了一个断点但它从未捕获,消息也是从未修改过,所以我确定代码没有执行,我在我的应用程序的AssemblyInfo.cs 中设置了[assembly] 标签
            猜你喜欢
            • 1970-01-01
            • 2015-08-17
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多