正在加载,请稍候…

应用性能监控(APM)指南:指标、日志、链路追踪详解

完整的APM指南:理解指标、日志和分布式链路追踪。涵盖Prometheus、Grafana、OpenTelemetry、Sentry,以及如何为Node.js应

应用性能监控(APM)指南:指标、日志、链路追踪详解

为什么你的应用会以你从未预料到的方式失败

你对自己的应用进行了负载测试,谨慎地部署了它,它在正常条件下表现良好。然后凌晨2点流量激增,一个慢数据库查询开始阻塞连接池,内存使用量在12小时内逐渐攀升,你的应用崩溃了。等你发现时,成千上万的用户已经放弃了你的服务。

可观测性——随时了解你的应用在做什么——可以防止这种情况。可观测性的三大支柱是指标、日志和链路追踪。本指南将向你展示如何实现这三者。

应用性能监控(APM)指南:指标、日志、链路追踪详解插图

可观测性的三大支柱

1. 指标:正在发生什么(现在和随时间变化)

指标是随时间采样的数值测量:CPU使用率、请求速率、错误率、响应时间百分位数。它们回答“是否出了问题?”

指标示例:
- http_requests_total{method="GET", status="200"} = 15203
- http_request_duration_seconds{p99} = 0.234
- nodejs_heap_used_bytes = 142606336
- database_connections_active = 12

2. 日志:发生了什么(事件)

日志是带有上下文的离散事件。它们回答“到底发生了什么,为什么?”

{
  "timestamp": "2026-05-28T14:23:11.234Z",
  "level": "error",
  "message": "数据库连接超时",
  "requestId": "req_abc123",
  "userId": "user_456",
  "query": "SELECT * FROM orders WHERE user_id = ?",
  "duration_ms": 30002,
  "error": "connect ETIMEDOUT 10.0.0.5:5432"
}

3. 链路追踪:请求如何传播(因果关系)

分布式链路追踪显示请求经过多个服务的路径。它们回答“为什么这个特定请求很慢?”

请求 abc123(总计:234ms)
├── API 网关(2ms)
├── 认证服务(8ms)
├── 用户服务(12ms)
│   └── PostgreSQL 查询(10ms)
└── 订单服务(212ms)← 慢!
    ├── Redis 缓存未命中(3ms)
    └── PostgreSQL 查询(208ms)← 这是瓶颈

应用性能监控(APM)指南:指标、日志、链路追踪详解插图

搭建 Prometheus + Grafana

Prometheus 从你的应用抓取指标。Grafana 将其可视化。

# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports: ["3000:3000"]
    
  prometheus:
    image: prom/prometheus:latest
    ports: ["9090:9090"]
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml

  grafana:
    image: grafana/grafana:latest
    ports: ["3001:3000"]
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana_data:/var/lib/grafana

volumes:
  grafana_data:
# prometheus.yml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'nodejs-app'
    static_configs:
      - targets: ['app:3000']
    metrics_path: '/metrics'

从 Node.js 暴露指标

import express from 'express';
import { register, Counter, Histogram, Gauge } from 'prom-client';
import { collectDefaultMetrics } from 'prom-client';

const app = express();

// 收集默认的 Node.js 指标(CPU、内存、事件循环、GC)
collectDefaultMetrics({ prefix: 'nodejs_' });

// 自定义指标
const httpRequestsTotal = new Counter({
  name: 'http_requests_total',
  help: 'HTTP 请求总数',
  labelNames: ['method', 'route', 'status_code'],
});

const httpRequestDuration = new Histogram({
  name: 'http_request_duration_seconds',
  help: 'HTTP 请求持续时间(秒)',
  labelNames: ['method', 'route'],
  buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5],
});

const activeConnections = new Gauge({
  name: 'http_active_connections',
  help: '活跃 HTTP 连接数',
});

// 中间件:追踪请求
app.use((req, res, next) => {
  const end = httpRequestDuration.startTimer({ 
    method: req.method, 
    route: req.path 
  });
  
  activeConnections.inc();
  
  res.on('finish', () => {
    httpRequestsTotal.inc({
      method: req.method,
      route: req.route?.path ?? req.path,
      status_code: res.statusCode,
    });
    end(); // 记录持续时间
    activeConnections.dec();
  });
  
  next();
});

// 暴露指标端点(Prometheus 抓取此端点)
app.get('/metrics', async (req, res) => {
  res.set('Content-Type', register.contentType);
  res.send(await register.metrics());
});

有用的 Prometheus 查询(PromQL)

# 请求速率(每秒,5分钟窗口)
rate(http_requests_total[5m])

# 错误率百分比
rate(http_requests_total{status_code=~"5.."}[5m])
/ rate(http_requests_total[5m]) * 100

# 99% 延迟
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

# 内存使用量(MB)
nodejs_heap_used_bytes / 1024 / 1024

# 事件循环延迟(p99)——指示 CPU 压力
histogram_quantile(0.99, rate(nodejs_eventloop_lag_seconds_bucket[5m]))

使用 Winston/Pino 进行结构化日志

// pino 更快,winston 更灵活
import pino from 'pino';

const logger = pino({
  level: process.env.LOG_LEVEL ?? 'info',
  
  // 生产环境中输出 JSON 用于日志聚合
  transport: process.env.NODE_ENV === 'development'
    ? { target: 'pino-pretty', options: { colorize: true } }
    : undefined,
  
  // 为每条日志添加基础元数据
  base: {
    service: 'api-service',
    version: process.env.npm_package_version,
    env: process.env.NODE_ENV,
  },
  
  // 脱敏敏感字段
  redact: ['req.headers.authorization', 'req.body.password'],
});

