User activity audit system implementation for website

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:
Development stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    847
  • image_website-sbh_0.png
    Website development for SBH Partners
    999
  • image_website-_0.png
    Website development for Red Pear
    451

Implementing user activity audit system on a website

Audit logging is journaling all significant events in the system: who, what, and when did they do something. Essential for incident investigation, compliance with regulatory requirements (152-ФЗ, GDPR), tracking changes to critical data.

What to log

Essential:

  • Login / logout / failed login attempts
  • Password, email, phone number changes
  • Changes to access rights and roles
  • Creation, editing, deletion of critical entities
  • Payment transactions
  • Data exports

As needed:

  • Viewing other users' personal data
  • API requests to administrative endpoints
  • System configuration changes

Audit log table structure

CREATE TABLE audit_logs (
    id          BIGSERIAL PRIMARY KEY,
    user_id     BIGINT REFERENCES users(id) ON DELETE SET NULL,
    event       VARCHAR(100) NOT NULL,
    subject_type VARCHAR(100),
    subject_id   BIGINT,
    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);

Implementation via package (Laravel)

Package owen-it/laravel-auditing is the most common choice:

// 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'];
}

Custom implementation via Observer

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(),
        ]);
    }
}

User::observe(AuditObserver::class);
Order::observe(AuditObserver::class);

HTTP request audit middleware

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;
    }
}

Login/logout audit

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(),
    ]);
});

Performance

Synchronous audit logging in each request impacts database load. Solutions:

// Asynchronous logging via queues
dispatch(new WriteAuditLog($data))->onQueue('audit');

// Batch insertion — accumulate in Redis, flush once per minute
Redis::rpush('audit_queue', json_encode($data));

// Separate audit database
'audit' => [
    'driver'   => 'pgsql',
    'database' => 'audit_db',
]

Storage and rotation

// Retention policy — delete records older than N years
// For 152-ФЗ minimum 3 years, for PCI DSS — 1 year

class CleanOldAuditLogs extends Command
{
    public function handle(): void
    {
        AuditLog::where('created_at', '<', now()->subYears(3))->delete();
    }
}

Audit viewing interface

Key filters in administrative interface:

  • By user
  • By event type
  • By time range
  • By entity (type + ID)
  • By IP address

Implementation Timeline

  • Basic model + Observer for key entities: 2–3 days
  • Full system with queues, rotation, UI: 5–7 days