Serilog 范围的临时日志记录抑制
Temporary logging suppression for a scope in 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" 属性 丰富,即使在从范围中弹出后也是如此。
我找到的唯一解决方法(除了整个 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 用户他们对这个问题的看法。可能有一些我没有找到的记录抑制的常用方法?
我想添加到 ArXen42 答案中。
建议的用于跟踪 activesuppression 键的哈希集不是线程安全的,并且在使用多线程时会产生问题。
一种解决方案是使用 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;
}
}
我需要能够暂时禁用某些范围的日志记录。在我的例子中,有一个后台任务定期尝试为系统中每个可用的 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" 属性 丰富,即使在从范围中弹出后也是如此。
我找到的唯一解决方法(除了整个 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 用户他们对这个问题的看法。可能有一些我没有找到的记录抑制的常用方法?
我想添加到 ArXen42 答案中。
建议的用于跟踪 activesuppression 键的哈希集不是线程安全的,并且在使用多线程时会产生问题。
一种解决方案是使用 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;
}
}