Введение: Почему мониторинг критичен для TypeScript-приложений
TypeScript — мощный инструмент для создания масштабируемых веб-приложений, но даже самый качественный код не застрахован от сбоев в продакшене. Без системы мониторинга вы рискуете узнавать о проблемах от пользователей, а не от автоматических алертов. В этой статье мы разберем полный цикл настройки мониторинга: от сбора метрик до отправки уведомлений.
Мы рассмотрим три ключевых столпа observability: метрики (числовые показатели системы), логи (детальные записи событий) и трейсинг (отслеживание запросов). Особое внимание уделим интеграции с популярными инструментами — Prometheus, Grafana и Sentry.
1. Основные метрики для TypeScript-приложений
Прежде чем настраивать алерты, нужно определить, какие метрики критичны для вашего приложения. Разделим их на три группы:
1.1 Бизнес-метрики
Отражают здоровье приложения с точки зрения пользователя:
- Количество запросов в секунду (RPS) — показывает нагрузку на сервер
- Время ответа (latency) — p50, p95, p99 перцентили
- Количество ошибок — 4xx и 5xx статусы
- Активные пользователи — сессии, коннекты к WebSocket
1.2 Технические метрики
Показывают состояние инфраструктуры:
- CPU и Memory — загрузка процессора и утечки памяти
- Event Loop Lag — задержки в цикле событий Node.js
- Garbage Collection — частота и длительность сборки мусора
- HTTP connection pool — количество открытых соединений
1.3 Пример: сбор метрик с Prometheus в TypeScript
Установим библиотеку prom-client для Node.js:
npm install prom-client @types/prom-client
Создадим файл metrics.ts:
import client from 'prom-client';
import express, { Request, Response } from 'express';
// Создаем регистр метрик
const register = new client.Registry();
client.collectDefaultMetrics({ register });
// Кастомная метрика: количество HTTP запросов
const httpRequestCounter = new client.Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status_code'],
registers: [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],
registers: [register],
});
// Middleware для сбора метрик
export function metricsMiddleware(req: Request, res: Response, next: Function) {
const end = httpRequestDuration.startTimer();
res.on('finish', () => {
const labels = {
method: req.method,
route: req.route?.path || 'unknown',
status_code: res.statusCode.toString(),
};
httpRequestCounter.inc(labels);
end(labels);
});
next();
}
// Endpoint для Prometheus
export function metricsEndpoint(req: Request, res: Response) {
res.set('Content-Type', register.contentType);
res.send(register.metrics());
}
2. Настройка алертов: от теории к практике
Алерты должны быть умными — слишком много ложных срабатываний утомляет команду. Используем правило "трех сигм" для аномалий и пороговые значения для критических метрик.
2.1 Правила алертов в Prometheus
Создайте файл alerts.yml:
groups:
- name: typescript-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: "High error rate ({{ $value | humanizePercentage }})"
description: "Error rate above 5% for 2 minutes"
- alert: HighLatency
expr: |
histogram_quantile(0.95,
rate(http_request_duration_seconds_bucket[5m])
) > 2
for: 3m
labels:
severity: warning
annotations:
summary: "High p95 latency ({{ $value }}s)"
description: "95th percentile latency above 2 seconds"
2.2 Интеграция с Telegram через Webhook
Напишем простой сервис для отправки алертов в Telegram:
import axios from 'axios';
interface AlertPayload {
status: string;
labels: Record;
annotations: Record;
}
class AlertManager {
private telegramToken: string;
private chatId: string;
constructor(token: string, chatId: string) {
this.telegramToken = token;
this.chatId = chatId;
}
async sendAlert(alert: AlertPayload): Promise<void> {
const message = `
🚨 *${alert.status.toUpperCase()}*: ${alert.labels.alertname}
📊 Severity: ${alert.labels.severity}
📝 ${alert.annotations.description}
`;
await axios.post(
`https://api.telegram.org/bot${this.telegramToken}/sendMessage`,
{
chat_id: this.chatId,
text: message,
parse_mode: 'Markdown',
}
);
}
}
// Использование
const alertManager = new AlertManager(
process.env.TELEGRAM_BOT_TOKEN!,
process.env.TELEGRAM_CHAT_ID!
);
3. Логирование и трейсинг в TypeScript
Метрики показывают "что" произошло, логи — "почему". Для структурированного логирования используем pino или winston.
3.1 Структурированное логирование с pino
import pino from 'pino';
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
transport: {
target: 'pino-pretty',
options: {
colorize: true,
translateTime: 'SYS:standard',
},
},
redact: ['req.headers.authorization', 'req.body.password'],
});
// Middleware для логирования запросов
export function requestLogger(req: any, res: any, next: Function) {
const start = Date.now();
res.on('finish', () => {
logger.info