Serilog中范围的临时日志记录抑制

时间:2018-06-29 23:16:15

标签: c# logging serilog

我需要能够在某些范围内临时禁用日志记录。在我的情况下,有一个后台任务,该任务会定期尝试为系统中的每个可用COM端口实例化一些设备API,并查看其是否失败。该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”属性。

2 个答案:

答案 0 :(得分:1)

我发现的唯一解决方法(整个API中繁琐的传递自定义ILogger除外)包括以下步骤:

  • "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用户对这个问题的看法。可能是我没有找到抑制日志记录的一些常用方法吗?

答案 1 :(得分:1)

我想添加到 ArXen42 答案中。

用于跟踪 activesuppression 键的提议 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;
    }
}