Serilog 已經是我們團隊的標準的 Log Provider,在此我針對它的配置粗略的紀錄,希望可以幫助到需要的人
開發環境
- Windows 11
- Rider
ASP.NET Core 配置 Serilog
開啟一個新的 ASP.NET Core 專案
dotnet new webapi -o Lab.SerilogProject.WebApi
安裝 Serilog
進入目錄,安裝 Serilog 套件:
dotnet add package Serilog.AspNetCore --version 6.0.1
可以看到相關依賴的套件都準備好了
設定 Host 載體的 Serilog 組態
- 初始化
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.File("logs/host-.txt", rollingInterval: RollingInterval.Hour)
.CreateLogger();
- 把 hosting 整段用 try/catch 包起來。
- 讓 Host 使用 Serilog:builder.Host.UseSerilog()。
- 每一個 Request 使用 Serilog 記錄下來:app.UseSerilogRequestLogging()。
- 讓 Log 寫入 Sinks:Log.CloseAndFlush()。
- finally 區段的確保應用程式當掉的時候能寫入serilog sinks
try
{
Log.Information("Starting web host");
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog();//<=== 讓 Host 使用 Serilog
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseSerilogRequestLogging(); //<=== 每一個 Request 使用 Serilog 記錄下來
app.UseAuthorization();
app.MapControllers();
app.Run();
return 0;
}
catch (Exception ex)
{
Log.Fatal(ex, "Host terminated unexpectedly");
return 1;
}
finally
{
Log.CloseAndFlush();
}
二階段初始化
第一次的初始化 Serilog 是為了要攔截 host 啟動/執行的異常,這時候還不能使用 host 的 DI Container,這時候 Configuration Provider 尚未工作,也就是 appsetting.json 還不能用,所幸 Serilog 支援二階段初始化,可以在 DI Container 套用 appsetting.json,步驟如下
- CreateLogger() 取代成 CreateBootstrapLogger()
- 使用 UseSerilog()
- ReadFrom.Configuration(context.Configuration):我們可以在 appsetting.json 設定有關 Serilog 的配置,通過該方法設定載入Configuration(appsetting.json),參考
- ReadFrom.Services(services):注入 enrichers and sinks,在每一個 pipeline 將會註冊以下,
IDestructuringPolicy
ILogEventEnricher
ILogEventFilter
ILogEventSink
LoggingLevelSwitch
如下圖所示:
參考
serilog/serilog-aspnetcore: Serilog integration for ASP.NET Core (github.com)
除了二階段,我們也可以先行初始化 ConfigurationBuilder,然後再餵給 host 的 DI Container 以及 Serilog.Log
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build;
餵給 Serilog.Log
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(config)
.....
.CreateLogger();
有關 Configuration 可以參考
如何使用組態 Microsoft.Extensions.Configuration | 余小章 @ 大內殿堂 - 點部落 (dotblogs.com.tw)
設定應用程式的 Serilog 組態
- 指定應用程式的 Serilog 設定:builder.Host.UseSerilog
- 從 host 讀取組態檔(例如:appsettings.json):config.ReadFrom.Configuration
builder.Host.UseSerilog((context, services, config) =>
config.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.File("logs/aspnet-.txt", rollingInterval: RollingInterval.Minute)
);
在 appsettings.json 檔案中的 Logging 區段移除並加入Serilog的區段,如下
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning"h
}
}
}
更多的 Setting
在 Controller 裡面加入以下代碼
this._logger.LogInformation(new EventId(2000, "Trace"), "Start {ControllerName}.{MethodName}...",
nameof(WeatherForecastController), nameof(Get));
執行結果如下:
.NET Console 配置 Serilog
接下來輪到 Console 應用程式了,新增一個 Console 專案
dotnet new console -o Lab.SerilogProject.ConsoleApp --framework net6.0
安裝套件
dotnet add package Microsoft.Extensions.Hosting --version 6.0.1
dotnet add package Serilog.Extensions.Hosting --version 5.0.1
dotnet add package Serilog.Formatting.Compact --version 1.1.0
dotnet add package Serilog.Settings.Configuration --version 3.3.0
dotnet add package Serilog.Sinks.Console --version 4.0.1
dotnet add package Serilog.Sinks.File --version 5.0.0
dotnet add package Serilog.Sinks.Seq --version 5.1.1
Console 的配置跟 ASP.NET Core 大同小異,就不再多述
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.File("logs/host-.txt", rollingInterval: RollingInterval.Day)
.CreateBootstrapLogger()
;
try
{
Log.Information("Starting host");
var builder = Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<LabBackgroundService>();
})
.UseSerilog((context, services, config) =>
{
var formatter = new JsonFormatter();
config.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.Enrich.FromLogContext()
.WriteTo.Console(formatter)
.WriteTo.Seq("http://localhost:5341")
.WriteTo.File(formatter, "logs/app-.txt", rollingInterval: RollingInterval.Minute);
});
;
var host = builder.Build();
host.StartAsync();
host.StopAsync();
Console.WriteLine("Bye~~~");
}
catch (Exception ex)
{
Log.Fatal(ex, "Host terminated unexpectedly");
throw;
}
finally
{
Log.CloseAndFlush();
}
LabBackgroundService 也有吃到 Serilog 的實例
public class LabBackgroundService : BackgroundService
{
private readonly ILogger<LabBackgroundService> _logger;
public LabBackgroundService(ILogger<LabBackgroundService> logger)
{
this._logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
this._logger.LogInformation(new EventId(2000, "Trace"), "Start {ClassName}.{MethodName}...",
nameof(LabBackgroundService), nameof(this.ExecuteAsync));
var sensorInput = new { Latitude = 25, Longitude = 134 };
this._logger.LogInformation("Processing {@SensorInput}", sensorInput);
}
}
Log 等級
在 Controller 注入 ILogger<T>,然後依照需求呼叫各個不同等級的 logging,Serilog 提供以下 Log
_logger.LogInformation("Info!");
_logger.LogWarning("Warning!");
_logger.LogTrace("Trace!");
_logger.LogDebug("Debug");
_logger.LogCritical("Critical");
_logger.LogError("Error");
Serilog 的 LogEventLevel 只有 6 個層級,分別是
- Verbose = 0
- Debug = 1
- Information = 2
- Warning = 3
- Error = 4
- Fatal = 5
這跟微軟的 LogLevel 列舉 (Microsoft.Extensions.Logging) | Microsoft Docs 他有七個,對不太起來,Serilog 沒有 None,使用上要稍微注意一下,不要用錯了
Sinks
Serilog 提供了相當豐富的 Provided Sinks · serilog/serilog Wiki (github.com),這有點像是 NLog 的 Target
Serilog 寫入 Seq
log 寫入本機檔案仍不易除錯,seq 是一套強大的 log server,支援結構化查詢
安裝
docker run --name seq -d --restart unless-stopped -e ACCEPT_EULA=Y -p 5341:80 datalust/seq:latest
.WriteTo.File() 改成 .WriteTo.Seq("http://localhost:5341")
執行結果如下圖:
結構化資料
有關結構化相關知識的這裡可以參考這裡
Structured Data · serilog/serilog Wiki (github.com)
Structured logging concepts in .NET Series (1) (nblumhardt.com)
裡面有一個很關鍵的 Message Template,這跟我們在使用 string.Format() 的寫法很像,以下例,最後寫入到 log 的時候可以看到 ControllerName 和 MethodName 欄位
this._logger.LogInformation(new EventId(2000, "Trace"), "Start {ControllerName}.{MethodName}...",
nameof(WeatherForecastController), nameof(Get));
執行結果如下圖,可以看到除了 Message 有完整的句子,ControllerName 和 MethodName 欄位被拆了出來
解構符
把一個物件攤平,使用 @ 解構操作符號
var sensorInput = new { Latitude = 25, Longitude = 134 };
Log.Information("Processing {@SensorInput}", sensorInput);
在 Console 應用程式輸出,SensorInput 相關的欄位被攤開,如下
Processing {"Latitude":25,"Longitude":134}
在 ASP.NET Core 應用程式輸出,SensorInput 相關的欄位被攤開,如下
{
"Timestamp": "2022-09-04T12:20:30.9831700+08:00",
"Level": "Information",
"MessageTemplate": "Processing {@SensorInput}",
"Properties": {
"SensorInput": {
"Latitude": 25,
"Longitude": 134
},
"SourceContext": "Lab.SerilogProject.WebApi.Controllers.WeatherForecastController",
"ActionId": "535101ad-6bd6-40de-a4af-7640c77f1d1b",
"ActionName": "Lab.SerilogProject.WebApi.Controllers.WeatherForecastController.Get (Lab.SerilogProject.WebApi)",
"RequestId": "0HMKE5J13R5K2:00000005",
"RequestPath": "/WeatherForecast",
"ConnectionId": "0HMKE5J13R5K2"
}
}
Seq 呈現效果如下
輸出格式化
安裝
dotnet add package Serilog.Formatting.Compact --version 1.1.0
在 .WriteTo.Console(), Debug(), and File() 這些 Skins 支援 Json Format,這個功能在 Serilog.Formatting.Compact 套件,預設有以下
- CompactJsonFormatter()
- JsonFormatter()
- MessageTemplateTextFormatter()
- RawFormatter()
- RenderedCompactJsonFormatter()
範例如下:
var formatter = new CompactJsonFormatter();
builder.Host.UseSerilog((context, services, config) =>
{
config.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.Enrich.FromLogContext()
.WriteTo.Console(formatter)
.WriteTo.Seq("http://localhost:5341")
.WriteTo.File(formatter, "logs/aspnet-.txt", rollingInterval: RollingInterval.Minute)
});
執行結果如下:
ExpressionTemplate
提供更豐富的輸出方式,比如,我想要把 @t 變成 _t
安裝
dotnet add package Serilog.Expressions --version 3.4.0
範例如下:
var formatter = new ExpressionTemplate(
"{ {_t: @t, _msg: @m, _props: @p} }\n");
執行結果如下:
參考
附加額外訊息
替每一個 Log 加上附加訊息
Microsoft.Extensions.Logging.ILogger.BeginScope
using var scope = this._logger.BeginScope(new Dictionary<string, object>
{
["UserId"] = "svrooij",
["OperationType"] = "update",
});
// UserId and OperationType are set for all logging events in these brackets
this._logger.LogInformation(new EventId(2000, "Trace"), "Start {ControllerName}.{MethodName}...",
nameof(WeatherForecastController), nameof(Get));
var sensorInput = new { Latitude = 25, Longitude = 134 };
this._logger.LogInformation("Processing {@SensorInput}", sensorInput);
Serilog.Context.LogContext
using (LogContext.PushProperty("UserId","svrooij"))
using (LogContext.PushProperty("OperationType", "update"))
{
}
Serilog.LoggerConfiguration.Enrich.WithProperty
builder.Host.UseSerilog((context, services, config) =>
{
config.ReadFrom.Configuration(context.Configuration)
...
.Enrich.WithProperty("UserId","svrooij")
.Enrich.WithProperty("OperationType","update")
...
;
});
Middleware
public class TraceMiddleware
{
private readonly RequestDelegate _next;
public TraceMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, ILogger<TraceMiddleware> logger)
{
using (logger.BeginScope(new Dictionary<string, object>
{
["UserId"] = "svrooij",
["OperationType"] = "update",
}))
{
await this._next.Invoke(context);
}
}
}
註冊 Middleware
app.UseMiddleware<TraceMiddleware>();
Seq 執行結果如下:
在每一個 Request 附加訊息
代碼如下:
app.UseSerilogRequestLogging(options =>
{
options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
{
diagnosticContext.Set("UserId", "svrooij");
diagnosticContext.Set("OperationType", "update");
};
});
Seq 執行結果如下:
範例位置
sample.dotblog/StructLog/Lab.SerilogProject at master · yaochangyu/sample.dotblog (github.com)
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET