Реализация системы аудита действий пользователей на сайте
Аудит действий — журналирование всех значимых событий в системе: кто, что и когда сделал. Необходим для расследования инцидентов, соответствия регуляторным требованиям (152-ФЗ, GDPR), отслеживания изменений в критических данных.
Что логировать
Обязательно:
- Вход / выход / неудачные попытки входа
- Изменение пароля, email, номера телефона
- Изменения прав доступа и ролей
- Создание, редактирование, удаление критических сущностей
- Платёжные операции
- Экспорт данных
По необходимости:
- Просмотр персональных данных других пользователей
- API-запросы к административным эндпоинтам
- Изменения конфигурации системы
Структура таблицы аудита
CREATE TABLE audit_logs (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT REFERENCES users(id) ON DELETE SET NULL,
event VARCHAR(100) NOT NULL, -- 'user.password_changed'
subject_type VARCHAR(100), -- 'App\Models\User'
subject_id BIGINT, -- ID изменённой сущности
old_values JSONB, -- состояние до
new_values JSONB, -- состояние после
ip_address INET,
user_agent TEXT,
session_id VARCHAR(100),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_audit_user ON audit_logs(user_id);
CREATE INDEX idx_audit_event ON audit_logs(event);
CREATE INDEX idx_audit_subject ON audit_logs(subject_type, subject_id);
CREATE INDEX idx_audit_created ON audit_logs(created_at DESC);
Реализация через пакет (Laravel)
Пакет owen-it/laravel-auditing — наиболее распространённый выбор:
// composer require owen-it/laravel-auditing
// В модели
use OwenIt\Auditing\Contracts\Auditable;
class User extends Model implements Auditable
{
use \OwenIt\Auditing\Auditable;
// Исключить из аудита чувствительные поля
protected $auditExclude = ['password', 'remember_token'];
// Только при определённых событиях
protected $auditEvents = ['created', 'updated', 'deleted'];
}
Кастомная реализация через Observer
// app/Observers/AuditObserver.php
class AuditObserver
{
public function updated(Model $model): void
{
if (!$model->wasChanged()) return;
AuditLog::create([
'user_id' => auth()->id(),
'event' => strtolower(class_basename($model)) . '.updated',
'subject_type' => get_class($model),
'subject_id' => $model->getKey(),
'old_values' => $model->getOriginal(),
'new_values' => $model->getChanges(),
'ip_address' => request()->ip(),
'user_agent' => request()->userAgent(),
]);
}
}
// Регистрация в AppServiceProvider
User::observe(AuditObserver::class);
Order::observe(AuditObserver::class);
Middleware для аудита HTTP-запросов
// app/Http/Middleware/AuditRequests.php
class AuditRequests
{
private array $auditedRoutes = [
'admin.*',
'api.users.*',
'api.settings.*',
];
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
if ($this->shouldAudit($request)) {
AuditLog::create([
'user_id' => auth()->id(),
'event' => 'http.' . strtolower($request->method()),
'new_values' => [
'url' => $request->url(),
'method' => $request->method(),
'status' => $response->status(),
],
'ip_address' => $request->ip(),
]);
}
return $response;
}
}
Аудит входа/выхода
// Через Laravel события
Event::listen(Login::class, function (Login $event) {
AuditLog::create([
'user_id' => $event->user->id,
'event' => 'auth.login',
'new_values' => ['guard' => $event->guard],
'ip_address' => request()->ip(),
'user_agent' => request()->userAgent(),
]);
});
Event::listen(Failed::class, function (Failed $event) {
AuditLog::create([
'user_id' => null,
'event' => 'auth.failed',
'new_values' => ['email' => $event->credentials['email'] ?? null],
'ip_address' => request()->ip(),
]);
});
Производительность
Запись аудита синхронно в каждом запросе — нагрузка на БД. Решения:
// Асинхронная запись через очереди
dispatch(new WriteAuditLog($data))->onQueue('audit');
// Пакетная вставка — накапливать в Redis, сбрасывать раз в минуту
Redis::rpush('audit_queue', json_encode($data));
// Отдельная БД для аудита
// config/database.php
'audit' => [
'driver' => 'pgsql',
'database' => 'audit_db',
// ...
]
Хранение и ротация
// Политика хранения — удалять записи старше N лет
// Для 152-ФЗ минимум 3 года, для PCI DSS — 1 год
class CleanOldAuditLogs extends Command
{
public function handle(): void
{
AuditLog::where('created_at', '<', now()->subYears(3))->delete();
}
}
Интерфейс просмотра аудита
Ключевые фильтры в административном интерфейсе:
- По пользователю
- По типу события
- По временному диапазону
- По сущности (тип + ID)
- По IP-адресу
Срок реализации
- Базовая модель + Observer для ключевых сущностей: 2–3 дня
- Полноценная система с очередями, ротацией, UI: 5–7 дней







