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