Редактирование сообщений Log4Net до того, как они попадут к аппендерам - PullRequest
3 голосов
/ 05 октября 2010

У меня есть инструмент безопасности, который отправляет пользователям новый пароль по электронной почте. Модуль производственной электронной почты (который я не являюсь владельцем и не хочу менять) будет регистрировать все тело сообщения электронной почты в формате html, используя Log4Net, когда порог равен VERBOSE. Поскольку в письме содержится пароль пользователя домена в виде обычного текста, я хотел бы удалить пароль из сообщений журнала до того, как он попадет к аппендерам.

Есть ли способ для меня временно вставить объект в стек Log4Net, который позволил бы мне искать сообщение LoggingEvent и изменять его, чтобы маскировать любые пароли, которые я нахожу? Я хотел бы вставить объект, вызвать модуль электронной почты, а затем удалить объект.

Ответы [ 7 ]

5 голосов
/ 28 апреля 2015

У меня была похожая проблема, и я решил ее, унаследовав от 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(), которая соответствует вашим потребностям, и вы ушли!

3 голосов
/ 24 марта 2016

Небольшое улучшение в решении Криса Приста: унаследуйте своего аппендера не от 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>
2 голосов
/ 06 октября 2010

Я бы, наверное, написал конвертер шаблонов. Вы можете найти пример здесь . Ваша реализация может быть такой:

protected override void Convert(TextWriter writer, LoggingEvent loggingEvent)
{
    string msg = loggingEvent.RenderedMessage;
    // remove the password if there is any
    writer.Write(msg);
}
1 голос
/ 11 января 2019

Это улучшает @ jeremy-fizames, где вам не нужно беспокоиться о том, установлен ли ILoggerFactory перед вызовом любого LogManager.GetLogger(). Вы можете использовать каркас плагина log4net, чтобы убедиться, что InterceptLoggerFactory установлен перед назначением любого корневого регистратора. Атрибут [assembly:] гарантирует, что log4net находит и загружает плагин до того, как он настроит запись в журнал.

Это решение работает без изменения конфигурации существующих модулей и работает независимо от того, загружаете ли вы конфигурацию log4net из XML и / или программно во время выполнения.

// 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);
        }
    }
}
1 голос
/ 06 декабря 2017

Другим решением является перехват LoggingEvent перед тем, как связаться с любым приложением напрямую из Logger.Одним из предварительных условий является возможность изменить корневую иерархию перед созданием любого регистратора.

В приведенном ниже примере мы просто воссоздаем новое событие LoggingEvent, но в этом нет необходимости, если вы заботитесь об интенсивном копировании памяти, с отражением, которое вы можетеполучить доступ к базовому LoggingEventData (is struct) и установить новые значения непосредственно для полей.

Вам просто нужно вызвать InterceptLoggerFactory.Apply () перед любым LogManager.GetLogger ().

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);
        }
    }
}
0 голосов
/ 05 октября 2010

Вы можете попытаться перехватить вызовы log4net, используя метод-перехватчик Unity Application Block .Или вы можете написать пользовательский log4net appender.

0 голосов
/ 05 октября 2010

log4net с открытым исходным кодом, вы можете изменить его.

...