Разработка · 6 мин чтения

Аудит лог действий пользователей в веб-приложении: что логировать и зачем

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

Зачем нужен журнал действий пользователей

На практике аудит-лог решает три разные задачи, и путать их не стоит.

Первая - разбор инцидентов и споров. Если клиент пишет в поддержку «я не подтверждал оплату» или «у меня пропал заказ», без журнала остаётся только развести руками. С логом видно: пользователь с таким IP авторизовался, нажал «оплатить», платёжная система прислала статус success, заказ перешёл в обработку. На проектах с оплатой через T‑Bank или другой эквайринг именно такие записи закрывают спор за несколько минут вместо разбирательства с банком по выпискам.

Вторая - контроль за администраторами и сотрудниками. Обычные пользователи вредят себе, админы вредят всем сразу: могут выгрузить базу клиентов перед увольнением, обнулить остатки на складе, поменять реквизиты для выплат. Без лога такие действия видны только постфактум, когда ущерб уже случился.

Третья - отладка интеграций. Вебхук от СДЭК не обновил статус доставки, бот на aiogram не записал заявку, n8n-сценарий не отправил уведомление. В большинстве случаев причина видна сразу, если в логе есть запись о том, что вебхук вообще пришёл и с каким телом запроса.

Какие события обязательно попадают в аудит-лог

Не пытаюсь логировать вообще всё - журнал распухнет, а искать в нём нужную запись станет невозможно. На своих проектах фиксирую минимум такой список:

  • Вход и выход из системы, включая неудачные попытки входа
  • Смена пароля, email или телефона, привязанного к аккаунту
  • Создание, изменение и удаление записей, которые влияют на деньги: заказ, платёж, скидка, цена товара
  • Изменение ролей и прав доступа
  • Действия администраторов в панели управления, отдельно от действий обычных пользователей
  • Экспорт данных, особенно персональных или платёжных
  • Изменение статуса платежа и данные от эквайринга
  • Обработка вебхуков от внешних сервисов: СДЭК, платёжные системы, Telegram-бот

Чтение данных в этот список не включаю - объём записей вырастет в разы, а пользы для разбора инцидентов почти нет. Исключение - просмотр чужих персональных данных в CRM: это стоит фиксировать отдельно, если к системе есть доступ у нескольких сотрудников.

Бесплатный материал

🎁 Полезный скрипт в подарок

Подпишитесь на Telegram - пришлю готовый скрипт по этой теме.

Без спама. Отписка в 1 клик.

Из чего состоит запись в журнале действий

Одной строки «пользователь 42 удалил заказ» недостаточно - через месяц непонятно, с какого устройства, по чьей команде и что именно было до удаления. Минимальный набор полей выглядит так:

Поле Что содержит
created_at точное время события с точностью до секунды
user_id идентификатор пользователя, который выполнил действие
role роль пользователя на момент действия, а не текущая
ip_address IP-адрес, с которого пришёл запрос
user_agent браузер или клиент, из которого пришёл запрос
action тип действия, например order.update или login.failed
entity_type, entity_id тип и идентификатор объекта, с которым работали
old_value, new_value значения до и после изменения для критичных полей
result успех или отказ, особенно важно для попыток входа

Поля old_value и new_value храню не для всех действий подряд, а точечно: для цены, скидки, суммы платежа, роли пользователя. Для остальных действий достаточно факта, что событие произошло.

Где хранить журнал и как защитить его от правок

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

Вариант хранения Сложность внедрения Что учесть
Отдельная таблица в той же базе данных Низкая Быстро внедряется на обычном хостинге сайта, главное - ограничить права учётной записи приложения на уровне MySQL
Отдельная база на том же сервере Средняя Изоляция от основных данных и отдельный бэкап, нужен ещё один пользователь БД с урезанными правами
Внешний сервис логов (self-hosted, например Graylog) Высокая Это отдельный сервер: его нужно настраивать, обновлять, продлевать сертификат и оплачивать каждый месяц, обычного хостинга сайта для этого недостаточно

На практике для большинства сайтов достаточно первого варианта, если сразу забрать у пользователя приложения права на изменение и удаление по таблице лога:

