API Monitoring и Logging: как отслеживать здоровье API

← Вернуться к статьям

Введение

Мониторинг и логирование API критически важны для поддержания работоспособности и производительности системы. Без правильного мониторинга невозможно понять, что происходит с API в production, выявить проблемы до того, как они затронут пользователей, и оптимизировать производительность.

В этом руководстве мы рассмотрим различные аспекты мониторинга и логирования API: health check endpoints, сбор метрик (latency, error rate, throughput), логирование запросов, популярные инструменты (Prometheus, Grafana, Datadog) и настройку alerting.

✅ Что вы узнаете:

💡 Зачем нужен мониторинг API?

Мониторинг позволяет выявлять проблемы до того, как они затронут пользователей, оптимизировать производительность, понимать использование API и быстро реагировать на инциденты. Хороший мониторинг — это глаза и уши вашего API.

📋 Содержание

1. Health Check Endpoints 💚

Health check endpoints используются для проверки состояния API. Они должны быстро отвечать и проверять ключевые зависимости (база данных, кэш, внешние сервисы).

Базовый health check

// Express.js health check
app.get('/health', (req, res) => {
  res.status(200).json({
    status: 'ok',
    timestamp: new Date().toISOString(),
    uptime: process.uptime()
  });
});

// Более детальный health check
app.get('/health', async (req, res) => {
  const health = {
    status: 'ok',
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    checks: {}
  };

  // Проверка базы данных
  try {
    await db.query('SELECT 1');
    health.checks.database = 'ok';
  } catch (error) {
    health.checks.database = 'error';
    health.status = 'degraded';
  }

  // Проверка Redis
  try {
    await redis.ping();
    health.checks.redis = 'ok';
  } catch (error) {
    health.checks.redis = 'error';
    health.status = 'degraded';
  }

  // Проверка внешнего сервиса
  try {
    await externalService.check();
    health.checks.externalService = 'ok';
  } catch (error) {
    health.checks.externalService = 'error';
    health.status = 'degraded';
  }

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

Readiness и Liveness проверки

// Liveness probe - приложение работает?
app.get('/health/live', (req, res) => {
  res.status(200).json({ status: 'alive' });
});

// Readiness probe - приложение готово принимать трафик?
app.get('/health/ready', async (req, res) => {
  const checks = {
    database: false,
    redis: false,
    migrations: false
  };

  try {
    // Проверка БД
    await db.query('SELECT 1');
    checks.database = true;

    // Проверка Redis
    await redis.ping();
    checks.redis = true;

    // Проверка миграций
    const migrations = await db.checkMigrations();
    checks.migrations = migrations.isUpToDate;

    const ready = Object.values(checks).every(check => check === true);

    if (ready) {
      res.status(200).json({ status: 'ready', checks });
    } else {
      res.status(503).json({ status: 'not ready', checks });
    }
  } catch (error) {
    res.status(503).json({
      status: 'not ready',
      error: error.message
    });
  }
});

// Startup probe - приложение запустилось?
app.get('/health/startup', (req, res) => {
  if (app.get('started')) {
    res.status(200).json({ status: 'started' });
  } else {
    res.status(503).json({ status: 'starting' });
  }
});

✅ Использование health checks:

2. Сбор метрик 📈

Метрики — это количественные измерения состояния системы. Ключевые метрики для API: latency (задержка), error rate (частота ошибок), throughput (пропускная способность).

Основные метрики API

Метрика Описание Целевое значение Как измерять
Latency Время ответа API < 200ms (p50), < 500ms (p95) Время от запроса до ответа
Error Rate Процент ошибок < 0.1% Количество ошибок / общее количество запросов
Throughput Запросов в секунду Зависит от нагрузки RPS (Requests Per Second)
CPU Usage Использование CPU < 70% Процент использования процессора
Memory Usage Использование памяти < 80% Процент использования памяти
Database Query Time Время запросов к БД < 100ms Среднее время выполнения запросов

Реализация сбора метрик в Node.js

// Простой сбор метрик
class MetricsCollector {
  constructor() {
    this.metrics = {
      requests: {
        total: 0,
        errors: 0,
        byMethod: {},
        byEndpoint: {}
      },
      latency: [],
      timestamps: []
    };
  }

  recordRequest(method, endpoint, statusCode, latency) {
    this.metrics.requests.total++;
    this.metrics.latency.push(latency);
    this.metrics.timestamps.push(Date.now());

    if (statusCode >= 400) {
      this.metrics.requests.errors++;
    }

    // Группировка по методу
    if (!this.metrics.requests.byMethod[method]) {
      this.metrics.requests.byMethod[method] = 0;
    }
    this.metrics.requests.byMethod[method]++;

    // Группировка по endpoint
    if (!this.metrics.requests.byEndpoint[endpoint]) {
      this.metrics.requests.byEndpoint[endpoint] = 0;
    }
    this.metrics.requests.byEndpoint[endpoint]++;

    // Храним только последние 1000 записей
    if (this.metrics.latency.length > 1000) {
      this.metrics.latency.shift();
      this.metrics.timestamps.shift();
    }
  }

  getMetrics() {
    const errorRate = this.metrics.requests.total > 0
      ? (this.metrics.requests.errors / this.metrics.requests.total) * 100
      : 0;

    const sortedLatency = [...this.metrics.latency].sort((a, b) => a - b);
    const p50 = this.percentile(sortedLatency, 50);
    const p95 = this.percentile(sortedLatency, 95);
    const p99 = this.percentile(sortedLatency, 99);

    const recent = this.metrics.timestamps.filter(
      ts => Date.now() - ts < 60000
    );
    const rps = recent.length / 60;

    return {
      totalRequests: this.metrics.requests.total,
      errorRate: `${errorRate.toFixed(2)}%`,
      latency: {
        p50: `${p50.toFixed(2)}ms`,
        p95: `${p95.toFixed(2)}ms`,
        p99: `${p99.toFixed(2)}ms`
      },
      throughput: `${rps.toFixed(2)} req/s`,
      byMethod: this.metrics.requests.byMethod,
      byEndpoint: this.metrics.requests.byEndpoint
    };
  }

  percentile(sortedArray, percentile) {
    if (sortedArray.length === 0) return 0;
    const index = Math.ceil((percentile / 100) * sortedArray.length) - 1;
    return sortedArray[index] || 0;
  }

  reset() {
    this.metrics = {
      requests: { total: 0, errors: 0, byMethod: {}, byEndpoint: {} },
      latency: [],
      timestamps: []
    };
  }
}

const metrics = new MetricsCollector();

// Middleware для сбора метрик
app.use((req, res, next) => {
  const start = Date.now();

  res.on('finish', () => {
    const latency = Date.now() - start;
    metrics.recordRequest(
      req.method,
      req.path,
      res.statusCode,
      latency
    );
  });

  next();
});

// Endpoint для получения метрик
app.get('/metrics', (req, res) => {
  res.json(metrics.getMetrics());
});

Prometheus формат метрик

// Установка: npm install prom-client
const client = require('prom-client');

// Регистр метрик
const register = new client.Registry();

// Counter для подсчета запросов
const httpRequestsTotal = new client.Counter({
  name: 'http_requests_total',
  help: 'Total number of HTTP requests',
  labelNames: ['method', 'endpoint', 'status'],
  registers: [register]
});

// Histogram для latency
const httpRequestDuration = new client.Histogram({
  name: 'http_request_duration_seconds',
  help: 'Duration of HTTP requests in seconds',
  labelNames: ['method', 'endpoint'],
  buckets: [0.1, 0.5, 1, 2, 5],
  registers: [register]
});

// Gauge для активных соединений
const activeConnections = new client.Gauge({
  name: 'active_connections',
  help: 'Number of active connections',
  registers: [register]
});

// Middleware для сбора метрик
app.use((req, res, next) => {
  const start = Date.now();

  res.on('finish', () => {
    const duration = (Date.now() - start) / 1000;
    httpRequestsTotal.inc({
      method: req.method,
      endpoint: req.path,
      status: res.statusCode
    });
    httpRequestDuration.observe(
      { method: req.method, endpoint: req.path },
      duration
    );
  });

  next();
});

// Endpoint для Prometheus
app.get('/metrics', async (req, res) => {
  res.set('Content-Type', register.contentType);
  res.end(await register.metrics());
});

// Пример вывода:
// http_requests_total{method="GET",endpoint="/api/users",status="200"} 1234
// http_request_duration_seconds_bucket{method="GET",endpoint="/api/users",le="0.1"} 800
// http_request_duration_seconds_bucket{method="GET",endpoint="/api/users",le="0.5"} 1200

3. Логирование запросов 📝

Логирование позволяет отслеживать все запросы к API, анализировать проблемы и аудировать использование системы.

Базовое логирование

// Winston для логирования
// Установка: npm install winston
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' }),
    new winston.transports.Console({
      format: winston.format.simple()
    })
  ]
});

