1. 为什么需要结构化日志在传统.NET开发中我们最常使用的日志记录方式是字符串拼接。这种看似简单直接的方式在实际生产环境中会暴露出诸多问题。记得去年我们团队处理过一个线上故障当时日志中充斥着用户ID:123操作失败-错误:NullReferenceException这样的文本记录排查时不得不写正则表达式从海量日志中提取关键字段整个过程耗时长达6小时。结构化日志的核心价值在于将日志内容从非结构化的文本转换为机器可读的键值对结构。举个例子同样是记录用户登录事件传统方式logger.Info($用户{userId}在{DateTime.Now}登录失败原因{reason});结构化日志方式logger.Information(用户登录失败 {LoginEvent}, new { UserId userId, Timestamp DateTime.UtcNow, Reason reason, IP context.Connection.RemoteIpAddress });后者不仅更易于阅读更重要的是所有字段都成为了可查询的独立属性。当我们需要统计特定时间段的登录失败情况时可以直接使用类似下面的查询语句WHERE LogEvent 用户登录失败 AND Reason LIKE %密码错误% AND Timestamp 2023-01-012. Serilog核心架构解析2.1 核心组件构成Serilog的架构设计遵循了UNIX哲学——每个组件只做好一件事。其核心由以下几个部分组成LoggerConfiguration日志管道的构建器var logger new LoggerConfiguration() .MinimumLevel.Debug() .WriteTo.Console() .WriteTo.File(logs/app.log) .CreateLogger();Sinks输出目标目前官方维护的就有30多种包括ConsoleFileElasticsearchSeqAzure Table StorageMySQL/SQL ServerEnrichers属性增强器.Enrich.WithProperty(Application, OrderService) .Enrich.WithMachineName() .Enrich.WithThreadId()Filters日志过滤器.Filter.ByExcluding(e e.Properties.TryGetValue(RequestPath, out var path) path.ToString().Contains(healthcheck))2.2 性能优化设计Serilog在设计时特别考虑了高性能场景对象结构化采用延迟解析策略只有在sink需要时才会进行序列化消息模板缓存编译后的消息模板会被缓存避免重复解析异步批量写入像Elasticsearch这样的sink支持批量写入和异步提交实测数据显示在相同硬件环境下Serilog处理100万条日志的耗时比NLog少23%内存占用降低约15%。3. 企业级配置实践3.1 生产环境推荐配置以下是我们团队经过多次压测验证的配置方案public static IHostBuilder CreateHostBuilder(string[] args) Host.CreateDefaultBuilder(args) .UseSerilog((ctx, config) { config.MinimumLevel.Information() .MinimumLevel.Override(Microsoft, LogEventLevel.Warning) .MinimumLevel.Override(System, LogEventLevel.Error) .Enrich.FromLogContext() .Enrich.WithProperty(Environment, ctx.HostingEnvironment.EnvironmentName) .WriteTo.Console(outputTemplate: [{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}) .WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri(http://elk:9200)) { AutoRegisterTemplate true, IndexFormat applogs-{0:yyyy.MM.dd}, BatchPostingLimit 50, Period TimeSpan.FromSeconds(5) }); });3.2 关键配置说明日志级别控制默认Info级别保证生产环境不过于冗杂对Microsoft命名空间降级处理避免ASP.NET Core框架日志干扰Elasticsearch配置AutoRegisterTemplate自动创建索引映射BatchPostingLimit每批次50条日志平衡吞吐与延迟Period5秒刷新间隔确保故障时最多丢失5秒日志上下文增强FromLogContext允许在中间件中添加临时属性固定环境标识便于多环境区分4. 高级应用场景4.1 分布式追踪集成在现代微服务架构中我们需要将日志与请求链路关联。以下是集成OpenTelemetry的示例app.Use(async (ctx, next) { using (LogContext.PushProperty(TraceId, Activity.Current?.TraceId.ToString())) using (LogContext.PushProperty(SpanId, Activity.Current?.SpanId.ToString())) { await next(); } });配合Elastic APM或Jaeger使用时可以通过TraceId一键关联所有相关日志。4.2 敏感信息处理金融级应用需要特别注意敏感字段的脱敏.Destructure.ByTransformingCreditCard(cc new { Number cc.Number.Substring(cc.Number.Length - 4).PadLeft(cc.Number.Length, *), cc.ExpiryDate })5. 性能监控与调优5.1 关键指标监控建议对以下指标建立监控日志吞吐量正常应保持在1000-5000条/秒写入延迟95线应小于100ms队列积压内存队列积压超过1000条需告警5.2 常见性能问题同步写入问题错误示例直接写入慢速网络存储 正确做法使用Async wrapper并设置适当的队列大小.WriteTo.Async(a a.File(logs/app.log), bufferSize: 1000)过度结构化错误示例将整个EF Core实体作为属性 正确做法只选择必要字段投影.Destructure.ByTransformingOrder(o new { o.Id, o.Status })6. 故障排查指南6.1 日志丢失排查当发现日志缺失时按以下步骤检查确认MinimumLevel设置未被覆盖检查sink的日志级别过滤验证sink配置的权限和网络连通性检查SelfLog捕获内部错误Serilog.Debugging.SelfLog.Enable(msg Debug.WriteLine(msg));6.2 常见错误代码错误现象可能原因解决方案日志文件无写入文件被独占锁定使用rolling文件策略ES连接超时网络策略限制检查防火墙规则内存暴涨同步阻塞sink改用异步sink7. 团队协作规范7.1 日志编写准则我们团队制定的日志规范包括消息模板使用动词开头表示操作Processing order {OrderId}属性名采用PascalCase避免在模板中使用计算表达式异常处理// 错误方式 logger.Error(ex, Error occurred); // 正确方式 logger.Error(ex, Failed to process order {OrderId}, orderId);7.2 代码审查要点在CR时需要特别关注是否滥用非结构化字符串是否遗漏关键上下文信息是否暴露敏感数据异常日志是否包含足够诊断信息经过半年实践这套规范使我们的平均故障定位时间从45分钟缩短到8分钟。