mysql -u root -p shop_db -e "
CREATE TABLE audit_log (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  created_at DATETIME NOT NULL,
  user_id INT UNSIGNED NULL,
  role VARCHAR(50) NULL,
  ip_address VARCHAR(45) NOT NULL,
  action VARCHAR(100) NOT NULL,
  entity_type VARCHAR(50) NULL,
  entity_id INT UNSIGNED NULL,
  old_value TEXT NULL,
  new_value TEXT NULL,
  result VARCHAR(20) NOT NULL
);
"

mysql -u root -p shop_db -e "
REVOKE UPDATE, DELETE ON shop_db.audit_log FROM 'app_user'@'%';
GRANT INSERT, SELECT ON shop_db.audit_log TO 'app_user'@'%';
"

После этого даже если через уязвимость в коде получится выполнить произвольный SQL-запрос от имени приложения, изменить или удалить уже записанные события не выйдет - только добавить новые.

Практическая реализация: middleware, триггеры и уведомления

Записывать событие в аудит-лог обычно делаю на уровне middleware или сервисного слоя, а не в каждом контроллере отдельно - иначе часть действий забудут залогировать при следующей доработке. Один метод вроде logAction() с параметрами пользователь, действие, объект, старое и новое значение вызывается из всех мест, где меняются деньги, роли или доступ.

Для критичных таблиц вроде orders или users дополнительно ставлю MySQL-триггер на UPDATE и DELETE - он пишет в audit_log, даже если изменение пришло не через приложение, а прямым запросом от разработчика или скриптом миграции.

Если нужно поднять такой аудит-лог с нуля в рамках нового бэкенда на стороне API, обычно это часть разработки бэкенда на Laravel: логирование сразу закладывается в middleware вместе с аутентификацией и правами доступа.

Отдельно логирую действия в Telegram-ботах на aiogram - там своя таблица событий бота: кто из пользователей запустил сценарий, какую заявку оформил, на каком шаге отвалился. Для мониторинга подозрительной активности, например десяти неудачных попыток входа за минуту, использую n8n: сценарий проверяет таблицу audit_log и шлёт уведомление в Telegram, если порог превышен.

Аудит-лог и требования 152-ФЗ

Журнал действий пользователей сам по себе - персональные данные: там есть привязка IP-адреса и идентификатора пользователя к конкретному человеку. Из этого следует три практических вывода.

Хранить такие логи нужно на серверах в России, как и остальные персональные данные проекта, а не в стороннем зарубежном облаке для логов.

Доступ к журналу стоит давать не всей команде, а конкретным людям, которым он нужен по работе, в идеале с отдельным логированием того, кто и когда открывал сам аудит-лог.

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

Частые вопросы

Сколько хранить журнал действий пользователей?

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

Нужно ли логировать чтение данных, а не только изменения?

В общем случае нет - объём записей вырастет в разы, а разбирать инциденты станет сложнее, а не проще. Исключение - системы, где несколько сотрудников имеют доступ к чужим персональным данным, например к базе клиентов в CRM. Там просмотр карточки клиента тоже стоит фиксировать, потому что утечка данных чаще случается через обычный просмотр, чем через что-то экзотическое.

Можно ли использовать один и тот же лог для отладки и для аудита безопасности?

Технически можно писать в одну таблицу, но лучше разделять по смыслу с самого начала. Отладочные логи вроде ошибок и времени ответа живут неделями, их можно чистить агрессивно. Аудит-лог живёт месяцами и годами, его нельзя чистить по расписанию просто потому, что таблица разрослась. Если смешать оба потока, рано или поздно кто-то напишет скрипт очистки логов для производительности и случайно снесёт записи, нужные для разбора инцидента.

Что делать, если объём таблицы с логом растёт слишком быстро?

Сначала проверяю, не попало ли в список логируемых событий что-то лишнее вроде обычного чтения страниц. Если список событий адекватный, а объём всё равно большой, использую партиционирование таблицы по дате в MySQL и переношу партиции старше года в архивную таблицу или в отдельную базу. Удалять старые записи сразу не тороплюсь: сначала архив, и только потом, по истечении срока хранения, удаление.

Есть задача?

Обсудим в мессенджере

Расскажите, что нужно сделать — отвечу в течение 4 часов в рабочее время. Первая консультация бесплатно.

Продолжая пользование настоящим сайтом Вы выражаете своё согласие на обработку Ваших персональных данных (файлов куки) с использованием Yandex.Metrika.
Понятно