在Log4Net消息到达appender之前编辑它们

时间:2010-10-05 18:06:55

标签: c# logging log4net log4net-configuration appender

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

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

7 个答案:

答案 0 :(得分: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()实施,您就不在了!

答案 1 :(得分:3)

Chris Priest解决方案的一个小改进:继承你的appender不是来自ForwardingAppender,而是来自基类AppenderSkeleton。它使配置变得更简单 - 您不需要从您的配置中引用其他配置器并立即将其应用于不同的记录器

git commit

使用

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;
    }
}

答案 2 :(得分:2)

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

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

答案 3 :(得分:1)

另一种解决方案是在直接从Logger到达任何appender之前拦截LoggingEvent。一个先决条件是能够在创建任何Logger之前修改根层次结构。

在下面的示例中,我们只是重新创建一个新的LoggingEvent,但是如果您关心密集的内存副本则没有必要,通过反射,您可以访问底层的LoggingEventData(是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);
        }
    }
}

答案 4 :(得分:1)

这在@ jeremy-fizames的基础上有所改进,您不必担心在调用任何ILoggerFactory之前是否设置了LogManager.GetLogger()。您可以使用log4net的插件框架来确保在分配任何根记录程序之前已设置此InterceptLoggerFactory[assembly:]属性可确保log4net在设置任何日志记录之前先找到并加载该插件。

此解决方案在不修改现有附加程序配置方式的情况下有效,并且无论您是从XML加载log4net config还是在运行时以编程方式加载,都可以使用。

// 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);
        }
    }
}

答案 5 :(得分:0)

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

答案 6 :(得分:-4)

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