冠军

导航

在 ASP.NET Core 中 使用 Serilog

Serilog.AspNetCore

https://github.com/serilog/serilog-aspnetcore#two-stage-initialization

这是 Serilog 日志系统的 ASP.NET Core 支持包。该包将 ASP.NET Core 的日志信息路由到 Serilog,所以你可以得到关于 ASP.NET 的内部处理信息,与你的应用程序事件一起写入到同样的输出中。

当安装 Serilog.AspNetCore 并配置之后,你可以直接通过 Serilog 写出日志消息,或者使用任何通过 ASP.NET 所注入的 ILogger 接口。所有的日志器将使用相同的底层实现、日志级别以及输出目标。

.NET Framework 和 .NET Core 2.x 由本包的 v3.4.0 版本所支持。最新版本的 Serilog.AspNetCore 需要 .NET 3.x、.NET 5 或者更新版本的支持。

使用说明

首先,安装 Serilog.AspNetCore NuGet 包到你的应用中

dotnet add package Serilog.AspNetCore

然后,在你的应用程序的 Program.cs 文件中,先配置 Serilog,然后使用 try/catch 语句块来确保任何配置问题都会被正确记录下来。

using Serilog;
using Serilog.Events;

public class Program
{
    public static int Main(string[] args)
    {
        Log.Logger = new LoggerConfiguration()
            .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
            .Enrich.FromLogContext()
            .WriteTo.Console()
            .CreateLogger();

        try
        {
            Log.Information("Starting web host");
            CreateHostBuilder(args).Build().Run();
            return 0;
        }
        catch (Exception ex)
        {
            Log.Fatal(ex, "Host terminated unexpectedly");
            return 1;
        }
        finally
        {
            Log.CloseAndFlush();
        }
    }

然后,使用 UseSerilog() 到 Host 上来。

var builder = WebApplication.CreateBuilder(args);

    // Add this line to add Serilog
    builder.Host.UseSerilog ();

最后,通过删除对于默认日志器完成清理工作。

  • 删除 appsetting.json 配置文件中的 Logging 配置节

完成!使用配置的日志级别,你将会看到类似如下输出

[22:14:44.646 DBG] RouteCollection.RouteAsync
    Routes: 
        Microsoft.AspNet.Mvc.Routing.AttributeRoute
        {controller=Home}/{action=Index}/{id?}
    Handled? True
[22:14:44.647 DBG] RouterMiddleware.Invoke
    Handled? True
[22:14:45.706 DBG] /lib/jquery/jquery.js not modified
[22:14:45.706 DBG] /css/site.css not modified
[22:14:45.741 DBG] Handled. Status code: 304 File: /css/site.css

提示:为了在运行在 IIS 下的时候,通过 Visual Studio 中的 Output 窗口中看到 Serilog 输出,或者从输出窗口的 Show output 的下拉列表中选择 ASP.NET Core Web Server,或者将 WriteTo.Console() 替换为 WriteTo.Debug()

更多完整示例,包括使用 appsetting.json 配置,可以参考示例项目

请求日志

本包中包括了一个用于智能处理 HTTP 请求日志的中间件。在默认的 ASP.NET Core 的请求日志实现中包含杂音。对于单个请求生成多个事件。本包所提供的中间件将它们合并为单个事件,包含了 Method、Path、Status Code 和计时信息。

以纯文本方式,示例格式如下

[16:05:54 INF] HTTP GET / responded 200 in 227.3253 ms

JSON 格式:

{
  "@t": "2019-06-26T06:05:54.6881162Z",
  "@mt": "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms",
  "@r": ["224.5185"],
  "RequestMethod": "GET",
  "RequestPath": "/",
  "StatusCode": 200,
  "Elapsed": 224.5185,
  "RequestId": "0HLNPVG1HI42T:00000001",
  "CorrelationId": null,
  "ConnectionId": "0HLNPVG1HI42T"
}

为了启用该中间件,首先在你的日志配置或者 appsetting.json 中修改 Microsoft.AspNetCore 的最小日志级别到 Warning

