人人都会AI编程

日志收集、性能监控、错误告警

更新时间:2026-07-10

上一节我们讨论了使用 Docker 和 CI/CD 实现自动化部署,应用上线后还需要一套完整的可观测性体系来保证服务的健康运行。日志收集、性能监控和错误告警三者相辅相成,是线上排错和容量规划的基石。本节聚焦 Node.js 技术栈下这三项能力的落地实践,力求真实可用。

19.4.1 日志收集:从控制台到集中式平台

Node.js 应用的日志不止是 console.log 的输出。在生产环境下,日志需要具备结构化、可分级、可集中存储的特性,以满足搜索、过滤和聚合的需求。

1. 日志的三个基本要求

  • 结构化:采用 JSON 格式输出,每条日志包含 timestamplevelmessagecontext 等字段,方便下游解析。
  • 分级:区分 error/warn/info/debug/trace 等不同严重程度,通过环境变量控制输出等级。
  • 异步无阻塞:日志 I/O 操作绝不能阻塞事件循环,因此推荐使用 pino 等专为性能设计的日志库。

2. 日志框架选择

目前 Node.js 生态中最具代表性的两个日志库是 winstonpino

  • winston:历史悠久,支持多种传输(控制台、文件、HTTP、流),可搭配 winston-daily-rotate-file 实现日志轮转。适合对功能完备性要求较高的项目。
  • pino:以极致性能著称,基准测试中吞吐量比 winston 高数倍。pino 默认输出 JSON 至 stdout,通过管道可以重定向到文件或日志收集器,本身不直接执行文件写入,避免磁盘 I/O 影响主线程。

推荐在生产环境中使用 pino 或类似的轻量级日志器,配合操作系统级的日志收集器(如 Fluentd、Filebeat)转发日志,避免在应用进程内进行复杂的日志轮转和网络发送。

3. 本地开发与生产环境的不同策略

  • 本地开发:使用 pino-prettywinston 的格式化输出,人类可读。
  • 生产环境:输出纯 JSON 到标准输出(stdout),由容器编排平台(如 Docker、Kubernetes)或日志代理收集。例如,在 Kubernetes 中,容器控制台输出自动被采集到集群级别的日志系统中(如 Elasticsearch + Kibana 或 Loki + Grafana)。

常用架构:

Node.js App → stdout (JSON) → Fluentd/Filebeat → Elasticsearch → Kibana

或更轻量的 Grafana 全家桶:

Node.js App → stdout → Promtail → Loki → Grafana

4. 日志轮转与保留策略

如果在应用内写文件日志,可以使用 winston-daily-rotate-file 定期切割日志,并设定最大保留天数。但在容器化部署中,通常建议让日志流出容器,由宿主机或日志收集器统一管理轮转,容器本身不做持久化。

19.4.2 性能监控:洞察运行时关键指标

性能监控的目标是实时掌握进程的内部状态,在问题出现之前发现异常趋势。Node.js 应用需要关注的核心指标包括:

  • 事件循环延迟(Event Loop Lag):反映主线程的繁忙程度,延迟过高意味着 CPU 负载接近饱和。
  • 内存使用:V8 堆内存总量、使用量、外部内存(Buffer)等,关注是否存在内存泄漏。
  • CPU 使用率:用户态与系统态的 CPU 占比。
  • 请求吞吐量与响应时间:每秒请求数(RPS)、p50/p95/p99 延迟。
  • 异步资源与活动句柄:打开的文件描述符数、活跃的异步请求数等,监控资源泄漏。

1. 应用内指标暴露

prom-client 是 Node.js 中事实上的 Prometheus 客户端,它收集并暴露符合 Prometheus 格式的指标。基础用法:

const client = require('prom-client');
const collectDefaultMetrics = client.collectDefaultMetrics;
collectDefaultMetrics({ prefix: 'app_' }); // 自动收集进程、事件循环等默认指标

// 自定义指标:请求计数器
const httpRequestCounter = new client.Counter({
  name: 'http_requests_total',
  help: 'Total number of HTTP requests',
  labelNames: ['method', 'route', 'status']
});

// 在 HTTP 中间件中增加计数
app.use((req, res, next) => {
  res.on('finish', () => {
    httpRequestCounter.inc({
      method: req.method,
      route: req.route?.path || req.path,
      status: res.statusCode
    });
  });
  next();
});

// 暴露 /metrics 端点
app.get('/metrics', async (req, res) => {
  res.set('Content-Type', 'text/plain');
  res.end(await client.register.metrics());
});

然后由 Prometheus 服务器定期拉取 /metrics 路径,存入时序数据库,再通过 Grafana 进行可视化。

2. PM2 内置监控

