ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Serilog结构化日志在.NET中的实践与优化

Serilog结构化日志在.NET中的实践与优化 1. 结构化日志的价值与Serilog的定位在传统的.NET日志记录实践中我们最常看到的是这样的日志输出2023-07-20 14:30:45 [INF] User login successful for user123 2023-07-20 14:31:02 [ERR] Database connection timeout这种纯文本日志看似直观但在实际生产环境中暴露出三个致命缺陷检索困难当需要查找所有包含特定用户ID的登录记录时不得不使用正则表达式或全文搜索分析受限无法对日志字段进行聚合统计如按错误类型分组计数上下文缺失异常发生时无法自动关联同一请求链路上的所有相关日志结构化日志通过将日志转换为机器可读的数据结构通常是JSON来解决这些问题。以Serilog生成的日志为例{ t: 2023-07-20T14:30:45.123456Z, l: Information, mt: User {Username} login from {IP}, Username: user123, IP: 192.168.1.100, RequestId: a1b2c3d4, SessionId: z9y8x7w6 }Serilog作为.NET生态中最成熟的结构化日志框架其核心优势体现在原生JSON支持内置CompactJsonFormatter等格式化器上下文丰富通过LogContext自动注入调用链信息扩展性强100官方和社区开发的Sink输出目标性能优异基准测试显示其吞吐量比NLog高20%实际项目中我们发现采用结构化日志后故障排查时间平均缩短了40%特别是在微服务架构中当请求需要跨越多个服务时结构化日志中的TraceId能快速串联整个调用链路。2. Serilog核心组件与配置实战2.1 基础环境搭建首先通过NuGet安装核心包dotnet add package Serilog dotnet add package Serilog.AspNetCore dotnet add package Serilog.Sinks.Console dotnet add package Serilog.Sinks.File推荐采用代码式配置Code-as-configuration而非纯JSON配置这样能获得更好的类型安全和智能提示Log.Logger new LoggerConfiguration() .MinimumLevel.Debug() .Enrich.FromLogContext() .WriteTo.Console( outputTemplate: [{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}) .WriteTo.File( formatter: new CompactJsonFormatter(), path: logs/app-.log, rollingInterval: RollingInterval.Day) .CreateLogger();2.2 关键配置解析MinimumLevel设置全局日志级别支持动态调整.MinimumLevel.ControlledBy(new LoggingLevelSwitch(LogEventLevel.Information))Enrich丰富日志上下文.Enrich.WithProperty(Application, OrderService) .Enrich.WithMachineName() .Enrich.WithEnvironmentUserName()Filter灵活过滤日志.Filter.ByExcluding(e e.Properties.TryGetValue(SourceContext, out var value) value.ToString().Contains(Microsoft.EntityFrameworkCore))2.3 与ASP.NET Core集成Program.cs的推荐配置方式builder.Host.UseSerilog((ctx, config) config.ReadFrom.Configuration(ctx.Configuration) .Enrich.FromLogContext());在控制器中使用时与传统ILogger完全兼容public class OrderController : ControllerBase { private readonly ILoggerOrderController _logger; public OrderController(ILoggerOrderController logger) { _logger logger; } [HttpGet({id})] public IActionResult GetOrder(int id) { _logger.LogInformation(Getting order {OrderId} for user {UserId}, id, User.Identity.Name); // ... } }3. 高级特性与性能优化3.1 日志上下文增强通过LogContext推入上下文信息using (LogContext.PushProperty(TransactionId, Guid.NewGuid())) { _logger.LogInformation(Processing payment); // 该作用域内所有日志自动包含TransactionId字段 }3.2 自定义日志格式器实现ILogEventFormatter创建自定义格式public class CustomJsonFormatter : ILogEventFormatter { public void Format(LogEvent logEvent, TextWriter output) { var json new JObject( new JProperty(time, logEvent.Timestamp), new JProperty(level, logEvent.Level), new JProperty(message, logEvent.RenderMessage())); output.Write(json.ToString(Formatting.None)); } }3.3 性能关键配置异步批量写入对于Elasticsearch等远程Sink.WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri(http://localhost:9200)) { BatchPostingLimit 50, Period TimeSpan.FromSeconds(2), BufferBaseFilename ./logs/elasticbuffer })控制日志量避免过度记录.MinimumLevel.Override(Microsoft.EntityFrameworkCore, LogEventLevel.Warning)对象序列化优化// 避免全量序列化大对象 _logger.LogInformation(Processing order {Order}, new { order.Id, order.CustomerName });4. 生产环境最佳实践4.1 日志生命周期管理建议在应用启动和关闭时添加标记日志try { Log.Information(Starting application); await app.RunAsync(); } catch (Exception ex) { Log.Fatal(ex, Application terminated unexpectedly); } finally { Log.Information(Application shutting down); Log.CloseAndFlush(); // 确保所有缓冲日志被写入 }4.2 敏感信息处理配置Destructurer避免记录敏感字段.Destructure.ByTransformingCreditCard(cc new { cc.LastFourDigits, cc.ExpiryDate })4.3 与监控系统集成以Grafana Loki为例的配置.WriteTo.Grafana.Loki(http://localhost:3100, labels: new ListLokiLabel { new() { Key app, Value payments } }, textFormatter: new LokiJsonTextFormatter())4.4 典型问题排查日志丢失检查BufferBaseFilename配置和磁盘空间性能下降检查是否有同步Sink阻塞主线程字段缺失确认LogContext.PushProperty的作用域我们在金融项目中曾遇到一个典型案例当系统负载高时日志突然停止记录。最终发现是File Sink的共享锁冲突通过改用异步File Sink并设置合适的buffer大小解决了问题。
返回列表