Аудит лог действий пользователей - это первое, о чём спрашиваю клиента, когда разбираюсь с инцидентом на его сайте или в админке: кто удалил заказ, кто поменял цену в карточке товара, откуда взялась выгрузка базы клиентов. Без записи событий такие вопросы остаются без ответа, а разбирательство превращается в перебор бэкапов наугад. Ниже разберу, какие события обязательно попадают в журнал, из каких полей состоит запись и как защитить лог от правки задним числом.
Зачем нужен журнал действий пользователей
На практике аудит-лог решает три разные задачи, и путать их не стоит.
Первая - разбор инцидентов и споров. Если клиент пишет в поддержку «я не подтверждал оплату» или «у меня пропал заказ», без журнала остаётся только развести руками. С логом видно: пользователь с таким 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 и переношу партиции старше года в архивную таблицу или в отдельную базу. Удалять старые записи сразу не тороплюсь: сначала архив, и только потом, по истечении срока хранения, удаление.