给本产品接上可观测性
把生产后端的「跑挂了怎么知道」落到本仓库的 server.js
- 理解可观测性三大支柱:日志、指标、追踪
- 会把 Sentry DSN 接进 Next.js 并看懂最小配置
- 能用结构化 JSON 日志代替零散 console 输出
- 能写一个 /health 健康检查端点并设一条告警阈值
车能开不代表没毛病,仪表盘就是给你看的:转速表告诉你转得快不快,油温灯告诉你是不是过热,故障灯亮了就是「出事了」。服务端也一样——跑起来只是第一步,日志、健康检查、告警就是它的仪表盘。
为什么 s8 还要补这一课
s8-m3-l1 讲了「怎么把产品跑起来」:构建、部署、server.js 起服务。但产品上线后,最怕的是它悄悄挂了、你却不知道。可观测性解决的就是「跑挂了怎么第一时间知道、知道了怎么定位」。
三大支柱
# 可观测性看三样东西
print("""
1. 日志 logs :谁、什么时候、干了什么、报了什么错
2. 指标 metrics:CPU/内存/错误率/延迟,随时间的数字曲线
3. 追踪 traces :一次请求穿过了哪几跳,慢在哪一跳
""")第一步:Sentry 抓前端报错
Sentry 把浏览器里未捕获的异常连同堆栈、用户环境一起上报到它的面板。你只要拿一个 DSN(一串项目地址),在 Next.js 初始化时配进去。DSN 是项目级密钥,从环境变量读,别硬编码进仓库。
# Sentry 接进 Next.js(概念示意,SDK 版本与配置项以官方最新为准)
print("""
// instrumentation-client.ts:客户端初始化
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, // 环境变量注入,不写死
tracesSampleRate: 0.1, // 采样 10%,省配额
debug: false,
});
""")NEXT_PUBLIC_ 前缀的变量会打进前端包,所以它本来就是公开可见的;但私钥(服务端用的)绝不能加这个前缀。具体哪些变量放哪,以 Sentry 官方文档为准。
第二步:server.js 打结构化 JSON 日志
现在 node server.js 里 console.log("listen on 3000") 是给人看的一句话,机器没法按字段过滤。Pino 的思路是:一行就是一个 JSON 对象,level/msg/port 各自成键,采集系统就能按 level=error 筛。下面用 Python 模拟这种 JSON 日志格式。
第三步:暴露 /health 健康检查
外部监控每隔几秒打一次 /health。它不该只看「进程在不在」,而要看「它能不能正常干活」——比如数据目录还在不在、能不能正常返回。
# server.js 里加一个 /health 端点(概念示意)
print("""
// 除了静态文件,再开一个探活接口
app.get("/health", (req, res) => {
const dataOk = fs.existsSync(DATA_DIR); // 数据目录在不在
res.status(dataOk ? 200 : 503).json({
status: dataOk ? "ok" : "down",
uptime: process.uptime(),
});
});
""")一条最简单的告警阈值
监控项:定时访问 /health触发条件:连续 3 次(约 30 秒)拿不到 200动作:立刻通知到运维补充:5xx 错误率 > 1% 持续 1 分钟,再配一条原则:阈值先粗后细,别一上来铺满屏指标,告警太多等于没告警。
日志级别:debug / info / warn / error 怎么用
结构化日志里最容易乱用的就是 level。什么都打 info,等于没有级别;动不动打 error,告警铃一响你就麻木了。业界通用四档,记牢分工:debug 是开发时才看的细节,上线后默认关掉;info 是正常业务流水(谁登录了、哪个订单创建了);warn 是不影响流程但值得留意的异常分支(重试了一次、缓存 miss 了);error 是真的出错了、这个请求没成功、需要人介入。判断口诀:能不能正常返回?正常返回最多 warn;返回了但走了异常分支是 warn;这个请求彻底失败、用户没拿到结果,才是 error。
debug:开发调试细节,生产默认关闭,如「进入函数X,参数=…」info:正常业务流水,如「用户登录成功」「订单已创建」warn:异常但可自愈/有兜底,如「第三方接口超时已重试」error:请求失败需介入,如「数据库写不进去」「支付回调处理失败」
错一:把预期内的分支打 error。比如用户输错验证码,这是正常业务分支,打 warn 都勉强,打 error 会让告警系统被垃圾淹没。错二:error 之后不带上下文。只写「出错了」三个字,没有 userId、没有请求参数、没有堆栈,等于没打。
traceId:把一次请求的散落日志串起来
一个用户点一下按钮,请求可能穿过前端 → 网关 → 业务服务 → 数据库,每一跳都打自己的日志。没有 traceId,你只能按时间猜哪几条是同一次请求;有了 traceId,就在入口生成一个唯一 ID,一路往下传,每跳日志都带上它,最后按这个 ID 一搜,整条链路全出来。
告警阈值怎么定:避免告警疲劳
告警最怕的不是漏报,是狼来了。一个人每天收到 50 条告警,其中 48 条是误报或无关痛痒,真出事那两条他反而会忽略。定阈值的原则:先从最粗、最不可能误报的开始,跑两周看看误报率,再慢慢收紧;每条告警都必须带「这是什么、影响谁、怎么处理」的一键手册;同一类告警 5 分钟内合并成一条,别把 100 个用户的报错当成 100 条告警发出来。
团队开始手动屏蔽告警群、告警来了没人立刻看、值班人开始说「这条先不管」——出现任何一个,立刻回去砍告警规则,而不是加人。
Sentry 前端错误 vs 服务端日志:分工
很多人以为接了 Sentry 就不用打服务端日志了,其实它俩管的是不同位置。Sentry 擅长抓「浏览器里崩了什么」:未捕获的 JS 异常、前端组件渲染报错、用户操作到哪一步崩的,连带着浏览器版本、屏幕分辨率、用户环境一起上报。服务端日志(Pino 那套)擅长抓「后端业务发生了什么」:谁登录了、哪个订单失败了、数据库慢在哪。前端报的错要能对应到服务端的某个 traceId,两边一起看才完整。
/health 健康检查应该检查什么?
用户登录时输错了一次验证码,这次操作该打什么级别的日志?
这里把 Sentry 上报、JSON 日志、/health、粗阈值告警接进 server.js 就够;s15-m7-l1 才系统讲通用生产工程——12-Factor 配置外置、无状态进程、限流与幂等、日志采集栈与 APM 链路平台、备份恢复演练。这里管「接得上」,那边管「为什么这么设计」。
资深工程师加餐
底层原理 · 大厂视角 · 工程经验,点卡片展开
快速连续发起请求时,先发的慢响应可能晚到、反而覆盖了新结果,导致页面显示「过期数据」。标准解法是给每次请求一个递增序号,或用 AbortController 取消上一个请求,回调里只接受最新一次的结果。此外 loading、error、空数据这三种状态都要显式处理,别只写成功路径。
挑战任务
健康状态分级器
实现 health_status(cpu, mem, disk):CPU≥90 或 内存≥90 或 磁盘≥95 → 返回 down;否则 CPU≥70 或 内存≥70 或 磁盘≥80 → 返回 degraded;都正常返回 ok。
课后作业
场景分析:哪些该打 error 日志
下面 5 个场景,判断哪些该打 error(True)、哪些不该(False)。把判断写进 should_be_error 列表,顺序对应①~⑤。 ① 数据库写订单失败,用户没收到确认 ② 用户输错验证码(第二次输对了) ③ 第三方接口超时,已自动重试成功 ④ 支付回调通知丢了,钱已扣但订单未更新 ⑤ 服务器正常启动,监听 3000 端口