在 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 或者更新版本的支持。
版本化
该包跟踪其 Microsoft.Extensions.Hosting 依赖项的版本控制和目标框架支持。大多数用户应选择与其应用程序的目标框架匹配的 Serilog.AspNetCore 版本。即,
- 如果面向 .NET 7.x,请选择 Serilog.AspNetCore 的 7.x 版本。
- 如果面向 .NET 8.x,请选择 8.x Serilog.AspNetCore 版本,依此类推。
使用说明
首先,安装 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": "*"
}