Files
ds-galo/openspec/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

103 lines
6.1 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.
# observability Specification
## Purpose
Конфигурируемое структурированное логирование сервера: уровень и формат вывода,
access-лог запросов с корреляцией по идентификатору и регистрация причин внутренних
ошибок.
## 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** содержимое тела не попадает в логи