«У нас всё работает» — слова, которые произносит разработчик, у которого нет observability. Без логов, метрик и трейсов вы узнаёте о проблемах от пользователей и чините их вслепую. Разберём, как собрать стек, который даст ответы на вопросы «почему страница тормозит», «куда делся запрос» и «что случилось в 3 ночи».
Три столпа observability
| Столп | Что отвечает | Инструменты |
|---|---|---|
| Логи | что произошло конкретно | Loki, Elastic, ClickHouse |
| Метрики | сколько/как часто/с какой задержкой | Prometheus, VictoriaMetrics |
| Трейсы | где время потерялось внутри запроса | Jaeger, Tempo, Honeycomb |
Все три нужны. Только логи — придётся гадать о паттернах. Только метрики — видишь «плохо», но не знаешь почему. Только трейсы — без счётчиков непонятно, насколько массово.
OpenTelemetry — стандарт
OTel — open standard, который заменяет proprietary-агентов всех вендоров. Один SDK в коде, дальше отправляете куда хотите: Datadog, Grafana Cloud, Jaeger, Tempo, Honeycomb. Не привязываетесь к вендору.
npm install @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-http
// instrumentation.ts (Next.js 15)
import { NodeSDK } from "@opentelemetry/sdk-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { Resource } from "@opentelemetry/resources";
const sdk = new NodeSDK({
resource: new Resource({ "service.name": "site-api" }),
traceExporter: new OTLPTraceExporter({
url: "http://otel-collector:4318/v1/traces",
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
Авто-инструментация подхватит fetch, http, postgres, redis — без явного wrap каждого вызова. На уровне Next.js — нужно подключить через файл instrumentation.ts в корне проекта.
Структурированные логи
Не console.log("user 42 created"), а JSON с полями:
import pino from "pino";
const log = pino();
log.info({
event: "user.created",
userId: 42,
email: user.email,
trace_id: getCurrentTraceId(),
}, "user created");
Loki/Elastic умеют фильтровать и агрегировать по полям. level=error AND service=api AND user_id=42 находит за секунду все логи конкретного пользователя за последний час.
trace_id — связка между логом и трейсом: видите ошибку в логах → переходите в Jaeger по trace_id → видите весь путь запроса.
Loki + Promtail
Дешёвая замена ELK. Хранит логи в объектном хранилище (S3-compatible), индексирует только метаданные.
# docker-compose
loki:
image: grafana/loki:3.0
ports: ["3100:3100"]
promtail:
image: grafana/promtail:3.0
volumes:
- /var/log:/var/log
- ./promtail.yaml:/etc/promtail/config.yml
Затраты на хранение: 10-100 раз дешевле Elasticsearch при той же глубине истории. Минус — нет полнотекста, поиск только по labels.
Prometheus для метрик
import { register, Counter, Histogram } from "prom-client";
const httpRequests = new Counter({
name: "http_requests_total",
help: "Total HTTP requests",
labelNames: ["method", "route", "status"],
});
const httpDuration = new Histogram({
name: "http_request_duration_seconds",
help: "HTTP request duration",
labelNames: ["method", "route"],
buckets: [0.01, 0.05, 0.1, 0.5, 1, 2, 5],
});
// в middleware
httpRequests.inc({ method: req.method, route, status });
httpDuration.observe({ method: req.method, route }, durationSec);
// app/api/metrics/route.ts
export async function GET() {
return new Response(await register.metrics(), {
headers: { "Content-Type": register.contentType },
});
}
Prometheus раз в 15 секунд опрашивает /api/metrics. Grafana строит графики.
Jaeger / Tempo для трейсов
Видите водопад: запрос пришёл в Next.js → вызвал API-роут → роут пошёл в Postgres → потом в Redis → потом в внешний API. Сразу понятно, что 80% времени съел внешний вызов.
jaeger:
image: jaegertracing/all-in-one:1.60
ports: ["16686:16686"]
environment:
COLLECTOR_OTLP_ENABLED: "true"
Tempo от Grafana — лучше для большого объёма (хранит в S3), но сложнее в установке. Jaeger проще для старта.
Что измерять — RED и USE
RED для сервисов:
- Rate — запросов в секунду
- Errors — % ошибок
- Duration — латенция (p50, p95, p99)
USE для ресурсов:
- Utilization — загрузка CPU, RAM, диска
- Saturation — длина очередей
- Errors — отказы (timeouts, OOM)
Эти 6 метрик покрывают 90% «почему упало». Дальше уже бизнес-метрики (конверсия, оборот, активные пользователи).
Алерты
Не на всё подряд. Хорошие алерты:
- p95 латенции эндпоинта > 1 сек 5 минут подряд.
- Error rate > 1% 2 минуты.
- БД использует > 90% CPU 10 минут.
- Очередь Redis длиннее 1000 сообщений.
- Сертификат истекает через 7 дней.
Плохие алерты — на каждый чих. Пара ложных срабатываний — и команда начинает их игнорировать («это опять оно»).
В Grafana Alerting или Alertmanager — отправка в Telegram/Slack. На дежурный канал — критика, в чат разработки — предупреждения.
Sampling
Трейсы дорогие в хранении. На 10 000 RPS вы быстро забьёте диск. Решения:
- Head-based sampling: рандомно 1-10% запросов трейсятся полностью. Просто, но «интересные» события (медленные, с ошибками) теряются.
- Tail-based sampling: трейсятся все, но сохраняются только «интересные» — медленные, с ошибками. Сложнее, нужен OTel collector с processor.
# otel-collector tail_sampling
processors:
tail_sampling:
decision_wait: 10s
policies:
- name: errors
type: status_code
status_code: { status_codes: [ERROR] }
- name: slow
type: latency
latency: { threshold_ms: 1000 }
- name: random
type: probabilistic
probabilistic: { sampling_percentage: 1 }
Российские реалии
Datadog, New Relic, Honeycomb — недоступны для оплаты из РФ. Self-hosted — единственный реальный вариант:
- Grafana + Loki + Prometheus + Tempo на одном VPS — стартовый стек, $20-50/мес.
- VictoriaMetrics вместо Prometheus — российский, эффективнее по RAM, проще кластеризация.
- ClickHouse для логов — если много, и нужен SQL-поиск.
- Yandex Cloud Monitoring — managed-аналог Cloud Monitoring, оплата рублями.
С чего начать
- Подключить OpenTelemetry в код, отправлять в локальный collector.
- Поставить Grafana + Loki + Prometheus + Jaeger в docker-compose.
- Вывести минимум: 4 RED-метрики, логи приложения, трейсы критичных эндпоинтов.
- Настроить 5-10 базовых алертов.
- Через месяц — пересобрать алерты, выкинуть шумные, добавить недостающие.
Один день работы — и у вас совсем другой уровень understanding своего прода.
Итого
Observability — не роскошь, а инструмент команды. Без него инциденты длятся часами, постмортемы — гадание. С ним инцидент: «вижу пик 5xx → проверяю dashboard → вижу всплеск latency БД → проверяю slow queries → нахожу запрос → правлю». 15 минут вместо 5 часов. OpenTelemetry стандартизирует это и убирает вендор-лок.
Частые вопросы
Datadog или self-hosted?
Datadog удобнее, но дорогой и недоступен для российских юрлиц. Self-hosted (Grafana + Loki + Prometheus + Tempo) — почти то же самое за стоимость VPS. Команда из 3-5 человек поднимет за 1-2 дня. Если уже на Datadog в зарубежной юрисдикции — оставайтесь.
OpenTelemetry — это сложно?
Базовая настройка с авто-инструментацией — час работы (одна команда npm install, файл instrumentation.ts, переменные окружения). Кастомные spans для бизнес-операций — отдельные дни. Самое сложное — collector конфигурация и tail-sampling, но без них на старте можно жить.
Loki или Elasticsearch для логов?
Loki — для большинства проектов. Дешевле в 10-50 раз по диску, оптимизирован под высокий throughput. Elasticsearch — когда нужен полнотекст и сложные агрегации, обычно избыточно для логов веб-приложения. ClickHouse — компромисс: дешёвое хранение + SQL для аналитики.
Сколько хранить логи и трейсы?
Логи: 7-30 дней горячие (Loki/Elastic), 1-3 месяца архив (S3). Трейсы: 7 дней горячие, sampled — 30 дней. Метрики — 1-3 года, агрегированные. Дольше — обычно бесполезно, если только нет регуляторных требований.
trace_id в каждом логе — сложно настроить?
Нет, OpenTelemetry автоматически прокидывает context. В pino:pino-opentelemetry-transport подхватывает activeSpan и добавляет trace_id/span_id в JSON-лог. Дальше в Loki/Grafana — клик по логу → переход в Jaeger по trace_id. Это самая полезная фича connected observability.
Метрики на стороне клиента (frontend) — как собирать?
Web Vitals (LCP, INP, CLS) — через web-vitals npm и POST на свой /api/metrics. Дальше в Prometheus или ClickHouse. Sentry умеет это сам в Performance, GoatCounter и Plausible — частично. Для серьёзной аналитики front-perf берите OpenTelemetry browser SDK или специализированные инструменты типа SpeedCurve.
Сколько ресурсов жрёт OpenTelemetry в продакшене?
Auto-instrumentation добавляет 1-5% CPU и 50-200 МБ RAM на процесс приложения. Collector — отдельный процесс, 200-500 МБ RAM на средний поток. Не критично для типичного сервиса. Если упираетесь — sampling: 1-10% запросов трейсится, остальное считается метрикой.