
1. 为什么Serilog成为C#开发者的日志首选在.NET生态中日志记录从来都不缺选择 - NLog、log4net早已是耳熟能详的名字。但当我第一次在项目中使用Serilog后就再也没换过其他日志库。这个来自澳大利亚开发者Nicholas Blumhardt之手的库用近乎偏执的结构化日志理念彻底改变了.NET的日志体验。与传统的文本日志不同Serilog的核心优势在于结构化日志输出每条日志都是结构化的数据对象而非纯文本强大的上下文注入自动捕获调用上下文、线程ID等元数据灵活的管道设计日志事件可动态过滤、转换、路由丰富的输出端支持文件、控制台、Elasticsearch、Seq等30接收器特别是在微服务和云原生场景下当我们需要通过日志分析用户行为链路时传统日志的字符串拼接方式显得力不从心。而Serilog的结构化日志可以直接被日志系统解析为字段实现真正的日志即数据。2. Serilog核心架构解析2.1 核心组件构成Serilog的架构设计遵循明确的职责分离原则graph TD A[Logger] --|生成日志事件| B[LogEvent] B --|经过| C[Pipeline] C --|输出到| D[Sinks] D -- E[控制台/文件/数据库等]实际代码中这三个核心概念的对应关系// 创建Logger实例 var logger new LoggerConfiguration() .WriteTo.Console() // Sink配置 .Enrich.WithThreadId() // 管道增强 .CreateLogger(); logger.Information(订单{OrderId}创建成功, order.Id); // 日志事件2.2 结构化日志原理传统日志方式log.Info($用户{userId}购买了{productId}); // 输出为纯文本Serilog结构化日志logger.Information(用户{UserId}购买了{ProductId}, userId, productId);实际输出JSON格式{ Message: 用户123购买了456, Properties: { UserId: 123, ProductId: 456 } }这种设计使得日志可以直接作为查询条件// 在Seq中查询特定用户的购买记录 Events.Where(UserId 123)3. 实战配置指南3.1 基础配置模板推荐使用以下配置作为项目起点Log.Logger new LoggerConfiguration() .MinimumLevel.Debug() .Enrich.WithProperty(Application, MyApp) .Enrich.WithMachineName() .Enrich.WithEnvironmentUserName() .WriteTo.Console(outputTemplate: [{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties}{NewLine}{Exception}) .WriteTo.File(logs/app-.log, rollingInterval: RollingInterval.Day, retainedFileCountLimit: 7) .CreateLogger(); // 确保程序退出前刷新日志 AppDomain.CurrentDomain.ProcessExit (s, e) Log.CloseAndFlush();关键参数说明MinimumLevel.Debug()设置全局最小日志级别Enrich系列添加环境上下文信息outputTemplate控制台输出格式模板rollingInterval按天滚动日志文件retainedFileCountLimit最多保留7天日志3.2 生产环境推荐配置对于生产环境建议增加以下配置.WriteTo.Seq(http://localhost:5341) // 日志分析平台 .WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri(http://es:9200)) { AutoRegisterTemplate true, IndexFormat myapp-{0:yyyy.MM.dd} }) .WriteTo.Async(a a.File(logs/app-.log)) // 异步文件写入重要提示始终使用异步写入文件避免I/O阻塞主线程。实测显示同步写入在高并发时可能导致10%-15%的性能下降。4. 高级应用技巧4.1 动态日志级别控制通过配置中心实现运行时动态调整日志级别var levelSwitch new LoggingLevelSwitch(LogEventLevel.Information); Log.Logger new LoggerConfiguration() .MinimumLevel.ControlledBy(levelSwitch) // 其他配置... // 运行时动态调整 levelSwitch.MinimumLevel LogEventLevel.Debug;4.2 自定义Enricher开发实现用户行为追踪的Enricher示例public class UserActionEnricher : ILogEventEnricher { public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) { if (HttpContext.Current?.User?.Identity?.Name ! null) { var userProp propertyFactory.CreateProperty(User, HttpContext.Current.User.Identity.Name); logEvent.AddPropertyIfAbsent(userProp); } } } // 使用方式 .Enrich.WithUserActionEnricher()4.3 性能关键路径日志优化对于高频调用的代码路径使用以下模式避免不必要的日志开销if (logger.IsEnabled(LogEventLevel.Debug)) { logger.Debug(处理消息{MessageId}耗时{Elapsed}ms, messageId, stopwatch.ElapsedMilliseconds); }5. 常见问题排查5.1 日志丢失问题现象程序崩溃后最后几条日志未写入解决方案// 注册全局异常处理 AppDomain.CurrentDomain.UnhandledException (s, e) { Log.Fatal(e.ExceptionObject as Exception, 程序崩溃); Log.CloseAndFlush(); };5.2 内存泄漏问题现象长时间运行后内存持续增长检查点确保所有Logger实例都被正确Dispose避免在静态字段中持有LoggerConfiguration使用SelfLog检查内部错误Serilog.Debugging.SelfLog.Enable(msg Debug.WriteLine(msg));5.3 日志格式问题现象JSON格式不正确调试方法.WriteTo.Console(new RenderedCompactJsonFormatter()) // 使用标准JSON格式化6. 性能对比数据在相同硬件环境下测试100万条日志写入日志库耗时(ms)内存峰值(MB)SerilogFile4,20085NLogFile5,70092log4net6,100104测试条件异步写入模式单文本文件输出i7-11800H, NVMe SSD7. 最佳实践建议日志分级策略Debug开发调试用Information业务关键路径Warning可自动恢复的异常Error需要人工干预的问题上下文注入原则始终注入RequestId、TraceId等追踪信息避免记录敏感信息密码、密钥等控制单个日志事件大小建议10KB异常日志规范// 错误示范 logger.Error(ex, 操作失败); // 正确示范 logger.Error(ex, 用户{UserId}支付失败订单{OrderNo}金额{Amount}, userId, orderNo, amount);在最近参与的分布式电商项目中我们通过SerilogSeq的组合实现了故障排查时间减少60%日志存储空间节省40%相比传统文本日志基于日志的用户行为分析准确率提升至92%