// 使用子日志器添加上下文
app.use((req, res, next) => {
  req.log = logger.child({ 
    requestId: crypto.randomUUID(),
    method: req.method,
    url: req.url,
  });
  next();
});

// 在路由处理器中
app.get('/users/:id', async (req, res) => {
  req.log.info('正在获取用户');
  
  try {
    const user = await db.users.findById(req.params.id);
    req.log.info({ userId: user.id }, '用户已找到');
    res.json(user);
  } catch (error) {
    req.log.error({ err: error }, '获取用户失败');
    res.status(500).json({ error: '内部服务器错误' });
  }
});

应用性能监控(APM)指南:指标、日志、链路追踪详解插图

OpenTelemetry:分布式链路追踪

// 插桩设置(必须在其他导入之前运行!)
// tracing.js
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { Resource } from '@opentelemetry/resources';
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';

const sdk = new NodeSDK({
  resource: new Resource({
    [ATTR_SERVICE_NAME]: 'my-api-service',
  }),
  
  traceExporter: new OTLPTraceExporter({
    url: 'http://jaeger:4318/v1/traces', // 或 Tempo、Zipkin 等
  }),
  
  // 自动插桩:http、express、pg、redis 等
  instrumentations: [getNodeAutoInstrumentations()],
});

sdk.start();

// 优雅关闭
process.on('SIGTERM', () => sdk.shutdown());
// 向链路追踪添加自定义 Span
import { trace, context, SpanStatusCode } from '@opentelemetry/api';

const tracer = trace.getTracer('my-service');

async function processOrder(orderId) {
  // 创建一个自定义 Span
  return tracer.startActiveSpan('processOrder', async (span) => {
    span.setAttribute('order.id', orderId);
    span.setAttribute('order.processor', 'v2');
    
    try {
      const order = await db.orders.findById(orderId);
      span.setAttribute('order.amount', order.total);
      
      // 嵌套 Span 会自动关联
      await chargePayment(order);
      await updateInventory(order);
      await sendConfirmationEmail(order);
      
      span.setStatus({ code: SpanStatusCode.OK });
      return order;
    } catch (error) {
      span.setStatus({ 
        code: SpanStatusCode.ERROR, 
        message: error.message 
      });
      span.recordException(error);
      throw error;
    } finally {
      span.end(); // 始终结束 Span!
    }
  });
}

使用 Sentry 进行错误监控

import * as Sentry from '@sentry/node';

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  environment: process.env.NODE_ENV,
  
  // 性能监控(捕获链路追踪)
  tracesSampleRate: 0.1, // 采样 10% 的事务
  
  // 面包屑:错误前的自动上下文
  integrations: [
    Sentry.httpIntegration(),
    Sentry.expressIntegration(),
    Sentry.postgresIntegration(),
  ],
  
  // 不发送某些错误
  ignoreErrors: [
    'ResizeObserver loop limit exceeded',
    /^Network Error/,
  ],
  
  beforeSend(event) {
    // 擦除敏感数据
    if (event.request?.data?.password) {
      event.request.data.password = '[REDACTED]';
    }
    return event;
  },
});

// 向错误添加用户上下文
app.use((req, res, next) => {
  if (req.user) {
    Sentry.setUser({ id: req.user.id, email: req.user.email });
  }
  next();
});

// 错误处理器必须放在路由之后
app.use(Sentry.expressErrorHandler());

需要监控的关键指标

四个黄金信号(Google SRE)

1. 延迟      - 请求耗时(p50、p95、p99)
2. 流量      - 每秒请求数
3. 错误       - 失败百分比
4. 饱和度   - 接近容量的程度(CPU、内存、队列深度)

Node.js 特定指标

// 事件循环延迟——最重要的 Node.js 指标
// 高延迟 = CPU 密集型代码阻塞异步操作
const Histogram = require('prom-client').Histogram;
const lag = new Histogram({ name: 'eventloop_lag', help: '事件循环延迟' });

let prev = Date.now();
setInterval(() => {
  const now = Date.now();
  lag.observe((now - prev - 1000) / 1000); // 预期 1000ms,测量偏差
  prev = now;
}, 1000);

// 堆使用量
// 持续增长 → 内存泄漏
// GC 暂停时间 → 垃圾回收压力
// 外部内存 → 原生插件、Buffer

告警最佳实践

# Prometheus 告警规则
groups:
  - name: app-alerts
    rules:
      # 高错误率
      - alert: HighErrorRate
        expr: |
          rate(http_requests_total{status_code=~"5.."}[5m]) 
          / rate(http_requests_total[5m]) > 0.05
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "错误率超过 5%"
          
      # 高延迟
      - alert: HighLatency
        expr: |
          histogram_quantile(0.99, 
            rate(http_request_duration_seconds_bucket[5m])
          ) > 1.0
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "p99 延迟超过 1 秒"
          
      # 内存泄漏指示
      - alert: HighMemoryUsage
        expr: nodejs_heap_used_bytes / nodejs_heap_size_total_bytes > 0.9
        for: 10m
        labels:
          severity: critical

→ 使用 Benchmark Builder 测量和比较不同代码实现的性能。