// Middleware для логирования запросов
app.use((req, res, next) => {
  const start = Date.now();

  res.on('finish', () => {
    const duration = Date.now() - start;
    logger.info({
      method: req.method,
      url: req.url,
      statusCode: res.statusCode,
      duration: `${duration}ms`,
      ip: req.ip,
      userAgent: req.get('user-agent')
    });

    if (res.statusCode >= 400) {
      logger.error({
        method: req.method,
        url: req.url,
        statusCode: res.statusCode,
        error: res.statusMessage,
        body: req.body,
        query: req.query
      });
    }
  });

  next();
});

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

// Структурированное логирование с контекстом
const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  defaultMeta: {
    service: 'api-service',
    environment: process.env.NODE_ENV
  },
  transports: [
    new winston.transports.Console(),
    new winston.transports.File({ filename: 'api.log' })
  ]
});

// Middleware с корреляционным ID
const { v4: uuidv4 } = require('uuid');

app.use((req, res, next) => {
  req.id = uuidv4(); // Correlation ID
  req.startTime = Date.now();

  logger.info({
    requestId: req.id,
    method: req.method,
    path: req.path,
    ip: req.ip,
    message: 'Incoming request'
  });

  res.on('finish', () => {
    const duration = Date.now() - req.startTime;
    logger.info({
      requestId: req.id,
      method: req.method,
      path: req.path,
      statusCode: res.statusCode,
      duration: `${duration}ms`,
      message: 'Request completed'
    });
  });

  next();
});

