ayup-creative/event-log

There is no license information available for the latest version (dev-main) of this package.

Event log package for models in a Laravel application.

Maintainers

Package info

github.com/Ayup-Creative/laravel-event-log

pkg:composer/ayup-creative/event-log

Statistics

Installs: 15

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

dev-main 2026-03-18 17:12 UTC

This package is auto-updated.

Last update: 2026-03-18 17:12:11 UTC


README

PHP Tests Latest Version on Packagist Total Downloads License

A high-performance, structured, asynchronous, and relational event logging package for Laravel. Designed for auditability, traceability, and cross-service correlation without storing sensitive payload data.

Core Design Goals

  • Log Facts, Not State: Records what happened (the event), not the resulting state of the model.
  • Privacy by Design: Avoids storing sensitive data, JSON blobs, or PII. It links to models instead of copying their data.
  • Async-First: All event persistence is handled via Laravel's queue system to ensure zero impact on request performance.
  • Exactly-Once Delivery: Built-in idempotency ensures that retried queue jobs do not create duplicate event records.
  • Relational Graph: Supports a primary "subject" and multiple "related" models per event, allowing for complex traceability.
  • Audit Ready: Captures causers (users, system, jobs), correlation IDs, and transaction IDs.
  • OpenTelemetry Ready: Seamlessly integrates with distributed tracing systems.

Installation

You can install the package via composer:

composer require ayup-creative/event-log

The service provider will automatically register itself.

You should publish and run the migrations:

php artisan vendor:publish --tag="event-log-migrations"
php artisan migrate

You can optionally publish the config file:

php artisan vendor:publish --tag="event-log-config"

Configuration

The published config file config/event-log.php allows you to customize the models used by the package:

return [
    /*
     * The model used to represent users in your application.
     * This is used for the 'causer' relationship.
     */
    'user_model' => \App\Models\User::class,

    /*
     * The Eloquent models used for event logs and their relations.
     * You can extend these to add your own logic or relationships.
     */
    'event_model' => \AyupCreative\EventLog\Models\EventLog::class,
    'relation_model' => \AyupCreative\EventLog\Models\EventLogRelation::class,

    /*
     * The name of the queue that event log jobs should be sent to.
     * It is recommended to use a lower priority queue.
     */
    'queue' => 'event-log',
];

Usage

1. Manual Domain Events

Use the event_log helper to record significant domain events asynchronously.

// Basic event
event_log('organisation.created', $organisation);

// Event with related models
event_log('user.enrolled', $user, [$organisation, $course]);

// Event with additional metadata (e.g., tracking reasons, API errors)
event_log('payment.failed', $payment, metadata: [
    'error_reason' => 'Insufficient funds',
    'provider' => 'Stripe'
]);
  • Event Name: A human-readable dot-notation string.
    • Subject: The primary Eloquent model the event is about.
    • Related: (Optional) An array of additional Eloquent models linked to this event.
    • Causer Type: (Optional) Explicitly set the type of actor ('user', 'system', 'worker', 'cron').
    • Metadata: (Optional) Key-value pairs of additional context for the event.

2. Automatic Lifecycle Logging

Add the LogsEvents trait to any Eloquent model to automatically log lifecycle events (created, updated, deleted, restored).

use AyupCreative\EventLog\Features\LogsEvents;
use Illuminate\Database\Eloquent\Model;

class Mandate extends Model
{
    use LogsEvents;
}

Customizing Trait Behavior

You can override these methods in your model:

class Mandate extends Model
{
    use LogsEvents;

    // Change the dot-notation prefix (defaults to snake_case of class name)
    public function eventNamespace(): string
    {
        return 'billing.mandate';
    }

    // Filter which events should be logged
    public function shouldLogEvent(string $event): bool
    {
        return $event !== 'mandate.updated';
    }

    // Attach related models to automatic lifecycle events
    public function eventRelations(string $event): array
    {
        return [$this->organisation];
    }

    // Include automatic metadata in lifecycle logs
    public function eventMetadata(string $event): array
    {
        return ['type' => $this->type];
    }
}

3. Custom Actor & Causer Resolution

By default, the package uses auth()->id() to identify the current user and determines if the action was triggered by a 'user' (web request) or 'worker' (CLI).

You can customize this behavior using the EventLog Facade in your AppServiceProvider:

use AyupCreative\EventLog\Facades\EventLog;

