Generic ILogger,或者通过 Serilog,添加 logged 属性

Generic ILogger, or through Serilog, add logged property

有没有办法向给定的 ILogger 实例添加属性? (来自 Microsoft.Extensions.Logging.Abstractions)。我正在使用 Serilog 并且通常将 ILogger 实例注入构造函数。所以它也可能是一个利用一些 Serilog API 的解决方案。

我知道 ILogger.BeginScopeLogContext.PushProperty 但它们只是将信息添加到当前异步上下文。我想将信息添加到记录器本身的实例中,以便该记录器生成的任何日志都具有额外的属性。

我想实现的类似于:

class Foo {
  private ILogger logger;
  public Foo(ILogger logger) {
     logger.AddProperty("Property", "value"); // now every calls using that logger have that extra prop
     _logger = logger;
  }

  public void Dummy() => _logger.LogDebug("logging something..."); // this log event would have the Property=value added to it automatically
}

2021-08-21 更新:您现在可以使用 Serilog.Enrichers.GlobalLogContext

从 NuGet 安装 Serilog.Enrichers.GlobalLogContext 包:

Install-Package Serilog.Enrichers.GlobalLogContext

将 Serilog 命名空间添加到您的 C# 文件中:

using Serilog;
using Serilog.Context;

在您的记录器配置中包含全局日志上下文增强器:

Log.Logger = new LoggerConfiguration()
    .Enrich.FromGlobalLogContext()
    // ... other configuration ...
    .CreateLogger();

FromGlobalLogContext() enricher 动态地将 Serilog.Context.GlobalLogContext 中存在的属性添加到应用程序中 所有 产生的事件。

然后,可以使用 GlobalLogContext.PushProperty():

在全局日志上下文中添加和删除属性
GlobalLogContext.PushProperty("AppVersion", GetThisAppVersion());
GlobalLogContext.PushProperty("OperatingSystem", GetCurrentOS());

执行上述代码后,写入任何Serilog sink的任何日志事件都会自动携带属性AppVersionOperatingSystem

来源:https://github.com/augustoproiete/serilog-enrichers-globallogcontext


原回答

实现这一目标的一种方法是创建一个自定义 Serilog Enricher,您在开始时将其连接到 Serilog 日志记录管道,您也可以在整个应用程序中访问(最好通过依赖注入)。

此丰富器将为您提供一种添加新属性的方法,并将负责确保将这些属性应用于所有日志事件。

下面的例子是这个想法的简化版本(绝对不建议按原样用于生产,而是作为伪代码来展示主要拼凑在一起):

// WARNING: This is *NOT* Production code :)

public sealed class GlobalPropertyEnricher : ILogEventEnricher
{
    // TODO: Make it thread-safe
    private static readonly Dictionary<string, object> _properties =
        new Dictionary<string, object>();

    private static readonly Lazy<GlobalPropertyEnricher> _instance =
        new Lazy<GlobalPropertyEnricher>(() => new GlobalPropertyEnricher());

    public static GlobalPropertyEnricher Instance => _instance.Value;

    private GlobalPropertyEnricher()
    {
    }

    public static void SetProperty(string propertyName, object value)
    {
        // TODO: Make it thread-safe
        _properties[propertyName] = value;
    }

    public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
    {
        // TODO: Make it thread-safe
        // TODO: Properties should be cached (instead of CreateProperty) every time

        foreach (var p in _properties)
        {
            var property = propertyFactory.CreateProperty(p.Key, p.Value);
            logEvent.AddPropertyIfAbsent(property);
        }
    }
}

注意:出于本示例的目的,我有一个静态 class 可通过您的应用程序在全球范围内访问,但您可能会这样做。会将其注册为您的 DI 容器中的 singleton 实例。

使用上面的丰富示例,您可以通过 GlobalPropertyEnricher.SetProperty 从任何地方 add/replace 属性。例如

GlobalPropertyEnricher.SetProperty("Name", "Augusto");
GlobalPropertyEnricher.SetProperty("Year", DateTime.UtcNow.Year);

最后,当您在 Serilog 管道中包含增强器时,您设置的所有属性都将添加到您在应用程序中任何位置写入的每条事件日志消息中:

Log.Logger = new LoggerConfiguration()
    .Enrich.With(GlobalPropertyEnricher.Instance)
    .WriteTo.(...)
    .CreateLogger();