# Логи без персональных данных

> Что писать в лог, что маскировать, сколько хранить и как идентификатор запроса помогает разбираться в проблемах, не зная, кто был пользователь.

Published: 2026-06-08 · Source: https://subxcat.com/ru/articles/logs-without-pii · Author: SUBXCAT

Логи обычно растут сами. Кто-то добавил `console.log(req.body)`, чтобы поймать баг, баг починили, строчка осталась. Через полгода в логах лежат пароли из формы входа, телефоны из анкет и токены из заголовков, а доступ к логам есть у всех разработчиков, у сервиса мониторинга и у подрядчика, которому дали посмотреть одну ошибку.

Логи — такая же база персональных данных, как основная, только хуже защищённая и без политики удаления. Проще не класть туда лишнего, чем потом выяснять, кому всё это ушло.

## Что писать

Хорошая строка лога отвечает на вопросы «что произошло, где и чем кончилось», а не «кто этот человек»:

- идентификатор запроса;
- маршрут в виде шаблона: `/orders/:id`, а не `/orders/8412?token=...`;
- метод, код ответа и время выполнения;
- внутренний идентификатор пользователя — непрозрачный, из базы, а не почта;
- для ошибок — тип, сообщение и стек;
- ключевые события продукта: заказ создан, оплата прошла, подписка отменена.

Этого хватает, чтобы разобраться почти в любой проблеме. Если нужно понять, кто этот пользователь, это делается в основной базе, где есть права доступа и журнал.

## Что не писать никогда

- пароли, коды подтверждения и одноразовые ссылки;
- токены, ключи API, заголовки `Authorization` и `Cookie`;
- номера карт и платёжные реквизиты;
- тела запросов и ответов целиком;
- содержимое обращений к ИИ, если туда попадает текст пользователя.

Почты и телефоны, если без них никак, маскируй: `a***@example.com`, последние две цифры номера. IP-адрес тоже персональные данные во многих юрисдикциях; если он нужен для борьбы со злоупотреблениями, храни его коротко или в обрезанном виде.

## Разрешённые поля, а не запрещённые

Список того, что нельзя логировать, всегда отстаёт: завтра появится поле `passport_number`, и о нём никто не вспомнит. Надёжнее обратный подход — логировать объекты только через явный набор разрешённых полей. Маскирование на уровне логгера остаётся страховкой для того, что всё-таки проскочило:

```js
const logger = pino({
  redact: {
    paths: ["req.headers.authorization", "req.headers.cookie", "*.password", "*.token"],
    censor: "[redacted]",
  },
});
```

Отдельно проверь трекер ошибок. Многие из них по умолчанию собирают тела запросов, куки и локальные переменные — у них есть настройки очистки, и их стоит включить в первый же день.

## Секреты в адресах

Ссылка для сброса пароля с токеном в параметре запроса попадает в логи прокси, балансировщика и CDN, причём в те, которые ты не контролируешь. Если токен в адресе неизбежен, он должен быть одноразовым и короткоживущим, а логирование адресов — без параметров запроса.

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

Каждый входящий запрос получает уникальный идентификатор на самом первом шаге: на прокси или в первом обработчике. Дальше он передаётся во все внутренние вызовы, попадает в фоновые задачи, которые запрос поставил, и пишется в каждую строку лога.

Это даёт две вещи. Во-первых, по одному идентификатору видна вся история запроса через все сервисы. Во-вторых, его можно вернуть в заголовке ответа и показать на странице ошибки. Человек пишет в поддержку «у меня ошибка, код такой-то», и ты находишь её за секунды, не спрашивая его почту и не ища по времени.

## Сколько хранить

Подробные логи нужны для разбора недавних проблем, и обычно хватает пары недель. Агрегированные метрики — сколько запросов, сколько ошибок — можно хранить сколько угодно, в них нет людей. Журнал аудита — кто что поменял в данных — это отдельная история со своими правилами и более долгим сроком, и смешивать его с отладочными логами не стоит.

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

## Кто читает

Доступ к логам — такой же доступ к данным, как к базе. Его стоит выдавать по необходимости и пересматривать, а внешним подрядчикам давать конкретную выборку, а не весь поток.

SUBXCAT может пройтись по тому, что сейчас пишется в логи твоего проекта, и настроить маскирование и идентификаторы запросов.