Логирование с уровнем детализации

// Логирование с разными уровнями
logger.debug('Detailed debug information');
logger.info('General information');
logger.warn('Warning message');
logger.error('Error occurred', { error: error.stack });

// Логирование с контекстом
logger.info('User created', {
  userId: user.id,
  email: user.email,
  timestamp: new Date().toISOString()
});

// Логирование ошибок с контекстом
try {
  await db.createUser(userData);
} catch (error) {
  logger.error('Failed to create user', {
    error: error.message,
    stack: error.stack,
    userData: userData,
    requestId: req.id
  });
  throw error;
}

4. Prometheus и Grafana 📊

Prometheus — система мониторинга и сбора метрик с временными рядами. Grafana — платформа для визуализации метрик и создания дашбордов.

Настройка Prometheus

# prometheus.yml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'api-server'
    static_configs:
      - targets: ['localhost:3000']
    metrics_path: '/metrics'
    scrape_interval: 5s

Экспорт метрик в Node.js

// Экспорт метрик для Prometheus
const express = require('express');
const client = require('prom-client');
const app = express();

// Создаем регистр
const register = new client.Registry();

// Добавляем стандартные метрики Node.js
client.collectDefaultMetrics({ register });

// Создаем кастомные метрики
const httpRequestDuration = new client.Histogram({
  name: 'http_request_duration_seconds',
  help: 'Duration of HTTP requests in seconds',
  labelNames: ['method', 'route', 'status_code'],
  buckets: [0.1, 0.5, 1, 2, 5, 10],
  registers: [register]
});

const httpRequestsTotal = new client.Counter({
  name: 'http_requests_total',
  help: 'Total number of HTTP requests',
  labelNames: ['method', 'route', 'status_code'],
  registers: [register]
});