如果使用 PM2 管理进程,其自带的 pm2 monit 可以实时查看每个进程的 CPU、内存和事件循环延迟。此外,PM2 也可以通过 pm2 web 暴露一个简单的 JSON API,或者集成 PM2 Plus(付费)获取更丰富的面板和告警功能。

对于小型项目,PM2 的本地监控已足够排查问题,但它不适合多机器聚合,更适合单机部署。

3. 应用性能管理(APM)工具

APM 工具提供更深入的分析能力,例如请求级追踪、慢端点分析、数据库查询延迟等。热门的 Node.js APM 包括:

  • Elastic APM:与 ELK Stack 深度集成,自动检测 Express、Koa 等框架的路由,并记录数据库调用和外部请求。
  • DataDog / New Relic:商业产品,功能全面,但成本较高。
  • OpenTelemetry:CNCF 主导的开放标准,支持分布式追踪、指标和日志的收集,可与 Jaeger、Zipkin 等后端对接,是未来的趋势。

APM 的引入通常只需要添加一个 SDK 并配置服务地址,无需大幅修改业务代码。对于复杂微服务系统,分布式追踪可以清晰展示请求的完整调用链,定位瓶颈点。

19.4.3 错误告警:第一时间响应异常

错误告警体系的目标是:当生产环境出现异常时,能够及时通知到相关负责人,并附带足够的上下文信息用于快速定位

1. 全局错误捕获

Node.js 进程一旦抛出未捕获的异常(uncaughtException)或未处理的 Promise 拒绝(unhandledRejection),默认行为是直接退出或产生警告。线上应用必须注册全局处理器来记录错误并优雅关闭:

process.on('uncaughtException', (error) => {
  logger.fatal('未捕获异常', { error });
  gracefulShutdown();
});

process.on('unhandledRejection', (reason, promise) => {
  logger.fatal('未处理的Promise拒绝', { reason });
  gracefulShutdown();
});

注意:uncaughtException 触发后进程处于未知状态,应当终止并重启(PM2 或容器编排平台会自动拉起),因此在上面的处理中记录了致命错误后执行退出逻辑。

2. 错误追踪平台

对于未捕获异常和业务主动上报的错误,推荐使用专门的错误监控平台,例如 Sentry。Sentry 能聚合重复错误、记录错误发生的环境信息(版本、用户、调用栈等),并提供通知集成。

在 Node.js 中集成 Sentry 只需要安装 @sentry/node 并初始化:

const Sentry = require('@sentry/node');
Sentry.init({ dsn: 'your-dsn' });

app.use(Sentry.Handlers.requestHandler());
// 路由...
app.use(Sentry.Handlers.errorHandler());

错误会被自动上报到 Sentry 后台,你可以通过邮件、Slack、钉钉等方式配置报警规则。

3. 业务级告警

除了程序崩溃,还要监控业务指标的异常,例如:

  • 5xx 错误率突然升高
  • 登录失败频率超过阈值
  • 某接口 p99 延迟超过 2 秒

这类告警通常基于性能监控中收集的指标实现。使用 Prometheus 结合 Alertmanager,可以定义规则并在条件满足时发送通知。Alertmanager 支持邮件、Slack、Webhook、PagerDuty 等多种渠道。

4. 告警信息的设计原则

有效的告警需要避免“狼来了”——既要足够灵敏,又不能频繁误报。设计时注意:

  • 设置合适的阈值:例如错误率超过正常值 3 倍、持续 5 分钟才触发。
  • 分级告警:致命错误需要立即电话通知,而警告可以降级为邮件。
  • 携带上下文:告警消息中应包含服务名、环境、异常时间、错误摘要和直连监控面板的链接。

19.4.4 整合成一套可观测性方案

一个中等规模的 Node.js 应用,其线上监控体系通常会组合几样工具来实现全覆盖:

| 领域 | 工具组合 |
|------|----------|
| 日志收集 | pino → stdout → Filebeat/Fluentd → Elasticsearch → Kibana |
| 性能指标 | prom-client → Prometheus → Grafana |
| APM/追踪 | Elastic APM 或 OpenTelemetry + Jaeger |
| 错误追踪 | Sentry |
| 告警通知 | Alertmanager + Slack/钉钉/PagerDuty |

在所有组件中保持 日志与追踪的关联 非常重要:建议在日志中记录 traceId,方便从错误信息跳转到完整的请求链路。这一关联可以通过 APM SDK 或 OpenTelemetry 的自动传播来实现。

最后,监控体系本身也需要维护——定期检查采集器是否正常运行,告警规则是否仍然符合当前系统状态,避免出现“监控大面积失效却无人知晓”的尴尬。一套持续演进的监控体系,是 Node.js 应用长期稳定运行的最后一道防线。