serilog 格式 SourceContext 仅显示程序集名称

问题描述 投票:0回答:3

我将我的项目配置为使用 Serilog 通过依赖注入进行日志记录。 我在类构造函数中使用以下架构:

namespace FlickPopper.API.Controllers {

    public class ValuesController : Controller {
        private ILogger<ValuesController> _logger;
        public MyClass(ILogger<ValuesController> logger) {
           _logger = logger;
        }
    }
}

这样,serilog 创建了调用

Log.ForContext<classname>

的记录器

我的设置是:

  "Serilog": {
      "WriteTo": [
        {
          "Name": "RollingFile",
          "Args": {
            "pathFormat": "Logs\\log-{Date}.txt",
            "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] {Message}{NewLine}{Exception}"
          }
        }
      ],
      "Enrich": [ "FromLogContext" ]
    }

所以,日志看起来像这样:

2018-01-26 22:20:08 [INF] [FlickPopper.API.Controllers.ValuesController] Get all values

有什么方法可以格式化 SourceContext 属性以仅在日志中显示程序集名称,像这样吗?

2018-01-26 22:20:08 [INF] [FlickPopper.API] Get all values
c# .net-core asp.net-core-2.0 serilog
3个回答
37
投票

当您使用注入的

ILogger<T>
时,Serilog记录器在底层是由实现ILoggerProvider接口的
SerilogLoggerProvider
创建的。这个接口有唯一的方法:

public interface ILoggerProvider : IDisposable
{
    ILogger CreateLogger(string categoryName);
}

传递的

categoryName
用作消息格式中
{SourceContext}
属性的值。 ASP.NET Core 将其作为完全限定名称传递(例如
FlickPopper.API.Controllers.ValuesController
)。

因此,这个字符串值不应该在 Serilog 代码或配置中修复,而应该在 ASP.NET Core 日志基础设施中修复。

首先负责创建该值的类是

Logger<T>
类。当您将
Logger<T>
注入到类中时,
ILogger<T>
的实例就会被实例化。这是其构造函数的源代码

public class Logger<T> : ILogger<T>
{
    public Logger(ILoggerFactory factory)
    {
        if (factory == null)
        {
            throw new ArgumentNullException(nameof(factory));
        }

        _logger = factory.CreateLogger(TypeNameHelper.GetTypeDisplayName(typeof(T)));
    }

    //  ...
}

TypeNameHelper.GetTypeDisplayName(typeof(T))
的调用返回完全限定的名称,然后传递给
ILoggerFactory
并最终传递给
SerilogLoggerProvider

因此,如果您想更改该行为并调整传递给

categoryName
ILoggerFactory
,您应该拥有自己的
ILogger<T>
实现,以对
ILoggerFactory.CreateLogger()
进行所需的调用。这并不是那么困难,因为
Logger<T>
类非常薄,并且基于
Microsoft.Extensions.Logging.Logger
实现。这是
Logger<T>
的副本的类,除了生成记录器类别名称的一行:

public class LoggerEx<T> : ILogger<T>
{
    private readonly ILogger _logger;

    public LoggerEx(ILoggerFactory factory)
    {
        if (factory == null)
        {
            throw new ArgumentNullException(nameof(factory));
        }

        _logger = factory.CreateLogger(typeof(T).Assembly.GetName().Name);
    }

    IDisposable ILogger.BeginScope<TState>(TState state)
    {
        return _logger.BeginScope(state);
    }

    bool ILogger.IsEnabled(LogLevel logLevel)
    {
        return _logger.IsEnabled(logLevel);
    }

    void ILogger.Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        _logger.Log(logLevel, eventId, state, exception, formatter);
    }
}

您还应该在服务注册中将

ILogger<T>
的标准实现替换为以下代码:

services.AddSingleton(typeof(ILogger<>), typeof(LoggerEx<>));

现在

LoggerEx<T>
的实例将被注入到控制器中,并且
{SourceContext}
将具有您构建的值:

2018-01-27 09:54:21 [INF] [TestProject.TestApplication] 你好!


6
投票

我有类似的问题,但我只想包含类/接口名称。

我通过创建自定义丰富器解决了这个问题。

// Create logger
var logger = new LoggerConfiguration()
  // ...
  .Enrich.With(new SourceContextEnricher())
  // ...
  .CreateLogger();

public class SourceContextEnricher : ILogEventEnricher {

  public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) {
    if (logEvent.Properties.TryGetValue("SourceContext", out var property)) {
      var scalarValue = property as ScalarValue;
      var value = scalarValue?.Value as string;

      if (value?.StartsWith("FlickPopper.API.") ?? false) {
        var lastElement = value.Split(".").LastOrDefault();
        if (!string.IsNullOrWhiteSpace(lastElement)) {
          logEvent.AddOrUpdateProperty(new LogEventProperty("SourceContext", new ScalarValue(lastElement)));
        }
      }

    }
  }
}

0
投票

可以使用Serilog.Expressions对模板进行详细定制。

例如 1 2 仅显示类型名称:

.WriteTo.Console(new ExpressionTemplate(
  "[{@t} {@l:u3} "{Substring(SourceContext, LastIndexOf(SourceContext, '.') + 1)}] {@m}\n{@x}"))

或者仅命名空间:

.WriteTo.Console(new ExpressionTemplate(
  "[{@t} {@l:u3} "{Substring(SourceContext, 0, LastIndexOf(SourceContext, '.'))}] {@m}\n{@x}"))

1https://nblumhardt.com/2021/06/customize-serilog-text-output/#-formatting-sourcecontext-as-a-simple-class-name
2https://github.com/serilog/serilog-expressions?tab=readme-ov-file#recipes

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.