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

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

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

Начать курс

Observability в TypeScript: от чёрного ящика к прозрачной системе

Представьте, что ваше TypeScript-приложение — это сложный механизм из тысяч шестерёнок. Всё работает идеально, пока не ломается. И вот вы получаете сообщение: «Сайт тормозит». Где искать проблему? База данных? API? Или просто упал один из микросервисов?

Без observability (наблюдаемости) вы будете гадать, как шаман с бубном. С observability — вы видите всю картину: где упала производительность, какой запрос вызвал ошибку и почему утекла память.

В этой статье мы разберём три столпа observability — трейсинг, метрики и логи — и научимся внедрять их в TypeScript-приложения на реальных примерах. Вы узнаете, как превратить своё приложение из чёрного ящика в прозрачную систему, которую можно диагностировать за минуты.

Почему observability критична для TypeScript-приложений?

TypeScript — это не просто надстройка над JavaScript. Это язык, на котором пишут бэкенд для высоконагруженных систем, микросервисы и даже CLI-инструменты. И чем сложнее система, тем важнее понимать, что в ней происходит.

Традиционное логирование — это хорошо, но недостаточно. Когда у вас 50 микросервисов, логи превращаются в белый шум. Вам нужно:

  • Трейсинг — чтобы отследить путь одного запроса через все сервисы.
  • Метрики — чтобы увидеть, что RAM растёт как на дрожжах.
  • Структурированные логи — чтобы не искать иголку в стоге сена.

Давайте внедрим это в реальное приложение.

Трейсинг в TypeScript: OpenTelemetry на практике

Трейсинг (трассировка) — это история жизни одного запроса. Вы видите, сколько времени занял каждый шаг: от HTTP-запроса до запроса в базу данных. В TypeScript стандартом де-факто стал OpenTelemetry.

Установка и настройка OpenTelemetry

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

Создадим файл tracing.ts для инициализации трейсинга:

import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { JaegerExporter } from '@opentelemetry/exporter-jaeger';
import { Resource } from '@opentelemetry/resources';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';

const jaegerExporter = new JaegerExporter({
    endpoint: 'http://localhost:14268/api/traces',
    serviceName: 'my-typescript-app',
});

const sdk = new NodeSDK({
    resource: new Resource({
        [SemanticResourceAttributes.SERVICE_NAME]: 'my-typescript-app',
        [SemanticResourceAttributes.SERVICE_VERSION]: '1.0.0',
    }),
    traceExporter: jaegerExporter,
    instrumentations: [getNodeAutoInstrumentations()],
});

sdk.start();

Теперь каждый HTTP-запрос, каждый вызов базы данных будет автоматически трейситься. Вы увидите в Jaeger UI (или Grafana Tempo) полную карту вызовов.

Ручное создание спанов

Автоинструментирование — это круто, но иногда нужно добавить свои метки. Например, измерить время выполнения сложной бизнес-логики:

import { trace } from '@opentelemetry/api';

const tracer = trace.getTracer('business-logic');

async function processOrder(orderId: string): Promise<void> {
    const span = tracer.startSpan('process-order', {
        attributes: { 'order.id': orderId },
    });

    try {
        // Ваша логика обработки заказа
        await validateOrder(orderId);
        await chargePayment(orderId);
        await sendConfirmation(orderId);

        span.setStatus({ code: 1 }); // OK
    } catch (error) {
        span.recordException(error as Error);
        span.setStatus({ code: 2, message: 'Order processing failed' }); // ERROR
        throw error;
    } finally {
        span.end();
    }
}

Совет: добавляйте атрибуты — ID пользователя, тип ошибки, версию API. Это сделает трейсинг по-настоящему полезным.

Метрики в TypeScript: Prometheus и клиент для Node.js

Метрики — это цифры здоровья вашего приложения: количество запросов в секунду, latency, использование памяти. Без метрик вы не узнаете, что приложение «умирает», пока не станет слишком поздно.

Для TypeScript отлично подходит prom-client — клиент Prometheus для Node.js.

Установка и базовые метрики

npm install prom-client express

Создадим файл metrics.ts:

import client from 'prom-client';
import express, { Request, Response } from 'express';

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

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

// Кастомная метрика: счётчик запросов
const httpRequestCounter = new client.Counter({
    name: 'http_requests_total',
    help: 'Total number of HTTP requests',
    labelNames: ['method', 'route', 'status'],
    registers: [register],
});

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

// Экспортируем эндпоинт для Prometheus
const app = express();

app.get('/metrics', async (_req: Request, res: Response) => {
    res.set('Content-Type', register.contentType);
    res.send(await register.metrics());
});

export { app, httpRequestCounter, httpRequestDuration, register };

Использование метрик в приложении

Теперь добавим middleware, который будет собирать метрики с каждого запроса:

import { Request, Response, NextFunction } from 'express';
import { httpRequestCounter, httpRequestDuration } from './metrics';

export function metricsMiddleware(req: Request, res: Response, next: NextFunction): void {
    const end = httpRequestDuration.startTimer();

    res.on('finish', () => {
        // Увеличиваем счётчик
        httpRequestCounter.inc({
            method: req.method,
            route: req.route?.path || req.path,
            status: res.statusCode.toString(),
        });

        // Записываем длительность
        end({
            method: req.method,
            route: req.route?.path || req.path,
        });
    });

    next();
}

Теперь в Prometheus вы увидите графики: сколько запросов упало с 500-й ошибкой, какие эндпоинты самые медленные. А если подключить Grafana — получите красивый дашборд.

Логирование в TypeScript: Winston и структурированные логи

Логи — это дневник вашего приложения. Но не просто «Ошибка!», а структурированные записи с контекстом.

Блоги

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