    .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)

然后,在应用程序的 Startup.cs 中,使用 UseSerilogRequestLogging() 启用该中间件:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseSerilogRequestLogging(); // <-- Add this line

    // Other app configuration

特别重要的是,UseSerilogRequestLogging() 调用要出现在像 MVC 这样的处理器之前。中间件并不能处理流水线中出现在它之前的组件 ( 这样可以排除一些杂音,比如通过将 UseSerilogRequestLogging() 放在 UseStaticFiles() 之后 )

在请求处理过程中,附加的属性可以通过 IDiagnosticContext.Set() 来追加到完整的事件上。

public class HomeController : Controller
{
    readonly IDiagnosticContext _diagnosticContext;

    public HomeController(IDiagnosticContext diagnosticContext)
    {
        _diagnosticContext = diagnosticContext ??
            throw new ArgumentNullException(nameof(diagnosticContext));
    }

    public IActionResult Index()
    {
        // The request completion event will carry this property
        _diagnosticContext.Set("CatalogLoadTime", 1423);

        return View();
    }

通过这种模式可以压缩日志事件的数量,涉及到针对每个 HTTP 请求中构造、传输和存储。在同一事件中设置多个属性也可以使得关联请求的细节内容,以及其它数据更简单。

下列请求信息将被默认添加到属性上:

  • RequestMethod
  • RequestPath
  • StatusCode
  • Elapsed

你可以修改默认的请求事件的模板,添加附加的属性,或者变更事件级别,在 UseSerilogRequestLogging() 回调方法中使用 options 参数设置:

app.UseSerilogRequestLogging(options =>
{
    // Customize the message template
    options.MessageTemplate = "Handled {RequestPath}";
    
    // Emit debug-level events instead of the defaults
    options.GetLevel = (httpContext, elapsed, ex) => LogEventLevel.Debug;
    
    // Attach additional properties to the request completion event
    options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
    {
        diagnosticContext.Set("RequestHost", httpContext.Request.Host.Value);
        diagnosticContext.Set("RequestScheme", httpContext.Request.Scheme);
    };
});

两段式初始化

前面的示例展示了如何在应用启动的时候,立即配置 Serilog。这样可以得到通过异常捕获在 ASP.NET Core 宿主初始化阶段的信息。

首先初始化 Serilog 的缺点是在 ASP.NET Core 主机的服务,包括了 appsetting.json 的配置和依赖注入,将不能使用到。

为了处理这个问题,Serilog 支持两段式初始化,第一步在应用启动的时候,立即配置并初始化日志器,然后再主机加载之后,它被重新配置的日志器替换掉。

为了使用这个技术,首先使用 CreateBootstrapLogger() 替换原来的 CreateLogger()

using Serilog;
using Serilog.Events;

public class Program
{
    public static int Main(string[] args)
    {
        Log.Logger = new LoggerConfiguration()
            .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
            .Enrich.FromLogContext()
            .WriteTo.Console()
            .CreateBootstrapLogger(); // <-- Change this line!

然后,在调用 UseSerilog() 的时候通过回调函数来创建最终的日志器。

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .UseSerilog((context, services, configuration) => configuration
            .ReadFrom.Configuration(context.Configuration)
            .ReadFrom.Services(services)
            .Enrich.FromLogContext()
            .WriteTo.Console())
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });

特别需要注意的是,最终的日志器完全替换了原来的启动日志器。比如说,如果你希望它们都输出到控制台 Console,那么你需要在两个地方都指定 WriteTo.Console(),如上面示例。

支持 appsetting.json 配置

使用两段式初始化,插入 ReadFrom.Configuration(context.Configuration) 调用,如上面示例所示,JSON 配置文件的语法见 Serilog.Settings.Configuratio README 文档

将服务注入到 Enricher 和 Sink 中

在使用两段式初始化的时候,插入如上面示例所示的 ReadFrom.Services(services) 调用。该调用将使用任何注册的如下服务的实现来配置日志处理管线:

