Временное подавление ведения журнала для области в Serilog

Мне нужно временно отключить ведение журнала для некоторой области. В моем случае есть фоновая задача, которая периодически пытается создать экземпляр API какого-либо устройства для каждого доступного COM-порта в системе и проверяет, не выходит ли он из строя. Этот API записывает много информации в журнал в случае сбоя (исключения, вызовы Dispose внутренних компонентов и т. Д.). В результате журнал переполняется ошибками таких неудачных попыток каждую секунду.

Я придумал решение, которое использует LogContext.PushProperty для идентификации подавленных событий журнала. Однако следующий код ничего не регистрирует:

internal static class Program
{
    public static void Main(String[] args)
    {
        void StartListeningSomething()
        {
            Task.Factory.StartNew(() =>
            {
                while (true)
                {
                    Log.Information("Listening");
                    Thread.Sleep(500);
                }
            }, TaskCreationOptions.LongRunning);
        }

        Log.Logger = new LoggerConfiguration()
            .Enrich.WithThreadId()
            .Filter.ByExcluding(logEvent => logEvent.Properties.ContainsKey("SuppressLogging"))
            .Enrich.FromLogContext()
            .WriteTo.Console(new JsonFormatter())
            .CreateLogger();

        using (LogContext.PushProperty("SuppressLogging", true))
        {
            StartListeningSomething();
            Console.ReadKey(); // Will ignore background thread log messages until key enter
        }

        // We want to start logging events after exiting using block
        // But they won't be logged for listener thread at all

        Console.ReadKey();
    }
}

Все события журнала внутри задачи слушателя будут обогащены свойством «SupressLogging» даже после его извлечения из области видимости.


person ArXen42    schedule 29.06.2018    source источник


Ответы (2)


Единственный обходной путь, который я нашел (за исключением утомительной передачи настроенного ILogger по всему API), состоит из следующих шагов:

  • Присвойте уникальное значение свойству "SupressLogging"
  • Добавьте это значение во внутреннюю статическую память
  • При выходе из области действия удалите это значение из хранилища (сделать недействительным)
  • В разделе Filter конфигурации регистратора проверьте, не прикреплено ли свойство и является ли оно допустимым (содержится в хранилище).

В следующем коде используется пользовательский токен IDisposable, чтобы он выглядел как обычный PushProperty

internal static class Program
{
    public static void Main(String[] args)
    {
        void StartListeningSomething()
        {
            Task.Factory.StartNew(() =>
            {
                while (true)
                {
                    Log.Information("Listening");
                    Thread.Sleep(500);
                }
            }, TaskCreationOptions.LongRunning);
        }

        Log.Logger = new LoggerConfiguration()
            .Enrich.WithThreadId()
            .Filter.ByExcluding(logEvent => logEvent.IsSuppressed()) // Check if log event marked with supression property
            .Enrich.FromLogContext()
            .WriteTo.Console(new JsonFormatter())
            .CreateLogger();

        using (SerilogExtensions.SuppressLogging())
        {
            StartListeningSomething();
            Console.ReadKey(); // Will ignore background thread log messages until some key is entered
        }

        // Will start logging events after exiting the using block

        Console.ReadKey();
    }
}

И собственно SerilogExtensions:

/// <summary>
///     Provides helper extensions to Serilog logging.
/// </summary>
public static class SerilogExtensions
{
    private const           String        SuppressLoggingProperty = "SuppressLogging";
    private static readonly HashSet<Guid> ActiveSuppressions      = new HashSet<Guid>();

    /// <summary>
    ///     Get disposable token to supress logging for context.
    /// </summary>
    /// <remarks>
    ///     Pushes "SuppressLogging" property with unique value to SerilogContext.
    ///     When disposed, disposes Serilog property push token and invalidates stored value so new log messages are no longer
    ///     supressed.
    /// </remarks>
    public static IDisposable SuppressLogging()
    {
        return new SuppressLoggingDisposableToken();
    }

    /// <summary>
    ///     Determines whether the given log event suppressed.
    /// </summary>
    /// <remarks>
    ///     Also removes "SuppressLogging" property if present.
    /// </remarks>
    public static Boolean IsSuppressed(this LogEvent logEvent)
    {
        Boolean containsProperty = logEvent.Properties.TryGetValue(SuppressLoggingProperty, out var val);
        if (!containsProperty)
            return false;

        logEvent.RemovePropertyIfPresent(SuppressLoggingProperty); //No need for that in logs

        if (val is ScalarValue scalar && scalar.Value is Guid id)
            return ActiveSuppressions.Contains(id);

        return false;
    }

    /// <summary>
    ///     Disposable wrapper around logging supression property push/pop and value generation/invalidation.
    /// </summary>
    private class SuppressLoggingDisposableToken : IDisposable
    {
        private readonly IDisposable _pushPropertyDisposable;
        private readonly Guid        _guid;

        public SuppressLoggingDisposableToken()
        {
            _guid                   = Guid.NewGuid();
            _pushPropertyDisposable = LogContext.PushProperty(SuppressLoggingProperty, _guid);

            ActiveSuppressions.Add(_guid);
        }

        public void Dispose()
        {
            ActiveSuppressions.Remove(_guid);
            _pushPropertyDisposable.Dispose();
        }
    }
}

Полный пример проекта можно найти на github.

Я хотел бы оставить здесь этот вопрос с самостоятельным ответом, а также спросить более опытных пользователей Serilog, каково их мнение об этой проблеме. Может быть, есть какой-то общий подход к подавлению логов, который я не нашел?

person ArXen42    schedule 29.06.2018

Я хотел бы добавить к ответу ArXen42.

Предлагаемый Hashset для отслеживания ключей активного подавления не потокобезопасен и создаст проблемы при использовании нескольких потоков.

Решением было бы использовать ConcurrentDictionary<T,T2> вместо HashSet<T> или решение, как указано ниже, без отслеживания GUID для подавления журналов.

///     Provides helper extensions to Serilog logging.
/// </summary>
public static class SerilogExtensions
{
    private const string SuppressLoggingProperty
        = "SuppressLogging";

    /// <summary>
    ///     Get disposable token to supress logging for context.
    /// </summary>
    public static IDisposable SuppressLogging()
    {
        return LogContext.PushProperty(SuppressLoggingProperty, true);
    }

    /// <summary>
    ///     Determines whether the given log event suppressed.
    /// </summary>
    /// <remarks>
    ///     Also removes "SuppressLogging" property if present.
    /// </remarks>
    public static bool IsSuppressed(this LogEvent logEvent)
    {
        var containsProperty = logEvent.Properties
            .TryGetValue(SuppressLoggingProperty, out var val);

        if (!containsProperty)
            return false;

        // remove suppression property from logs
        logEvent.RemovePropertyIfPresent(SuppressLoggingProperty);

        if (val is ScalarValue scalar && scalar.Value is bool isSuppressed)
            return isSuppressed;

        return false;
    }
}
person KoenW    schedule 06.05.2021