Chuyển đến nội dung chính

第 20 課:生產監控與擴展

Pino 结构化日志记录、OpenTelemetry 跟踪。 Prometheus 指標、Grafana 儀表板。 PM2 生态系统。優雅關閉、健康檢查。水平扩展,无状态设计。

💻 程式設計 — 第 20 課 第 20 課:生產監控與擴展

Node.js 核心:從基礎到高級

第 5 部分:測試、性能和生產

亞洲開發網

1.結構化日誌記錄(Pino)

import pino from 'pino'

const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  transport: process.env.NODE_ENV !== 'production'
    ? { target: 'pino-pretty', options: { colorize: true } }
    : undefined,
  redact: ['req.headers.authorization', 'password'],
})

// Context logging
const reqLogger = logger.child({ requestId: crypto.randomUUID() })
reqLogger.info({ userId: '123', action: 'login' }, 'User logged in')
reqLogger.error({ err, orderId: '456' }, 'Payment failed')

// HTTP request logging (middleware)
import pinoHttp from 'pino-http'
const httpLogger = pinoHttp({ logger })

2.開放遙測

// tracing.ts — import first!
import { NodeSDK } from '@opentelemetry/sdk-node'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'

const sdk = new NodeSDK({
  traceExporter: new OTLPTraceExporter({
    url: 'http://jaeger:4318/v1/traces',
  }),
  instrumentations: [
    getNodeAutoInstrumentations({
      '@opentelemetry/instrumentation-http': { enabled: true },
      '@opentelemetry/instrumentation-pg': { enabled: true },
      '@opentelemetry/instrumentation-redis': { enabled: true },
    }),
  ],
})

sdk.start()
process.on('SIGTERM', () => sdk.shutdown())
node --require ./tracing.js app.js

3. 普羅米修斯指標

import { Registry, Counter, Histogram, collectDefaultMetrics } from 'prom-client'

const register = new Registry()
collectDefaultMetrics({ register })

const httpRequests = new Counter({
  name: 'http_requests_total',
  help: 'Total HTTP requests',
  labelNames: ['method', 'path', 'status'],
  registers: [register],
})

const httpDuration = new Histogram({
  name: 'http_request_duration_seconds',
  help: 'HTTP request duration',
  labelNames: ['method', 'path'],
  buckets: [0.01, 0.05, 0.1, 0.5, 1, 5],
  registers: [register],
})

// Middleware
function metricsMiddleware(req, res, next) {
  const end = httpDuration.startTimer({ method: req.method, path: req.path })
  res.on('finish', () => {
    httpRequests.inc({ method: req.method, path: req.path, status: res.statusCode })
    end()
  })
  next()
}

// Metrics endpoint
app.get('/metrics', async (req, res) => {
  res.set('Content-Type', register.contentType)
  res.end(await register.metrics())
})

4. 健康檢查

app.get('/health', async (req, res) => {
  const checks = {
    uptime: process.uptime(),
    timestamp: Date.now(),
    status: 'ok' as string,
    checks: {} as Record<string, string>,
  }

  try {
    await pool.query('SELECT 1')
    checks.checks.database = 'ok'
  } catch {
    checks.checks.database = 'error'
    checks.status = 'degraded'
  }

  try {
    await redis.ping()
    checks.checks.redis = 'ok'
  } catch {
    checks.checks.redis = 'error'
    checks.status = 'degraded'
  }

  const statusCode = checks.status === 'ok' ? 200 : 503
  res.status(statusCode).json(checks)
})

5. 正常關機

async function gracefulShutdown(signal: string) {
  logger.info(`Received ${signal}. Starting graceful shutdown...`)

  // Stop accepting new connections
  server.close()

  // Drain existing connections (max 10s)
  const timeout = setTimeout(() => {
    logger.error('Forced shutdown after timeout')
    process.exit(1)
  }, 10000)

  try {
    await pool.end()      // Close DB connections
    await redis.quit()    // Close Redis
    clearTimeout(timeout)
    logger.info('Shutdown complete')
    process.exit(0)
  } catch (err) {
    logger.error({ err }, 'Error during shutdown')
    process.exit(1)
  }
}

process.on('SIGTERM', () => gracefulShutdown('SIGTERM'))
process.on('SIGINT', () => gracefulShutdown('SIGINT'))

6. 生產檢查表

類別清單
記錄Pino 結構、請求 ID、日誌級別
監控普羅米修斯 + Grafana、OpenTelemetry
錯誤追蹤Sentry,未處理的拒絕處理程序
健康檢查/健康端點,就緒探針
優雅關機SIGTERM 處理程序,連線耗盡
安全性頭盔、CORS、速率限制、輸入驗證
效能Cluster/PM2、連線池、快取

🎉 完成該系列 Node.js 核心:從基礎到高級!