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
This commit is contained in:
iu
2026-10-11 13:40:11 +03:00
parent a5d0cee0eb
commit 6361e48b20
18 changed files with 998 additions and 5 deletions
@@ -0,0 +1,24 @@
# Tasks
## 1. Конфигурация логирования (A)
- [x] 1.1 Добавить пакет `backend/internal/logging` с разбором уровня (`ParseLevel`) и сборкой handler (`NewHandler(format, level)`); проверка — `go test ./internal/logging` покрывает разбор `debug|info|warn|error`, `json|text` и ошибки.
- [x] 1.2 Добавить секцию `Log` в `config.Config` и чтение `LOG_LEVEL`/`LOG_FORMAT` с валидацией; проверка — `go test ./internal/config` покрывает значения по умолчанию и отказ на недопустимом значении.
- [x] 1.3 Строить `slog`-handler в `cmd/server/main.go` из `cfg.Log` вместо жёсткого `LevelInfo`; проверка — запуск с `LOG_LEVEL=debug`/`LOG_FORMAT=text` меняет вывод, а недопустимый `LOG_LEVEL` завершает старт явной ошибкой.
- [x] 1.4 Описать `LOG_LEVEL`/`LOG_FORMAT` в `.env.example` и `README.md`; проверка — задокументированный запуск с `LOG_LEVEL=debug` выводит подробные строки.
## 2. Идентификатор запроса и access-лог (B)
- [x] 2.1 Middleware в `app.Router()`: `request_id` из валидного `X-Request-ID` либо сгенерированный, запись в контекст запроса и заголовок ответа `X-Request-ID`; проверка — тест: переданный id сохраняется и возвращается, отсутствующий генерируется.
- [x] 2.2 Access-строка уровня `debug` (method, route `c.FullPath()`, status, latency, bytes, client_ip, user_id, request_id) без query; проверка — тест: на `debug` строка пишется, на `info` нет, query-параметры не попадают.
- [x] 2.3 Фильтрация access-логов: `/api/health` исключён всегда, `/api/objects/*` и `/api/sse/*` — только `debug`; проверка — тест на наборе маршрутов и уровне.
## 3. Регистрация внутренних ошибок (D)
- [x] 3.1 В `httpx.RespondError`/`Abort` регистрировать не-`AppError` через `c.Error(err)`; проверка — тест: неизвестная ошибка попадает в `c.Errors`, ответ остаётся обобщённым `500`.
- [x] 3.2 Логировать собранные ошибки в middleware: не-`AppError` и `AppError` со статусом `>=500` — уровень `error` с причиной и request_id, `AppError <500` — уровень `debug`; проверка — тест с буфером лога на обе ветки.
- [x] 3.3 Гарантировать приватность: не логировать query-строку, `Authorization` и тела запросов; проверка — тест с `?token=...` и телом убеждается, что значения не встречаются в буфере лога.
## 4. Интеграционная проверка
- [x] 4.1 Прогнать локальный инстанс с `LOG_LEVEL=debug`, выполнить несколько запросов (включая аутентифицированный и `/api/health`) и убедиться: access-строки есть, `X-Request-ID` возвращается в ответе, `/api/health` не логируется, секреты в логах отсутствуют.