  • IDestructuringPolicy
  • ILogEventEnricher
  • ILogEventFilter
  • ILogEventSink
  • LoggingLevelSwitch

启用 Microsoft.Extensions.Logging.ILoggerProviders

Serilog 将事件发送到被称为 sink 的输出中,Serilog 的 ILogEventSink 接口实现,并使用 WriteTo 添加到日志处理管线中。Microsoft.Extensions.Logging 有类似的概念,不过被称为 provider。它实现 ILoggerProvider 接口。这些 provider 通过类似 AddConsole() 这样的方法在底层被创建出来。

默认情况下,Serilog 忽略这些 provider,因为它们等价于 Serilog 中存在的 sink。它们在 Serilog 的处理管线中更为高效。如果需要支持 provider,它也可以被可选地启用。

为了使 Serilog 将事件传递给 provider,在使用两段式初始化的示例中,提供 writeToProviders: true 作为参数传递给 UseSerilog()

.UseSerilog(
    (hostingContext, services, loggerConfiguration) => /* snip! */,
    writeToProviders: true)

JSON 输出

Console()、Debug()、File() 输出中,都原生支持 JSON 格式,通过包含 Serilog.Formatting.Compact NuGet 包。

输出新行分隔的 JSON,传递 CompactJsonFormatter 或者 RenderedCompactJsonFormatter 到 sink 配置方法中。

    .WriteTo.Console(new RenderedCompactJsonFormatter())

如果你希望在你的代码的特定部分对日志都增加额外的属性,你可以将它们添加到 ILogger<> 中,在 Microsoft.Extensions.Logging 中使用如下代码。为了使得这些代码生效,确保你在 .UseSErilog(...) 的内部添加了 .Enrich.FromLogContext() 语句,如上面示例所示。

// Microsoft.Extensions.Logging ILogger<T>
// Yes, it's required to use a dictionary. See https://nblumhardt.com/2016/11/ilogger-beginscope/
using (logger.BeginScope(new Dictionary<string, object>
{
    ["UserId"] = "svrooij",
    ["OperationType"] = "update",
}))
{
   // UserId and OperationType are set for all logging events in these brackets
}

在 Serilog 中,使用如下代码:

// Serilog ILogger
using (logger.PushProperty("UserId", "svrooij"))
using (logger.PushProperty("OperationType", "update"))
{
    // UserId and OperationType are set for all logging events in these brackets
}

.NET 6 示例

Program

// step #1 serilog boostrap
Log.Logger = new LoggerConfiguration()
            .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
            .Enrich.FromLogContext()
            .WriteTo.Console()
            .CreateBootstrapLogger(); // <-- Change this line!

try
{
    Log.Information("Starting web host");

    var builder = WebApplication.CreateBuilder(args);

    // step #2 build final serilog logger
    builder.Host.UseSerilog((context, services, configuration) => configuration
            .ReadFrom.Configuration(context.Configuration)
            .ReadFrom.Services(services)
            .Enrich.FromLogContext()
            );

配置文件 appsetting.json

{
  "Serilog": {
    "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ],
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "Microsoft.AspNetCore": "Warning",
        "System": "Warning"
      }
    },
    "WriteTo": [
      { "Name": "Console" },
      {
        "Name": "File",
        "Args": {
          "path": "./Logs/log-.txt",
          "rollingInterval": "Day",
          "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact"
        }
      }
    ],
    "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],

    "Properties": {
      "Application": "Sample"
    }
  },
  "AllowedHosts": "*"
}

Reference

posted on 2022-07-16 17:28  冠军  阅读(2000)  评论(0编辑  收藏  举报