[feat] add guides

This commit is contained in:
Azamat 2026-03-26 21:56:10 +03:00
parent 1fbf77f879
commit c5d5e243c1
7 changed files with 836 additions and 43 deletions

178
docs/OBSERVABILITY_RU.md Normal file
View file

@ -0,0 +1,178 @@
# Логи, метрики и трейсы
## Зачем нужны три сигнала
### Логи
Логи отвечают на вопрос: что произошло.
Примеры:
- пришел 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 код остается во внешних слоях.