// Middleware для метрик
app.use((req, res, next) => {
  const start = Date.now();

  res.on('finish', () => {
    const duration = (Date.now() - start) / 1000;
    const route = req.route ? req.route.path : req.path;

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

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

  next();
});

// Endpoint для Prometheus
app.get('/metrics', async (req, res) => {
  try {
    res.set('Content-Type', register.contentType);
    res.end(await register.metrics());
  } catch (ex) {
    res.status(500).end(ex);
  }
});

app.listen(3000);

Grafana дашборд

{
  "dashboard": {
    "title": "API Metrics Dashboard",
    "panels": [
      {
        "title": "Request Rate",
        "targets": [{
          "expr": "rate(http_requests_total[5m])",
          "legendFormat": "{method} {route}"
        }]
      },
      {
        "title": "Request Duration (p95)",
        "targets": [{
          "expr": "histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))",
          "legendFormat": "{method} {route}"
        }]
      },
      {
        "title": "Error Rate",
        "targets": [{
          "expr": "rate(http_requests_total{status_code=~\"5..\"}[5m]) / rate(http_requests_total[5m])",
          "legendFormat": "Error Rate"
        }]
      }
    ]
  }
}
✅ Преимущества Prometheus + Grafana:

5. Datadog для мониторинга 🐕

Datadog — комплексная платформа мониторинга, которая предоставляет APM (Application Performance Monitoring), логирование, метрики и трейсинг в одном решении.

Интеграция Datadog

// Установка: npm install dd-trace
const tracer = require('dd-trace').init({
  service: 'api-server',
  env: process.env.NODE_ENV,
  version: '1.0.0'
});

// Автоматическое трейсинг для Express
const express = require('express');
const app = express();

// Datadog APM автоматически трейсит все запросы
app.get('/api/users', async (req, res) => {
  // Datadog автоматически отслеживает этот запрос
  const users = await db.getUsers();
  res.json(users);
});

// Кастомные спаны для детального трейсинга
app.post('/api/users', async (req, res) => {
  const span = tracer.startSpan('create_user');

  try {
    span.setTag('user.email', req.body.email);
    const user = await db.createUser(req.body);
    span.setTag('user.id', user.id);
    res.json(user);
  } catch (error) {
    span.setTag('error', true);
    span.setTag('error.message', error.message);
    throw error;
  } finally {
    span.finish();
  }
});

Метрики в Datadog

// Установка: npm install datadog-metrics
const metrics = require('datadog-metrics');
metrics.init({ host: 'api-server', prefix: 'api.' });

// Отправка кастомных метрик
metrics.gauge('users.active', activeUsersCount);
metrics.increment('api.requests');
metrics.histogram('api.response_time', responseTime);

// Счетчик с тегами
metrics.increment('api.requests', 1, ['method:GET', 'endpoint:/api/users']);

// Gauge для мониторинга состояния
metrics.gauge('database.connections', connectionCount);
metrics.gauge('cache.hit_rate', hitRate);

Логирование в Datadog

// Логирование в Datadog
const winston = require('winston');
const { DatadogTransport } = require('winston-datadog-logs');

const logger = winston.createLogger({
  transports: [
    new DatadogTransport({
      apiKey: process.env.DATADOG_API_KEY,
      service: 'api-server',
      hostname: 'api-server-1',
      ddsource: 'nodejs'
    })
  ]
});

logger.info('User created', {
  userId: 123,
  email: 'user@example.com',
  dd: {
    trace_id: tracer.currentSpan()?.context().toTraceId(),
    span_id: tracer.currentSpan()?.context().toSpanId()
  }
});
✅ Преимущества Datadog:
❌ Недостатки Datadog:

Сравнение инструментов 📊

Инструмент Тип Стоимость Сложность Функциональность
Prometheus + Grafana Open-source Бесплатно ⭐⭐⭐ ⭐⭐⭐⭐
Datadog SaaS Платно ⭐⭐ ⭐⭐⭐⭐⭐
New Relic SaaS Платно ⭐⭐ ⭐⭐⭐⭐⭐
ELK Stack Open-source Бесплатно ⭐⭐⭐⭐ ⭐⭐⭐⭐
CloudWatch SaaS (AWS) Платно ⭐⭐⭐ ⭐⭐⭐⭐

6. Alerting (Оповещения) 🚨

Alerting позволяет быстро реагировать на проблемы до того, как они затронут пользователей. Настройка правильных алертов критически важна для поддержания высокого uptime.

Настройка алертов в Prometheus

# alerts.yml
groups:
  - name: api_alerts
    interval: 30s
    rules:
      # Алерт на высокую error rate
      - alert: HighErrorRate
        expr: rate(http_requests_total{status_code=~"5.."}[5m]) > 0.05
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "High error rate detected"
          description: "Error rate is { $value }%"

      # Алерт на высокую latency
      - alert: HighLatency
        expr: histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) > 1
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "High latency detected"
          description: "P95 latency is { $value }s"

      # Алерт на недоступность сервиса
      - alert: ServiceDown
        expr: up{job="api-server"} == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "API service is down"
          description: "Service has been down for more than 1 minute"

      # Алерт на высокое использование CPU
      - alert: HighCPUUsage
        expr: rate(process_cpu_user_seconds_total[5m]) > 0.8
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High CPU usage"
          description: "CPU usage is { $value }%"

Интеграция с уведомлениями

// Отправка алертов в Slack
const { IncomingWebhook } = require('@slack/webhook');

const webhook = new IncomingWebhook(process.env.SLACK_WEBHOOK_URL);

async function sendAlert(alert) {
  await webhook.send({
    text: `🚨 Alert: ${alert.summary}`,
    attachments: [{
      color: alert.severity === 'critical' ? 'danger' : 'warning',
      fields: [
        { title: 'Description', value: alert.description },
        { title: 'Severity', value: alert.severity },
        { title: 'Time', value: new Date().toISOString() }
      ]
    }]
  });
}

// Отправка алертов в email
const nodemailer = require('nodemailer');

const transporter = nodemailer.createTransport({
  host: 'smtp.gmail.com',
  port: 587,
  auth: {
    user: process.env.EMAIL_USER,
    pass: process.env.EMAIL_PASS
  }
});

async function sendEmailAlert(alert) {
  await transporter.sendMail({
    from: 'alerts@api.example.com',
    to: 'devops@example.com',
    subject: `🚨 ${alert.summary}`,
    html: `
      

Alert: ${alert.summary}

${alert.description}

Severity: ${alert.severity}

Time: ${new Date().toISOString()}

` }); }

Умные алерты

// Алерты с дедупликацией и группировкой
class AlertManager {
  constructor() {
    this.alertHistory = new Map();
    this.alertCooldown = 5 * 60 * 1000; // 5 минут
  }

  async sendAlert(alert) {
    const alertKey = `${alert.type}-${alert.resource}`;
    const lastSent = this.alertHistory.get(alertKey);

    // Проверяем cooldown
    if (lastSent && Date.now() - lastSent < this.alertCooldown) {
      console.log(`Alert ${alertKey} is in cooldown`);
      return;
    }

    // Отправляем алерт
    await this.dispatchAlert(alert);

    // Запоминаем время отправки
    this.alertHistory.set(alertKey, Date.now());
  }

  async dispatchAlert(alert) {
    // Отправка в несколько каналов
    await Promise.all([
      sendSlackAlert(alert),
      sendEmailAlert(alert),
      logAlert(alert)
    ]);
  }
}

Best Practices для мониторинга 🌟

✅ 10 правил эффективного мониторинга:

  1. Мониторьте все критические метрики — latency, error rate, throughput
  2. Настройте health checks — для всех сервисов
  3. Логируйте структурированно — JSON формат с контекстом
  4. Используйте корреляционные ID — для трейсинга запросов
  5. Настройте алерты правильно — не слишком много, не слишком мало
  6. Мониторьте зависимости — БД, кэш, внешние API
  7. Используйте дашборды — для визуализации метрик
  8. Храните логи централизованно — для поиска и анализа
  9. Настройте retention policy — баланс между историей и стоимостью
  10. Регулярно проверяйте алерты — настройте и тестируйте

Ключевые метрики для мониторинга

Golden Signals (Золотые сигналы)

SLI/SLO/SLA

# Service Level Indicators (SLI)
- Availability: процент успешных запросов
- Latency: процент запросов быстрее X мс
- Error Rate: процент запросов без ошибок

# Service Level Objectives (SLO)
- 99.9% запросов должны быть успешными
- 95% запросов должны отвечать быстрее 500ms
- 99% запросов должны отвечать быстрее 1s

# Service Level Agreements (SLA)
- Контракт с пользователями о доступности сервиса
- Обычно 99.9% или выше
- Включает компенсации при нарушении

Чек-лист мониторинга

☐ Health check endpoints настроены
☐ Метрики собираются и экспортируются
☐ Логирование структурировано
☐ Дашборды созданы для ключевых метрик
☐ Алерты настроены для критических событий
☐ Мониторинг зависимостей настроен
☐ Retention policy для логов установлена
☐ Тестирование алертов проведено
☐ Документация по мониторингу создана
☐ On-call ротация настроена

Заключение

Мониторинг и логирование API — критически важные компоненты для поддержания работоспособности системы. Правильно настроенный мониторинг позволяет выявлять проблемы на ранней стадии и быстро реагировать на инциденты.

💡 Ключевые выводы:

⚠️ Частые ошибки:

Создайте Mock API за 2 минуты

Используйте Mock API для тестирования мониторинга и логирования без воздействия на production системы. LightBox API позволяет протестировать различные сценарии ошибок и нагрузки.

Попробовать бесплатно →
← Вернуться к статьям