public function boot()
{
    // Resolve the current actor ID (e.g., from a custom auth system)
    EventLog::resolveActorWith(function ($app) {
        return auth('api')->id();
    });

    // Determine the type of causer based on context
    EventLog::determineCauserTypeWith(function ($app) {
        if ($app->runningInConsole()) {
            return 'cron';
        }
        return 'user';
    });
}

4. Grouping Events with Transactions

Use the WithEventTransaction wrapper to group multiple events occurring within a single database transaction. This assigns a shared transaction_id to all events logged inside the closure.

use AyupCreative\EventLog\Support\WithEventTransaction;

WithEventTransaction::run(function () use ($user, $org) {
    $org->save();
    $user->organisations()->attach($org);

    event_log('organisation.created', $org);
    event_log('user.enrolled', $user, [$org]);
});

5. Correlation IDs & Middleware

Correlation IDs allow you to trace a logical action across multiple services and background jobs.

Global Middleware

Add the EventCorrelationMiddleware to your global or web/api middleware stack to automatically capture or generate a correlation ID for every request.

// app/Http/Kernel.php or bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
    $middleware->append(\AyupCreative\EventLog\Http\Middleware\EventCorrelationMiddleware::class);
})

Propagation via HTTP Client

The package adds a macro to the Laravel HTTP client to easily propagate the correlation ID:

use Illuminate\Support\Facades\Http;

Http::withEventContext()->post('https://api.other-service.com/data');

6. Human-Readable Event Formatting

You can map internal dot-notation event names (e.g., user.created) to human-readable strings (e.g., A new user was created). This is useful for displaying a timeline of events to end-users.

Using the Facade (Closure-based)

Register a formatter in your AppServiceProvider:

use AyupCreative\EventLog\Facades\EventLog;

public function boot()
{
    EventLog::formatEventsWith(function ($eventLog) {
        return match ($eventLog->event) {
            'user.created' => "User {$eventLog->subject->name} joined the platform",
            'payment.failed' => "Payment failed: {$eventLog->meta->error_reason}",
            default => $eventLog->event,
        };
    });
}

Using a Formatter Class (Cache-friendly)

For better performance and to keep your AppServiceProvider clean, you can use a dedicated class. This is also required if you want to use php artisan config:cache, as closures cannot be serialized.

  1. Create your formatter class:
namespace App\Support;

class MyEventFormatter
{
    public function __invoke($eventLog)
    {
        // Custom logic to return a human-readable string
        return "Action: " . $eventLog->event;
    }
}
  1. Register it in config/event-log.php:
'event_formatter' => \App\Support\MyEventFormatter::class,

Accessing the Formatted Description

Once a formatter is registered, you can access the human-readable string via the description attribute on the EventLog model:

$eventLog = EventLog::getFor($user)->first();
echo $eventLog->description; // "User John Doe joined the platform"

Querying Events

You can retrieve a unified timeline of events for any model using the EventLog Facade. This returns events where the model is either the subject or a related model.

use AyupCreative\EventLog\Facades\EventLog;

// Get all events for a model
$events = EventLog::getFor($organisation);

foreach ($events as $log) {
    echo "{$log->description} caused by {$log->causerLabel()}";
    
    // Access metadata
    echo $log->meta->error_reason;
}

// Paginated version
$paginatedEvents = EventLog::getForPaginated($organisation);

Causer Labels

The EventLog model provides a causerLabel() helper to identify the actor:

  • user: Returns the user's name (if authenticated).
  • system: Internal system action.
  • job: Action triggered by a background job.
  • webhook: Action triggered by an external service.

Metadata Helper

The meta attribute provides a shorthand for the metadata collection, allowing you to access values directly as properties:

echo $log->meta->error_reason;

Alternatively, you can access the full metadata relationship, which returns an Eloquent collection of metadata models.

Advanced Features

Idempotency

To prevent duplicate logs during queue retries, the package generates a deterministic idempotency_key for every event. If a job runs twice, the database uniqueness constraint will silently prevent the second record from being created.

OpenTelemetry Bridge

If the open-telemetry/opentelemetry package is installed, the logger will automatically create spans for each event. The correlation_id is used to maintain trace context.

Testing

The package includes a comprehensive test suite. To run the tests:

composer test

Or manually:

vendor/bin/phpunit

License

The MIT License (MIT).