Files
ds-galo/openspec/changes/archive/2026-10-11-add-observability-logging/specs/observability/spec.md
T
iu 6361e48b20 feat(observability): configurable logging, request ids and error logs
- internal/logging: LOG_LEVEL/LOG_FORMAT parsing and slog handler
- request_id middleware (X-Request-ID or generated, echoed) + debug access log
- httpx registers errors; middleware logs internal causes with request_id
- health excluded, objects/sse debug-only, no query/body/secrets in logs
- new observability capability spec; README/.env.example docs
2026-10-11 13:40:11 +03:00

6.1 KiB

Spec Delta

Purpose

Конфигурируемое структурированное логирование сервера: уровень и формат вывода, access-лог запросов с корреляцией по идентификатору и регистрация причин внутренних ошибок.

ADDED Requirements

Requirement: Уровень и формат логов

Сервер SHALL настраивать уровень и формат логирования из переменных окружения LOG_LEVEL (debug|info|warn|error) и LOG_FORMAT (json|text) со значениями по умолчанию info и json соответственно.

Scenario: Подробный режим

  • WHEN задан LOG_LEVEL=debug
  • THEN выводятся подробные строки, включая access-строки и отказы 4xx

Scenario: Некорректное значение

  • WHEN LOG_LEVEL или LOG_FORMAT содержит недопустимое значение
  • THEN сервер не стартует и сообщает явную ошибку конфигурации

Scenario: Текстовый формат

  • WHEN задан LOG_FORMAT=text
  • THEN логи выводятся в текстовом формате

Requirement: Идентификатор запроса

Сервер SHALL присваивать каждому HTTP-запросу идентификатор: принимать валидный X-Request-ID либо генерировать новый, SHALL включать его во все логи этого запроса и SHALL возвращать его в заголовке ответа X-Request-ID.

Scenario: Идентификатор передан клиентом

  • WHEN запрос содержит валидный заголовок X-Request-ID
  • THEN это значение используется как идентификатор запроса

Scenario: Идентификатор сгенерирован

  • WHEN заголовок X-Request-ID отсутствует или невалиден
  • THEN сервер генерирует новый идентификатор

Scenario: Эхо в ответе

  • WHEN запрос обработан
  • THEN ответ содержит заголовок X-Request-ID с использованным значением

Requirement: Access-лог запроса

На уровне debug сервер SHALL писать одну строку на завершённый HTTP-запрос с методом, шаблоном маршрута (без query-строки), статусом, длительностью, объёмом ответа, клиентским IP, идентификатором пользователя (если аутентифицирован) и идентификатором запроса.

Scenario: Режим debug

  • WHEN задан LOG_LEVEL=debug
  • THEN после обработки запроса появляется access-строка

Scenario: Обычный режим

  • WHEN задан LOG_LEVEL=info
  • THEN access-строки не пишутся

Scenario: Без query-строки

  • WHEN запрос содержит query-параметры
  • THEN query-параметры в access-строку не попадают

Requirement: Регистрация внутренних ошибок

Сервер SHALL логировать причину внутренних ошибок: ошибки, не являющиеся ожидаемыми AppError, SHALL писаться на уровне error с исходной причиной и идентификатором запроса; ожидаемые отказы со статусом ниже 500 SHALL писаться на уровне debug, а со статусом 500 и выше — на уровне error.

Scenario: Внутренняя ошибка

  • WHEN обработка запроса завершается неожиданной ошибкой
  • THEN её причина логируется на уровне error вместе с идентификатором запроса

Scenario: Ожидаемый отказ

  • WHEN запрос отклонён ожидаемой ошибкой со статусом 4xx
  • THEN на уровне info причина не логируется, а на уровне debug — логируется

Scenario: Клиентское сообщение не подменяет причину

  • WHEN клиент получает обобщённый ответ о внутренней ошибке
  • THEN в логе сохраняется исходная причина

Requirement: Фильтрация access-логов

Сервер SHALL всегда исключать маршрут /api/health из access-логов и SHALL писать access-логи для маршрутов /api/objects/* и /api/sse/* только на уровне debug.

Scenario: Health исключён

  • WHEN запрошен /api/health
  • THEN access-строка не пишется даже на уровне debug

Scenario: Объекты и SSE

  • WHEN запрошен /api/objects/* или /api/sse/* и задан LOG_LEVEL=debug
  • THEN access-строка пишется

Requirement: Приватность логов

Сервер SHALL не записывать в логи query-строку запроса, заголовок Authorization, тела запросов, пароли и подписанные URL.

Scenario: Токен в query

  • WHEN авторизация передана через query-параметр token
  • THEN его значение не попадает в логи

Scenario: Тело запроса не логируется

  • WHEN запрос содержит тело (JSON или multipart)
  • THEN содержимое тела не попадает в логи