其他
在 .NET/ASP.NET 服务中搭建可观测性时…
为 .NET 10/ASP.NET 服务从零搭建可观测性、配置 /health 端点与 Correlation ID、制定日志级别策略时使用。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 57/100 ×0.15
适用场景
在 .NET/ASP.NET 服务中搭建特定
解决思路
- 采用 Serilog 做结构化日志:每条日志是带命名属性的结构化事件,而不是格式化字符串,以便搜索、过滤和告警。
- Serilog 完整设置(two-stage bootstrap、AddSerilog()、sinks、enrichers)交给 serilog 技能;遵循该技能中 AddSerilog() 优于 UseSerilog() 的规范。
- 采用 OpenTelemetry 做分布式追踪:traces 连接跨服务请求,metrics 跟踪系统长期健康状态;完整设置交给 opentelemetry 技能。
- 为每个服务暴露 /health 端点供负载均衡器和编排器使用;liveness 与 readiness 是不同问题,使用不同端点。
- 为每个请求生成唯一 Correlation ID,并让它流经所有日志条目和下游服务调用,使一条用户投诉映射到一条过滤后的日志流。
- 接线顺序:先接日志(需要日志调试其余部分),再接健康检查,最后接 tracing。
- 分工:结构化应用日志由 Serilog(AddSerilog())负责;请求摘要日志由 UseSerilogRequestLogging() 负责;traces + metrics + OTLP export 由 OpenTelemetry SDK 负责;健康端点、Correlation ID、日志级别策略由本技能负责。
- 创建 CorrelationIdMiddleware(RequestDelegate next) 中间件,使用 header 名 X-Correlation-Id。
- 在 InvokeAsync 中从 context.Request.Headers[CorrelationIdHeader].FirstOrDefault() 读取 correlation ID;若不存在则用 Guid.NewGuid().ToString() 生成。
- 将 correlationId 写入 context.Items["CorrelationId"],并设置 context.Response.Headers[CorrelationIdHeader] = correlationId。
- 用 using (LogContext.PushProperty("CorrelationId", correlationId)) 包裹 await next(context),使该请求作用域内每条日志事件自动带上该属性。
- 在 Program.cs 中尽早注册 app.UseMiddleware<CorrelationIdMiddleware>(),确保所有下游日志都携带该 ID。
- 出站 HttpClient 调用通过 DelegatingHandler 传播同一个 header(见 httpclient-factory 技能)。
- 为什么用中间件:在管道边缘推送一次属性,即可附加到请求作用域内所有日志事件,无需在每个调用点手工传递。
- 在 Program.cs 中调用 builder.Services.AddHealthChecks() 注册健康检查。
- 用 .AddNpgSql(builder.Configuration.GetConnectionString("Default")!, name: "database", tags: ["ready"]) 添加数据库检查。
- 用 .AddRedis(builder.Configuration.GetConnectionString("Redis")!, name: "redis", tags: ["ready"]) 添加 Redis 检查。
- 用 .AddRabbitMQ(builder.Configuration.GetConnectionString("RabbitMq")!, name: "rabbitmq", tags: ["ready"]) 添加 RabbitMQ 检查。
- 映射 /health/live:app.MapHealthChecks("/health/live", new HealthCheckOptions { Predicate = _ => false });不执行依赖检查,只回答“我是否在运行”。
- 映射 /health/ready:app.MapHealthChecks("/health/ready", new HealthCheckOptions { Predicate = check => check.Tags.Contains("ready") })。
- 区分两个端点的原因:liveness 失败意味着“重启我”;readiness 失败意味着“停止发流量”;混在一起会导致数据库变慢时应用循环重启。
- 定义日志级别用途:Debug 用于诊断细节、payload dump(生产永不含 PII);Information 用于业务事件(下单、任务完成);Warning 用于可恢复异常(触发重试、使用回退);Error 用于需要关注的操作失败;Fatal/Critical 用于应用无法继续。
- 设置环境默认值:Debug 仅 Development;Information 用于 Dev + staging;Warning 用于所有环境并作为生产默认;Error 所有环境;Fatal/Critical 所有环境。
- 生产默认 Warning 的原因:规模化下 Information 级请求噪音会显著增加日志存储成本并淹没信号;通过命名空间覆盖保留真正的业务事件 Information(见 serilog 技能的 MinimumLevel.Override 模式)。
- 不要记录敏感数据:避免 logger.LogInformation("User logged in: {Email} with password {Password}", email, password)。
- 改为记录标识符,绝不在 Information 级别记录 secrets 或 PII:logger.LogInformation("User {UserId} logged in", userId)。
- 不要跳过健康检查 tags:不要只写 app.MapHealthChecks("/health"),否则所有检查会在 liveness 和 readiness 都运行。
- 正确做法是分开 liveness 与 readiness,使用 /health/live 与 /health/ready 及 predicate。
- 不要重新实现由所属技能提供的内容:不要凭记忆手写 Serilog bootstrap,不要使用 legacy API builder.Host.UseSerilog(...)。
- 正确做法是加载 serilog 技能并使用其两阶段 AddSerilog() bootstrap,例如 builder.Services.AddSerilog((services, lc) => lc.ReadFrom.Configuration(builder.Configuration)...)。
- 应用日志设置:加载 serilog 技能,使用 AddSerilog() 两阶段 bootstrap。
- 分布式追踪/指标:加载 opentelemetry 技能,使用 OTLP exporter。
- 自定义业务指标:使用 IMeterFactory + counters/histograms(opentelemetry 技能)。
- 请求追踪:使用 Correlation ID 中间件(本技能)。
- 容器健康:使用 /health/live 和 /health/ready 端点(本技能)。
- 日志存储:开发用 Seq;生产用 Elastic/Grafana/OTLP 后端。
- 日志级别:开发用 Debug,staging 用 Information,生产默认 Warning。
适用边界 · 注意事项
- 需要深入配置 Serilog(two-stage bootstrap、sinks、enrichers)时,应加载 serilog 技能,而不是用本技能。
- 需要深入配置 traces、metrics 或 OTLP export 时,应加载 opentelemetry 技能,而不是用本技能。
- 非 .NET/ASP.NET 技术栈不适用本技能的代码与端点方案。