8.2 环境配置与监控日志 环境机制让一份产物跑出三套行为:环境变量指定环境名,配置系统按名字加载对应配置文件(2.3 节分层链的生产应用);监控则把观察哨转正——健康检查端点给探活方看、结构化日志给人看、关键指标给大盘看,三条通道合起来构成生产观察面。 产物落位之后,本章最后一节回答两个问题:怎么让测试与生产用同一份产物但行为不同;以及服务跑起来后靠什么知道它"还活着、活得怎么样"。观察哨在全书最后一站转正上岗——从教学实验的临时哨变成生产编制的常驻哨。 环境变量是环境切换的开关 机制一句话讲完:主机启动时读环境变量里的环境名(默认 Production),配置系统按这个名字追加加载对应配置文件。
环境机制让一份产物跑出三套行为:环境变量指定环境名,配置系统按名字加载对应配置文件(2.3 节分层链的生产应用);监控则把观察哨转正——健康检查端点给探活方看、结构化日志给人看、关键指标给大盘看,三条通道合起来构成生产观察面。
产物落位之后,本章最后一节回答两个问题:怎么让测试与生产用同一份产物但行为不同;以及服务跑起来后靠什么知道它"还活着、活得怎么样"。观察哨在全书最后一站转正上岗——从教学实验的临时哨变成生产编制的常驻哨。
机制一句话讲完:主机启动时读环境变量里的环境名(默认 Production),配置系统按这个名字追加加载对应配置文件。做一遍实验看分层效果:
// 应用里只写一段代码:打印当前环境与某配置项 var app = builder.Build(); app.MapGet("/env", () => new { Env = app.Environment.EnvironmentName, Flag = app.Configuration["DevOnly:Flag"] ?? "(未配置)" }); app.Run();
# 场景一:不设置环境名(默认生产) dotnet run # GET /env → { "env": "Production", "flag": "(未配置)" } # 场景二:声明为开发环境(开发配置文件生效) set ASPNETCORE_ENVIRONMENT=Development dotnet run # GET /env → { "env": "Development", "flag": "dev-only-value" } # 该值来自开发专属配置文件,生产环境根本不加载它 # 场景三:测试环境的键靠环境变量逐项覆盖(容器部署的标准姿势) set ASPNETCORE_ENVIRONMENT=Staging set Obs__Tag=staging-by-env
三层组合构成完整的环境策略:环境名选择配置文件(整块切换,如连接串、日志级别);环境变量覆盖单个键(点对点修正,双下划线约定 2.3 节讲过);代码里按环境分支只留最少必要处(能靠配置就别靠 if)。判断一个配置项该放哪层的口诀:随环境变的进配置文件,随部署实例变的进环境变量,随代码逻辑变的才进代码。
⚠️ 常见坑:配置文件分环境写错了方向——把生产的连接串写进开发配置文件(或反过来),本地一跑就连上了生产库。纪律是:环境专属文件里只放该环境的差异项,共同项留主文件;生产机密只走环境变量或密钥服务,永远不进代码库。
健康检查端点是给"机器"看的观察哨:编排器、负载均衡器按周期访问它,凭状态码决定流量去留。接入非常轻:
// 注册健康检查服务,一个检查器盯一个依赖 builder.Services.AddHealthChecks() .AddCheck("self", () => HealthCheckResult.Healthy("进程存活")) .AddCheck("db", async () => { // 真实场景:对数据库发一条轻量探测 var canConnect = await db.Database.CanConnectAsync(); return canConnect ? HealthCheckResult.Healthy("数据库连通") : HealthCheckResult.Unhealthy("数据库不可达"); // 503,探活方摘流量 }); var app = builder.Build(); app.MapHealthChecks("/health"); // 端点默认:全 Healthy 返回 200,任一 Unhealthy 返回 503
# 观察输出: # 数据库正常时 GET /health → 200 # 数据库断开时 GET /health → 503(编排器随即把该实例摘出负载)
分层探活是容器编排的标配契约:存活探针答"进程要不要重启"(用轻量的 self 检查,别把依赖故障误杀进程);就绪探针答"要不要给我流量"(用含数据库的完整检查)。两个问题分开回答,数据库抖动时实例不会被连环重启,只是暂时不接新请求——两种探针用错对方,故障会被放大而不是收敛。
生产日志的三原则是结构化(字段化事件,2.3 节的占位符写法)、分级(按严重度分流,生产默认 Information 起步、排查时临时调 Debug)、集中(多实例日志汇到一处,靠 TraceId 串请求,2.3 节的作用域字段在这里变现)。日志回答"发生了什么",指标回答"整体怎么样"——最小指标集四项:请求速率(容量规划)、错误率(健康总闸)、耗时分布(体验水位)、线程池状态(6.3 节的饥饿前兆)。这四项接任意指标方案都行,缺一项都是观察面漏洞。

背景:凌晨两点错误率告警把值班工程师叫醒,用户反馈下单偶发失败。操作:按三通道顺序处置——大盘确认错误率从 0.2% 抬到 5%,耗时分布 p95 从 200ms 涨到 3s(指标定性与定量);健康检查面板显示就绪探针间歇 503,存活探针全绿(依赖抖动而非进程崩溃,流量已自动摘除一部分);按告警时段过滤日志,TraceId 聚出一批"数据库命令超时",全部指向订单表的插入语句。结果:临时扩容数据库连接、次日定位到索引缺失导致的锁等待,补索引后指标回落。解读:三条通道各答一问——指标说"哪里坏了多严重",健康检查说"要不要摘流量",日志说"根因是什么"。只建日志不建指标的项目,故障发现靠用户投诉;只建指标不建日志的项目,知道了"坏"却答不出"为什么"。变式:小项目可以把三通道全并进一套托管方案(云日志加云监控加容器探活),机制不变、运维减负。
需要的是同一套机制的轻量版:健康检查端点本地也有(顺手验证检查器逻辑),日志开着控制台结构化输出(2.3 节已配好),指标在压测时临时接上。观察哨的价值在平时练成肌肉记忆——生产事故不是学工具的时候。
💡 关键直觉:环境机制管"一份产物多种人格",观察面管"黑盒变成玻璃盒"。前者靠配置系统(第 2 章的地基),后者靠健康检查、日志、指标三件套——全书的知识在这里完成最后一次会师。