О насБлогКонтакты
DevOps18 апреля 2026 г. 5 мин 6

Мониторинг Node.js в production: Prometheus, Grafana и OpenTelemetry

AunimedaAunimeda
📋 Содержание

Мониторинг Node.js в production: Prometheus, Grafana и OpenTelemetry

Приложение упало в 3 ночи. Пользователи пишут в поддержку. Вы открываете логи — строк миллион, непонятно что случилось и когда началось. Правильный мониторинг означает что вы узнаёте о проблеме за 2 минуты до того как её заметят пользователи.


Три компонента наблюдаемости

Метрики — числа во времени: RPS, latency, error rate, использование памяти.
Логи — событийная история: что произошло и когда.
Трейсы — путь конкретного запроса через все сервисы.

Для большинства Node.js приложений достаточно метрик + структурированных логов. Трейсы нужны при микросервисной архитектуре.


Prometheus метрики в Express

npm install prom-client
// src/metrics.ts
import { Counter, Histogram, Gauge, Registry, collectDefaultMetrics } from 'prom-client';

export const registry = new Registry();

// Системные метрики Node.js: CPU, memory, event loop lag
collectDefaultMetrics({ register: registry });

// HTTP запросы
export const httpRequestsTotal = new Counter({
  name: 'http_requests_total',
  help: 'Total HTTP requests',
  labelNames: ['method', 'route', 'status_code'],
  registers: [registry],
});

export const httpRequestDuration = new Histogram({
  name: 'http_request_duration_seconds',
  help: 'HTTP request duration in seconds',
  labelNames: ['method', 'route', 'status_code'],
  buckets: [0.01, 0.05, 0.1, 0.5, 1, 2, 5], // 10мс, 50мс, 100мс...
  registers: [registry],
});

// Бизнес-метрики
export const activeOrders = new Gauge({
  name: 'active_orders_total',
  help: 'Number of orders currently processing',
  registers: [registry],
});

export const orderProcessingErrors = new Counter({
  name: 'order_processing_errors_total',
  help: 'Number of order processing errors',
  labelNames: ['error_type'],
  registers: [registry],
});
// src/middleware/metrics.ts
import { Request, Response, NextFunction } from 'express';
import { httpRequestsTotal, httpRequestDuration } from '../metrics';

export function metricsMiddleware(req: Request, res: Response, next: NextFunction) {
  const start = process.hrtime.bigint();

  res.on('finish', () => {
    const duration = Number(process.hrtime.bigint() - start) / 1e9;
    
    // Нормализуем маршрут — заменяем динамические части
    const route = req.route?.path ?? req.path.replace(/\/\d+/g, '/:id');

    httpRequestsTotal.inc({
      method: req.method,
      route,
      status_code: res.statusCode,
    });

    httpRequestDuration.observe(
      { method: req.method, route, status_code: res.statusCode },
      duration
    );
  });

  next();
}
// src/app.ts
import express from 'express';
import { registry } from './metrics';
import { metricsMiddleware } from './middleware/metrics';

const app = express();

app.use(metricsMiddleware);

// Эндпоинт для Prometheus
app.get('/metrics', async (req, res) => {
  res.set('Content-Type', registry.contentType);
  res.end(await registry.metrics());
});

Конфигурация Prometheus

# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'nodejs-app'
    static_configs:
      - targets: ['app:3000']  # или IP вашего сервера
    metrics_path: /metrics
    scrape_interval: 10s

  - job_name: 'node-exporter'  # системные метрики сервера
    static_configs:
      - targets: ['node-exporter:9100']
# docker-compose.yml
services:
  app:
    build: .
    ports: ['3000:3000']

  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    ports: ['9090:9090']
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.retention.time=15d'

  grafana:
    image: grafana/grafana:latest
    volumes:
      - grafana_data:/var/lib/grafana
    ports: ['3001:3000']
    environment:
      GF_SECURITY_ADMIN_PASSWORD: 'your-secure-password'
      GF_USERS_ALLOW_SIGN_UP: 'false'

  node-exporter:
    image: prom/node-exporter:latest
    network_mode: host
    pid: host
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro

volumes:
  prometheus_data:
  grafana_data:

Grafana: ключевые дашборды

После подключения Prometheus как источника данных — эти запросы дают главную картину:

Request Rate (RPS):

rate(http_requests_total[5m])

Latency p95 по маршрутам:

histogram_quantile(0.95, 
  rate(http_request_duration_seconds_bucket[5m])
) by (route)

Error Rate:

rate(http_requests_total{status_code=~"5.."}[5m]) 
/ 
rate(http_requests_total[5m])

Использование памяти Node.js:

