master/docs/OBSERVABILITY_RU.md
2026-03-26 21:56:10 +03:00

178 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Логи, метрики и трейсы
## Зачем нужны три сигнала
### Логи
Логи отвечают на вопрос: что произошло.
Примеры:
- пришел HTTP-запрос
- пользователь не найден
- сервис стартовал
### Метрики
Метрики отвечают на вопрос: как часто и насколько долго что-то происходит.
Примеры:
- сколько было запросов
- сколько было ошибок
- сколько миллисекунд заняла операция
### Трейсы
Трейсы отвечают на вопрос: как прошел путь одного конкретного запроса через систему.
Примеры:
- HTTP request -> use case -> repository
- где именно замедление
- на каком шаге произошла ошибка
## Как observability устроена в этом проекте
Внутренние слои не знают про OpenTelemetry напрямую.
Они работают только через порты из `usecase/interface.py`:
- `Logger`
- `Metrics`
- `Tracer`
- `Span`
Такой подход сохраняет чистую архитектуру: use case зависит от абстракций, а не от SDK.
## Где лежат реализации
### Логирование
- `adapter/observability/logging.py` - логирование в `stdout` или файл
- `adapter/otel/logging.py` - логирование через OpenTelemetry exporter
Поддерживаются режимы:
- `stdout`
- `file`
- `otel`
Поддерживаются форматы:
- `text`
- `json`
### Метрики
- `adapter/otel/metrics.py` - адаптер над OTel `Meter`
Метрики создаются лениво: счетчик или гистограмма заводятся при первом обращении по имени.
### Трейсинг
- `adapter/otel/tracing.py` - адаптер над OTel `Tracer`
Use case или repository может открыть span через `with tracer.start_span(...):`.
### Runtime factory
- `adapter/observability/factory.py` - выбирает, какой runtime собрать
- `adapter/observability/noop.py` - noop-реализации, когда сигнал отключен
- `adapter/otel/bootstrap.py` - создает OTel providers и exporters
## Как это работает на старте приложения
1. Загружается `AppConfig`
2. `build_observability(config)` выбирает нужные реализации
3. Если хотя бы один сигнал должен идти через OTel, создается OTel runtime
4. В контейнер кладутся готовые `logger`, `metrics`, `tracer`
5. Use case и repository получают их через конструктор
## Как observability работает в HTTP
### Request logging
В `adapter/http/fastapi/middleware.py` есть HTTP middleware, которое:
- замеряет длительность запроса
- фиксирует метод, путь и статус
- пишет событие `http_request`
### HTTP traces и HTTP metrics
В `adapter/http/fastapi/app.py` вызывается `FastAPIInstrumentor.instrument_app(...)`.
Это дает стандартную OTel instrumentation для FastAPI/ASGI без кастомного trace middleware.
## Как пользоваться логгером в use case
Пример:
```py
class CreateUser:
def __init__(self, logger: Logger) -> None:
self._logger = logger
def execute(self, email: str) -> None:
self._logger.info('user_create', attrs={'user.email': email})
```
Рекомендации:
- message короткий и стабильный
- важные поля передавать в `attrs`
- не шить JSON руками в строку сообщения
## Как пользоваться метриками
Пример:
```py
class CreateUser:
def __init__(self, metrics: Metrics) -> None:
self._metrics = metrics
def execute(self, email: str) -> None:
self._metrics.increment('user.create.total', attrs={'source': 'api'})
self._metrics.record('user.create.duration_ms', 12.5)
```
Практика именования:
- счетчики: `something.total`, `something.error`
- длительности: `something.duration_ms`
- атрибуты делать короткими и стабильными
## Как пользоваться трейсами
Пример:
```py
class CreateUser:
def __init__(self, tracer: Tracer) -> None:
self._tracer = tracer
def execute(self, user_id: str) -> None:
with self._tracer.start_span('usecase.create_user', attrs={'user.id': user_id}) as span:
span.set_attribute('user.flow', 'signup')
```
Если внутри возникнет ошибка, адаптер трейсинга сможет записать ее в span.
## Как включать и выключать сигналы
В `config/app.yaml` и env есть основные флаги:
- `logging.output=stdout|file|otel`
- `logging.format=text|json`
- `logging.file_path=...`
- `metrics.enabled=true|false`
- `tracing.enabled=true|false`
Когда метрики или трейсы выключены, внутренние слои получают `Noop`-реализации.
Это значит, что код use case не приходится усложнять проверками `if enabled`.
## Главное правило
`domain/` и `usecase/` не импортируют OpenTelemetry.
Весь OTel код остается во внешних слоях.