Observability в JavaScript: трейсинг, метрики и логи для Node.js

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Ваше приложение — чёрный ящик? Пора это исправить

Вы когда-нибудь ловили себя на мысли: «Почему мой сервис на Node.js тормозит, хотя код идеален?» Или получали жалобу от пользователя, но не могли понять, что пошло не так? Знакомо? Добро пожаловать в мир, где без observability в JavaScript вы работаете вслепую.

Observability (наблюдаемость) — это не просто модное слово. Это подход, который превращает ваш код из чёрного ящика в прозрачную систему. Мы научимся задавать ей вопросы: «Кто виноват?» (трейсинг), «Что происходит прямо сейчас?» (метрики) и «Что случилось вчера в 3:15?» (логи).

В этой статье мы разберём три столпа observability в Node.js: трейсинг, метрики и логи. Вы получите готовые примеры кода и поймёте, как внедрить эти практики уже сегодня. Поехали!

Трейсинг в JavaScript: путешествие запроса

Представьте, что пользователь нажимает кнопку «Оформить заказ». Этот запрос проходит через API Gateway, сервис авторизации, корзину, платежный шлюз и базу данных. Если что-то сломалось — где искать? Трейсинг (или distributed tracing) даёт вам карту этого путешествия.

Что такое трейсинг простыми словами

Трейсинг — это способ отследить путь конкретного запроса через все микросервисы и функции. Каждый шаг — это span, а вся цепочка — trace. Вы видите, сколько времени занял каждый этап и где произошла ошибка.

OpenTelemetry: стандарт для observability в JavaScript

OpenTelemetry (OTel) — это индустриальный стандарт для сбора трейсов, метрик и логов. Он поддерживается всеми крупными облаками и инструментами (Jaeger, Zipkin, Grafana).

Установка:

npm install @opentelemetry/api @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node

Базовый пример трейсинга для Express-приложения:

const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
const { SimpleSpanProcessor } = require('@opentelemetry/sdk-trace-base');
const { ConsoleSpanExporter } = require('@opentelemetry/exporter-console');
const { ExpressInstrumentation } = require('@opentelemetry/instrumentation-express');
const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http');

const provider = new NodeTracerProvider();
provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()));
provider.register();

const { getTracer } = require('@opentelemetry/api');
const tracer = getTracer('my-app');

// Автоматический трейсинг HTTP и Express
const { registerInstrumentations } = require('@opentelemetry/instrumentation');
registerInstrumentations({
  tracerProvider: provider,
  instrumentations: [
    new ExpressInstrumentation(),
    new HttpInstrumentation()
  ]
});

// Теперь каждый запрос будет создавать trace с spans
const express = require('express');
const app = express();

app.get('/api/order', async (req, res) => {
  // Ручной span для бизнес-логики
  const span = tracer.startSpan('processPayment');
  try {
    // ... ваша логика
    await new Promise(r => setTimeout(r, 100)); // симуляция
    span.setStatus({ code: 1 }); // OK
  } catch (err) {
    span.setStatus({ code: 2, message: err.message }); // ERROR
    throw err;
  } finally {
    span.end();
  }
  res.send('Order created');
});

app.listen(3000);

Теперь в консоли вы увидите структурированные данные: какой запрос, сколько времени занял каждый middleware, где была ошибка. Это observability javascript в действии.

Метрики: пульс вашего приложения

Трейсинг хорош для отдельных запросов, но что насчёт общей картины? Сколько запросов в секунду? Какая задержка? Сколько памяти жрёт ваш Node.js? За это отвечают метрики.

Prometheus и клиент для Node.js

Prometheus — стандарт де-факто для сбора метрик. Он «вытягивает» данные из вашего приложения по HTTP.

Установка:

npm install prom-client express

Пример сбора базовых метрик:

const client = require('prom-client');
const express = require('express');
const app = express();

// Включаем сбор метрик по умолчанию (CPU, память и т.д.)
const collectDefaultMetrics = client.collectDefaultMetrics;
collectDefaultMetrics({ timeout: 5000 });

// Создаём свою метрику: счётчик запросов
const httpRequestCounter = new client.Counter({
  name: 'http_requests_total',
  help: 'Total number of HTTP requests',
  labelNames: ['method', 'path', 'status']
});

// Гистограмма для времени ответа
const httpRequestDuration = new client.Histogram({
  name: 'http_request_duration_seconds',
  help: 'Duration of HTTP requests in seconds',
  labelNames: ['method', 'path'],
  buckets: [0.01, 0.05, 0.1, 0.5, 1, 5] // в секундах
});

// Middleware для сбора метрик
app.use((req, res, next) => {
  const end = httpRequestDuration.startTimer();
  res.on('finish', () => {
    httpRequestCounter.inc({ method: req.method, path: req.path, status: res.statusCode });
    end({ method: req.method, path: req.path });
  });
  next();
});

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

app.get('/api/orders', (req, res) => {
  res.json({ orders: [] });
});

app.listen(3001, () => console.log('Metrics server running on port 3001'));

Теперь добавьте /metrics в конфигурацию Prometheus, и вы будете видеть графики в Grafana. Метрики приложения javascript — это ваш пульс.

Логирование: запись истории

Трейсинг и метрики отвечают на вопросы «где» и «сколько». Логи отвечают на вопрос «что именно произошло». Без них вы не сможете прочитать сообщение об ошибке.

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

Забудьте про console.log. Используйте структурированные логи в JSON — их легко парсить и искать.

npm install winston

Настройка логгера с ротацией и транспортами:

const winston = require('winston');
const { combine, timestamp, json, errors } = winston.format;

const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: combine(
    timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
    errors({ stack: true }), // логирование стека ошибок
    json()
  ),
  transports: [
    new winston.transports.Console(),
    new winston.transports.File({ filename: 'combined.log' }),
    new winston.transports.File({ filename: 'error.log', level: 'error' })
  ]
});

// Пример использования
logger.info('User request processed', {
  userId: 12345,
  path: '/api/order',
  duration: 150
});

try {
  throw new Error('Database connection failed');
} catch (err) {
  logger.error('Operation failed', {                

Блоги

Рекомендаций по книгам