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

5.8 KiB
Raw Blame History

Логи, метрики и трейсы

Зачем нужны три сигнала

Логи

Логи отвечают на вопрос: что произошло.

Примеры:

  • пришел 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

Пример:

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 руками в строку сообщения

Как пользоваться метриками

Пример:

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
  • атрибуты делать короткими и стабильными

Как пользоваться трейсами

Пример:

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 код остается во внешних слоях.