Queue Support

Recording audit logs involves database writes — including hash computation and a locked transaction in full mode. By default, AuditChain dispatches these writes to a queued job so they do not slow down HTTP requests. The queue is enabled out of the box; this page covers how it works and when you might want to change the defaults.

Configuration

// config/audit-chain.php

'queue' => [
    'enabled' => true,
    'connection' => null,
    'queue' => 'default',
],
Key Default Description
queue.enabled true Dispatch audit recording to a queued job
queue.connection null Queue connection (null = application default)
queue.queue default Queue name for audit jobs

How It Works

When queue is enabled, audit log creation follows this flow:

  1. The Eloquent event fires (created, updated, deleted, etc.)
  2. The audit payload is built synchronously (old/new values, user, IP, user agent, context, batch UUID)
  3. A RecordAuditLog job is dispatched to the queue
  4. The queue worker picks up the job and writes the audit log to the database

The RecordAuditLog job implements ShouldQueue and includes sensible defaults:

  • 3 retries on failure
  • 5 second backoff between retries
  • 30 second timeout

Dedicated Queue

To avoid audit recording competing with other queued jobs (emails, notifications, exports), use a dedicated queue:

'queue' => [
    'enabled' => true,
    'connection' => 'redis',
    'queue' => 'auditing',
],

Then run a dedicated worker for audit jobs:

php artisan queue:work redis --queue=auditing

The isChained Property

The RecordAuditLog job uses an isChained property to distinguish between light mode (activity log) and full mode (audit trail) recording:

class RecordAuditLog implements ShouldQueue
{
    public function __construct(
        public readonly array $auditData,
        public readonly bool $isChained = false,
    ) {}
}

This property is intentionally named isChained rather than chained to avoid a conflict with Laravel's Queueable trait, which defines its own $chained property for job chaining. If the property were named chained, it would collide with the trait and cause unexpected behavior.

When isChained is true, the job calls DatabaseDriver::storeChained() which runs the hash chain computation inside a locked transaction. When false, it calls DatabaseDriver::store() for a simple insert with null hashes.

When to Disable Queuing

Set queue.enabled to false to record audit logs synchronously:

'queue' => [
    'enabled' => false,
],

Disable queuing when:

  • You are not running a queue worker — If no worker is processing jobs, audit logs will sit in the queue indefinitely and never be recorded.
  • You need immediate audit visibility — Some workflows require that the audit log exists in the database before the HTTP response is sent (e.g., a compliance API that returns the audit log ID).
  • Testing — Synchronous recording is simpler to test against. AuditChain's own test suite runs with queue disabled.
  • Low-traffic applications — If the overhead of a synchronous database write is negligible for your use case, the simplicity of synchronous recording may be preferable.

When to Enable Queuing

Keep queuing enabled (the default) when:

  • High-traffic applications — Offloading writes to a queue worker keeps HTTP response times consistent.
  • Audit trail mode with lockingHasAuditTrail uses lockForUpdate() to maintain chain ordering. Under high concurrency, this lock can briefly delay requests. A queue serializes these writes naturally.
  • Separate database connections — If your audit database is on a different server, the network round-trip is better absorbed by a background worker than by the request cycle.

Queue Failures

The RecordAuditLog job implements a failed() method that logs a critical error when a job permanently fails after all retries:

Log::critical('Audit log permanently lost after retries', [...])

This is important for audit completeness — you should monitor for these critical log entries in your logging infrastructure (e.g., via log aggregation alerts). A permanently failed audit job means an event occurred but was never recorded, which could create gaps in your audit trail.

If a queued audit job fails after all retries, it also lands in Laravel's failed_jobs table. Monitor this table (or use Laravel Horizon for Redis queues) to catch audit recording failures.

Missing audit logs in a hash chain will cause audit:verify to report chain breaks. Scheduled verification with --notify will alert you to these gaps. See Notifications for setup details.