其他
在 .NET 10 应用中配置 Serilog…
适用于 .NET 10 应用需要结构化日志、集中式 sink、请求日志和日志过滤的场景。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 48/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 57/100 ×0.15
适用场景
在 .NET 10 应用中配置 Seri通用
解决思路
- 使用两阶段初始化:先创建 bootstrap logger,调用 `.CreateBootstrapLogger()`,并配置 `MinimumLevel.Override("Microsoft", LogEventLevel.Information)`、`Enrich.FromLogContext()`、`WriteTo.Console()`;在 `try` 中记录 `Log.Information("Starting application")`,创建 `WebApplication.CreateBuilder(args)`,再用 `builder.Services.AddSerilog((services, lc) => lc.ReadFrom.Configuration(builder.Configuration).ReadFrom.Services(services).Enrich.FromLogContext().Enrich.WithMachineName().Enrich.WithEnvironmentName().Enrich.WithProperty("Application", "MyApp.Api"))` 替换为完整 logger;`catch` 中用 `Log.Fatal(ex, "Application terminated unexpectedly")`,`finally` 中 `await Log.CloseAndFlushAsync()`。
- 使用 `builder.Services.AddSerilog()`(现代 API)而不是 `builder.Host.UseSerilog()`,并通过 `ReadFrom.Services(services)` 集成 DI 服务。
- 使用消息模板而不是字符串插值:用 `{PropertyName}` 语法,避免 `$"..."`。
- 通过 `appsettings.json` 配置日志级别、sink 和覆盖项,以便按环境更改而无需重新部署。
- 在 `appsettings.json` 的 `Serilog` 节中配置 `MinimumLevel`(`Default` 和 `Override`,如 `Microsoft` 设为 `Warning`、`Microsoft.AspNetCore` 设为 `Warning`、`Microsoft.EntityFrameworkCore` 设为 `Warning`、`Microsoft.Hosting.Lifetime` 设为 `Information`、`System` 设为 `Warning`);配置 `WriteTo` 数组包含 `Console`(带 `outputTemplate`)、`File`(`path`: `logs/app-.log`,`rollingInterval`: `Day`,`retainedFileCountLimit`: 30,`fileSizeLimitBytes`: 104857600)、`Seq`(`serverUrl`: `http://localhost:5341`);配置 `Enrich` 数组为 `["FromLogContext", "WithMachineName", "WithEnvironmentName"]`;配置 `Destructure` 为 `ToMaximumDepth`(`maximumDestructuringDepth`: 4)、`ToMaximumStringLength`(`maximumStringLength`: 1024)、`ToMaximumCollectionCount`(`maximumCollectionCount`: 10)。`Override` 使用命名空间前缀匹配 `SourceContext`,更具体的前缀优先。
- 使用 `app.UseSerilogRequestLogging()` 中间件替换 ASP.NET Core 的多个每请求日志事件为单个摘要事件;设置 `MessageTemplate` 为 `"HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms"`,设置 `GetLevel` 根据异常或状态码返回 `LogEventLevel.Error` 或 `LogEventLevel.Information`,设置 `EnrichDiagnosticContext` 添加上下文如 `UserId`。
- 结构化日志使用命名属性:`logger.LogInformation("Order {OrderId} placed by {CustomerId} for {Total:C}", orderId, customerId, total)`;使用 `@` 操作符保留对象结构:`logger.LogInformation("Processing {@SensorInput}", sensorInput)`;使用 `$` 操作符强制 `ToString()`:`logger.LogInformation("Received {$Data}", new[] { 1, 2, 3 })`。
- 使用 `LogContext.PushProperty` 添加作用域属性:在 `using (LogContext.PushProperty("CorrelationId", correlationId)) using (LogContext.PushProperty("TenantId", tenantId)) { ... }` 中记录日志,使属性附加到作用域内所有日志事件;需要在 logger 配置中启用 `.Enrich.FromLogContext()`。
- 使用 OpenTelemetry sink 导出到 OTLP 后端:`.WriteTo.OpenTelemetry(options => { options.Endpoint = "http://localhost:4317"; options.Protocol = OtlpProtocol.Grpc; options.ResourceAttributes = new Dictionary<string, object> { ["service.name"] = "MyApp.Api", ["deployment.environment"] = "production" }; })`。
- 使用 `Serilog.Expressions` 进行过滤(需要 `Serilog.Expressions` 包):`.Filter.ByExcluding("RequestPath like '/health%'")` 排除健康检查噪音;`.WriteTo.Conditional("@l = 'Error'", wt => wt.File("logs/errors-.log", rollingInterval: RollingInterval.Day))` 将错误路由到单独文件。
- 对热路径使用 `[LoggerMessage]` 源生成器:定义 `public static partial class OrderLogs`,添加 `[LoggerMessage(Level = LogLevel.Information, Message = "Order {OrderId} created for {CustomerId}")]` 的 `public static partial void OrderCreated(this ILogger logger, Guid orderId, Guid customerId)`,然后调用 `logger.OrderCreated(order.Id, order.CustomerId)`;内置于 `Microsoft.Extensions.Logging.Abstractions`,编译时生成,级别禁用时零分配。
- 避免字符串插值:不要用 `logger.LogInformation($"Order {orderId} created for {customerId}")`,要用 `logger.LogInformation("Order {OrderId} created for {CustomerId}", orderId, customerId)`。
- 不要跳过 `CloseAndFlush`:不要直接 `app.Run()`;要用 `try { app.Run(); } catch (Exception ex) { Log.Fatal(ex, "Unhandled exception"); } finally { await Log.CloseAndFlushAsync(); }`,否则异步 sink(Seq、OTLP、Elasticsearch)会丢失缓冲事件。
- 不要记录敏感数据:不要记录密码、令牌或 PII,例如不要 `logger.LogInformation("Login: {Email} with password {Password}", email, password)`,只记录 `logger.LogInformation("Login: {Email}", email)`。
- 不要无限制地反结构化:不要直接 `logger.LogInformation("Request: {@Request}", httpContext.Request)`;要配置 `.Destructure.ToMaximumDepth(4).Destructure.ToMaximumStringLength(1024).Destructure.ToMaximumCollectionCount(10)`;更好的做法是用 `.Destructure.ByTransforming<HttpRequest>(r => new { r.Method, r.Path })` 反结构化为特定属性。
- 不要使用已弃用的 Elasticsearch sink:不要使用 `Serilog.Sinks.Elasticsearch` 包和 `.WriteTo.Elasticsearch("http://localhost:9200")`;要使用官方 `Elastic.Serilog.Sinks` 包,并写 `.WriteTo.Elasticsearch([new Uri("https://elastic.example.com:9200")], opts => opts.DataStream = new DataStreamName("logs", "myapp"))`,将 ECS 格式文档写入 data streams。
- 对于审计日志,使用 `AuditTo`(同步,异常传播)。
- 对于按环境的日志级别,在 `appsettings` 中按命名空间使用 `MinimumLevel.Override`。
- 对于 OpenTelemetry 集成,使用 `Serilog.Sinks.OpenTelemetry`(无 SDK 依赖)。
适用边界 · 注意事项
- 非 .NET 应用或不使用 Microsoft.Extensions.Logging 的项目。
- 项目已使用其他日志框架且无法替换或引入 Serilog 依赖。
- 只需要极简日志且不需要结构化、sink、enricher 等高级功能。