nodejs_heap_size_used_bytes / nodejs_heap_size_total_bytes

Event Loop Lag (важно для Node.js):

nodejs_eventloop_lag_seconds

Event loop lag > 100мс — ваш сервер перегружен синхронной работой.


Алерты в Prometheus

# alerts.yml
groups:
  - name: nodejs-app
    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: "Error rate > 5% for 2 minutes"

      - alert: HighLatency
        expr: |
          histogram_quantile(0.95, 
            rate(http_request_duration_seconds_bucket[5m])
          ) > 2
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "p95 latency > 2s"

      - alert: HighMemoryUsage
        expr: nodejs_heap_size_used_bytes / nodejs_heap_size_total_bytes > 0.85
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Node.js heap > 85%"

Структурированные логи

npm install pino pino-pretty
// src/logger.ts
import pino from 'pino';

export const logger = pino({
  level: process.env.LOG_LEVEL ?? 'info',
  transport: process.env.NODE_ENV !== 'production'
    ? { target: 'pino-pretty', options: { colorize: true } }
    : undefined,
  // В production — JSON, парсится Grafana Loki / ELK
});

// Использование
logger.info({ userId, orderId, amount }, 'Order created');
logger.error({ err, orderId }, 'Payment processing failed');

JSON-логи автоматически парсятся Grafana Loki или ELK Stack.


OpenTelemetry для трейсинга

Если у вас несколько сервисов — трейсинг показывает где именно тратится время:

npm install @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node @opentelemetry/exporter-jaeger
// src/tracing.ts (подключается ДО всего остального)
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { JaegerExporter } from '@opentelemetry/exporter-jaeger';

const sdk = new NodeSDK({
  serviceName: 'aunimeda-api',
  traceExporter: new JaegerExporter({
    endpoint: process.env.JAEGER_ENDPOINT ?? 'http://jaeger:14268/api/traces',
  }),
  instrumentations: [
    getNodeAutoInstrumentations({
      '@opentelemetry/instrumentation-http': { enabled: true },
      '@opentelemetry/instrumentation-express': { enabled: true },
      '@opentelemetry/instrumentation-pg': { enabled: true },  // PostgreSQL
    }),
  ],
});

sdk.start();
// package.json
"scripts": {
  "start": "node --require ./dist/tracing.js dist/app.js"
}

Автоинструментация добавляет трейсы для HTTP, Express маршрутов и PostgreSQL запросов без изменения бизнес-кода.


Мониторинг в production за 1 час

Минимальный набор который даст 80% пользы:

  1. prom-client с collectDefaultMetrics() → метрики Node.js
  2. Middleware для HTTP метрик → RPS, latency, error rate
  3. Prometheus scraping каждые 15 секунд
  4. Grafana дашборд с 4 панелями: RPS, p95 latency, error rate, heap memory
  5. Алерт на error rate > 5% → Telegram/Slack уведомление

Aunimeda настраивает production-мониторинг для веб-приложений в Кыргызстане. Обсудим проект.

Смотрите также: Docker Node.js production deploy, PostgreSQL оптимизация на VPS

Читайте также

Хостинг для сайта в Кыргызстане: VPS, shared или облако - что выбрать в 2026aunimeda
DevOps

Хостинг для сайта в Кыргызстане: VPS, shared или облако - что выбрать в 2026

Разбираем типы хостинга для сайтов в Кыргызстане: shared, VPS, облачный и выделенный сервер. Реальные цены в сомах, рейтинг провайдеров и когда стоит апгрейдиться.

Docker и CI/CD для небольшой команды: что мы реально запускаем в продакшенеaunimeda
DevOps

Docker и CI/CD для небольшой команды: что мы реально запускаем в продакшене

Kubernetes - не для всех. Наш продакшен-стек для 6 проектов командой из 8 человек: Docker Compose, GitHub Actions, Nginx - без Kubernetes и без $800/месяц на AWS.

Как настроить Docker для PHP-разработки в команде: опыт Бишкека (2016)aunimeda
DevOps

Как настроить Docker для PHP-разработки в команде: опыт Бишкека (2016)

В нашей команде в Бишкеке в 2016 году был постоянный хаос: у одного PHP 5.6, у другого 7.0, у третьего XAMPP с настройками 2014 года. Docker решил проблему: единое окружение у всех. Точный docker-compose.yml для PHP 7.0 + nginx + MySQL + Redis с учётом реалий слабого интернета.

Нужна IT-разработка для вашего бизнеса?

Разрабатываем сайты, мобильные приложения и AI-решения для бизнеса в Кыргызстане. Бесплатная консультация.

Получить консультацию Все статьи