7.8 KiB
Context
См. proposal.md и specs/backend-structured-logging/spec.md. Backend NestJS сейчас использует встроенный Logger в T-Bank-, Portfolio- и Upcoming Events-сервисах, а глобальный exception filter формирует application/problem+json. Request context и единый JSON-формат отсутствуют. Конфигурация уже валидируется при запуске.
Goals / Non-Goals
Goals:
- Один безопасный путь записи JSON-событий backend в stdout.
- Сквозной server-generated request ID для HTTP pipeline и T-Bank-клиента.
- Ровно одно HTTP completion-событие после фактической отправки ответа.
- Миграция существующих ручных логов на общий logger без изменения доменного поведения.
Non-Goals:
- Настройка collector, поиска, retention, alerting или distributed tracing.
- Логирование Prisma query, HTTP-тел, заголовков и payload внешних сервисов.
- Browser telemetry, изменение response body или статусов API.
Decisions
Backend использует nestjs-pino
Добавить nestjs-pino и настроить LoggerModule как единственный backend logger. Pino пишет JSON в stdout, реализует Nest LoggerService и создаёт request-scoped child logger. В bootstrap Nest использует Logger из nestjs-pino с буферизацией ранних framework-событий.
ConsoleLogger с json: true отклонён: он не предоставляет request-scoped logger, HTTP hooks и redaction. Самописный logger отклонён: реализация формата, уровня, отказоустойчивого вывода и sanitization дублирует поддерживаемую библиотеку.
Контекст запроса хранится в AsyncLocalStorage
nestjs-pino создаёт request-scoped logger и server-generated ID для каждого запроса. Middleware устанавливает этот ID в X-Request-ID; входящий ID сохраняется только отдельным валидированным clientRequestId и никогда не замещает server ID. AsyncLocalStorage остаётся минимальным способом передать внутренние user/account IDs в application-события без ручной передачи по всем вызовам.
Контекст расширяется внутренними userId и brokerageAccountId в точке, где они уже получены. Данные не извлекаются из body или headers для логирования. T-Bank client читает текущий контекст, поэтому его события автоматически связываются с HTTP-запросом; вызов вне request context остаётся корректным, но без request ID.
Итог HTTP-запроса фиксируется через Pino на response finish
pino-http создаёт request context и server-generated ID, но его automatic completion logging отключён: стандартный request serializer не знает безопасный route template и не должен журналировать headers. Response middleware измеряет duration и записывает через Pino одно allowlisted completion-событие с method, route template, status и request IDs. Это охватывает guards, validation и глобальный exception filter и отражает фактический status.
Exception filter передаёт в безопасное response-local состояние только public error code и, для 5xx, stack exception. Completion logger выбирает уровень по status: info по умолчанию, warn для authentication failures и 429, error для 5xx. Отдельные error-события HTTP не создаются.
Interceptor отклонён, потому что требует отдельной обработки Observable errors и не покрывает каждый Express response столь же надёжно. Логирование полного исходного URL отклонено из-за query data и высокой cardinality.
События используют allowlist полей
Pino redaction запрещает секретные request fields, а custom serializers передают только allowlisted HTTP context. Для T-Bank application-событий записываются operation, duration, gRPC code и attempts; для доменных событий - внутренние IDs и безопасные счётчики. Секретные, прямые персональные, финансовые и upstream payload значения не передаются API logger.
Allowlist выбран вместо generic redaction: запрет на запись опасного поля надёжнее, чем надежда распознать все формы токенов и вложенных payload. В частности, существующие logs Upcoming Events будут сокращены до разрешённых ID и счётчиков, без ticker, FIGI, display name и дат/сумм событий.
T-Bank client создаёт события в границах RPC
Общий вызов RPC измеряет каждый завершённый запрос. Успех записывается как debug; решение о retry - как warn; окончательный отказ - как error с gRPC code и числом попыток. В лог не передаются request object, metadata или token. Существующая семантика retry, deadline, rate limit и преобразования ошибок сохраняется.
Risks / Trade-offs
- [Незаполненный route template в раннем Express failure] -> Использовать безопасный fallback без query string и покрыть его интеграционным тестом.
- [Контекст теряется в новом async boundary] -> Logger допускает отсутствие request ID; для каждого нового background runner явно создавать свой context.
- [Ошибка записи JSON stdout] -> Pino изолирует вывод от business flow; проверить это unit-тестом интеграции logger.
- [Debug T-Bank события увеличат объём вывода] ->
LOG_LEVEL=infoв production по умолчанию; debug включается временно при диагностике.
Migration Plan
- Добавить
nestjs-pino, валидируемыйLOG_LEVEL, Pino configuration и request context middleware. - Подключить Pino в bootstrap, CORS expose header и HTTP completion logging вместе с exception filter metadata.
- Перевести T-Bank client, Portfolio и Upcoming Events на allowlisted события; удалить прямое использование Nest
Loggerв них. - Добавить unit и e2e проверки уровня, безопасности полей, request ID, CORS и уровней HTTP/T-Bank событий.
- Выпустить с
LOG_LEVEL=info, проверить JSON stdout иX-Request-ID; rollback состоит в откате backend release, так как данных и контрактов request body нет.