Логирование и трассировка (OpenTelemetry)
| Проект | OpenTelemetry (CNCF, с 2019) |
|---|---|
| Предшественники | OpenTracing и OpenCensus |
| Сигналы | трассы, метрики, логи, профили |
| Протокол | OTLP (gRPC и HTTP) |
| Компоненты | API, SDK, Collector, автоинструментирование |
| Контекст | W3C Trace Context (traceparent) |
| Бэкенды | Jaeger, Tempo, Zipkin, Prometheus, Loki, коммерческие APM |
Логирование и трассировка — способы понять, что происходит внутри работающей системы. Вместе с метриками их называют тремя сигналами наблюдаемости (observability): метрики показывают, что сломалось, логи — почему, а распределённые трассы — где именно в цепочке сервисов. OpenTelemetry (OTel) — открытый стандарт и набор библиотек для сбора всех трёх сигналов единым способом и отправки их в любую систему хранения, без привязки к конкретному поставщику.
Три сигнала
| Сигнал | Что это | Сильная сторона | Пример хранилища |
|---|---|---|---|
| Метрики | числа во времени (RPS, задержка, память) | дёшево, алерты, тренды | Prometheus |
| Логи | записи о событиях | подробности конкретного случая | Loki, Elasticsearch |
| Трассы | путь одного запроса через сервисы | где тратится время и возникает ошибка | Jaeger, Tempo |
Сила наблюдаемости — в связях между сигналами: из всплеска ошибок на графике Grafana перейти к трассам этих запросов, а из трассы — к логам конкретного сервиса с тем же trace_id.[2]
Логирование
Современная практика — структурированные логи: каждая запись — объект JSON с полями, а не произвольная строка.
{"ts":"2026-11-09T14:03:12Z","level":"error","service":"payments",
"msg":"charge failed","order_id":"A-1042","trace_id":"4bf92f3577b34da6"}
Такие логи можно фильтровать и агрегировать по полям. Принципы:
- писать в stdout, а сбор и доставку оставить среде: systemd и journald, Docker, агенты Fluent Bit, Vector или OTel Collector — так советует методология Twelve-Factor App;[6]
- использовать уровни (
debug,info,warn,error) и не включатьdebugв продакшене постоянно; - не писать в логи пароли, токены и персональные данные;
- добавлять идентификатор запроса или трассы, чтобы связать записи разных сервисов.
Распределённая трассировка
Трасса описывает путь одного запроса и состоит из спанов — операций с началом, длительностью, атрибутами и ссылкой на родителя. Запрос к API порождает спан, внутри — спаны обращения к базе данных и вызова другого сервиса, у того — свои спаны. В интерфейсе это выглядит как водопадная диаграмма, где сразу видно самое медленное звено.
Чтобы спаны разных сервисов сложились в одну трассу, контекст передаётся с запросом — в HTTP это заголовок traceparent по стандарту W3C Trace Context:[4]
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
версия-trace_id-родительский span_id-флаги
Хранить каждую трассу дорого, поэтому применяют сэмплирование: на входе (сохранять, например, 10% запросов) или по завершении (tail sampling — сохранять все медленные и ошибочные).
OpenTelemetry
OpenTelemetry появился в 2019 году слиянием двух конкурирующих проектов — OpenTracing и OpenCensus — и входит в CNCF; по активности это один из крупнейших проектов фонда после Kubernetes.[1] Он включает:
- API и SDK для основных языков (Go, Java, Python, JavaScript, .NET, Rust и др.);
- автоинструментирование — агенты, которые без изменения кода создают спаны для HTTP-фреймворков, клиентов баз данных и очередей;
- OTLP — протокол передачи телеметрии;
- Collector — отдельный сервис, принимающий, обрабатывающий и отправляющий данные;
- семантические соглашения — единые имена атрибутов (
http.request.method,db.system), чтобы данные разных сервисов были сопоставимы.[5]
Пример ручного спана на Python:
from opentelemetry import trace
tracer = trace.get_tracer("payments")
def charge(order):
with tracer.start_as_current_span("charge") as span:
span.set_attribute("order.id", order.id)
return gateway.charge(order)
Для веб-приложения на FastAPI достаточно запустить его через opentelemetry-instrument — спаны для входящих запросов и вызовов базы появятся автоматически.
Collector
Collector отделяет приложения от бэкендов: сервисы отправляют всё по OTLP на локальный Collector, а он фильтрует, сэмплирует, добавляет метки окружения и пересылает данные в нужные системы.[3]
receivers:
otlp:
protocols: {grpc: {}, http: {}}
processors:
batch: {}
exporters:
otlphttp/tempo: {endpoint: http://tempo:4318}
prometheus: {endpoint: 0.0.0.0:8889}
service:
pipelines:
traces: {receivers: [otlp], processors: [batch], exporters: [otlphttp/tempo]}
metrics: {receivers: [otlp], processors: [batch], exporters: [prometheus]}
Сменить бэкенд — Jaeger на Tempo или открытое решение на коммерческое — можно правкой конфигурации Collector, не трогая код сервисов.
Наблюдаемость LLM-приложений
Те же принципы применяют к системам на языковых моделях: каждый вызов модели, шаг агента и обращение к инструменту становится спаном с атрибутами — модель, число токенов, задержка. Для этого в OpenTelemetry развиваются семантические соглашения GenAI; на них опираются инструменты вроде Langfuse и Phoenix.