Laravel Event Logger is an event log, not an error log.
It focuses on recording facts about what happened in your system – “user enrolled”, “mandate created”, “payment failed”, “organisation created” – in a way that is:
- Structured and relational
- Privacy‑aware
- Queue‑friendly
- Ready for audit trails and distributed tracing
Instead of spraying JSON blobs or free‑text messages into a log file, you get a clean event model with:
- A primary subject (the main model the event is about)
- One or more related models (for cross‑entity traceability)
- A causer (user, system, worker, webhook, cron, etc.)
- Lightweight metadata (key‑value context, no sensitive payloads)
- Correlation IDs and transaction IDs for tracing flows across processes and services
Core design goals
This is the intent behind the package, in your own words:
- Log facts, not state
The log records events (“user.enrolled”, “payment.failed”), not full snapshots of your models. That keeps storage lean and the audit trail focused on what actually happened. - Privacy by design
No dumping of full JSON blobs, PII, or sensitive payloads. Events link back to models via relationships so you can always resolve the subject/related records without duplicating their data. - Async‑first
All persistence goes through Laravel’s queue system. Logging events does not slow down your HTTP requests or block users. You can even send event logging to a low‑priority queue. - Exactly‑once delivery
Built‑in idempotency means retried queue jobs will not create duplicate records. If a job is re‑run, the database uniqueness constraint will quietly drop the second insert. - Relational graph of events
Each event can link to a primary subject and multiple related models. That gives you a graph of interactions across entities instead of siloed activity on a single model. - Audit‑ready
Events can capture causer type and ID, correlation IDs, transaction IDs, and metadata. That’s enough to build an audit trail or regulatory report without re‑inventing your own event schema. - OpenTelemetry‑ready
If you’re using OpenTelemetry, the package can act as a bridge, using correlation IDs to plug your event log into distributed tracing.
Installation via Composer
composer require ayup-creative/event-logThe service provider is auto‑discovered.
Next, you’ll need to publish the migrations, and run them.
php artisan vendor:publish --tag="event-log-migrations"
php artisan migrateYou can also optionally publish the config if you wish to specify your own Event or EventMetaData models:
php artisan vendor:publish --tag="event-log-config"Usage in a project
1. Manual domain events (event_log helper)
This is the primary API: a small helper that records meaningful domain events asynchronously.
// Basic event
event_log('organisation.created', $organisation);
// Event with related models
event_log('user.enrolled', $user, [$organisation, $course]);
// Event with metadata (e.g. reasons, provider results)
event_log('payment.failed', $payment, metadata: [
'error_reason' => 'Insufficient funds',
'provider' => 'Stripe',
]);Each call lets you specify:
- Event name – dot‑notation, human readable (
organisation.created,payment.failed). - Subject – the main Eloquent model this event is about.
- Related – optional array of additional models involved in the event.
- Causer type – optional (
user,system,worker,cron,webhook, etc.). - Metadata – optional key‑value context, kept small and deliberate.
Because logging runs through Laravel’s queue, these calls are cheap to sprinkle through domain services, listeners, or controllers without cost to the request lifecycle.
Automatic lifecycle logging (LogsEvents trait)
For models where you want a timeline of CRUD operations, you add a trait:
use AyupCreative\EventLog\Features\LogsEvents;
use Illuminate\Database\Eloquent\Model;
class Mandate extends Model
{
use LogsEvents;
}Out of the box this logs:
createdupdateddeletedrestored
You can customise how each model logs its lifecycle:
class Mandate extends Model
{
use LogsEvents;
// Use a specific prefix for events (default: snake_case class name)
public function eventNamespace(): string
{
return 'billing.mandate';
}
// Filter which events to log
public function shouldLogEvent(string $event): bool
{
return $event !== 'mandate.updated';
}
// Attach related models automatically</em>
public function eventRelations(string $event): array
{
return [$this->organisation];
}
// Add automatic metadata per event
public function eventMetadata(string $event): array
{
return ['type' => $this->type];
}
}This lets you turn any Eloquent model into something with a first‑class event trail, without writing listeners by hand.
3. Custom actor & causer resolution
By default, the package uses auth()->id() and checks whether the app is running in the console to decide:
- Who caused the event
- What type of causer it is (
user,worker,system,cron, etc.)
You can override that in your AppServiceProvider:
use AyupCreative\EventLog\Facades\EventLog;
public function boot()
{
// Custom actor resolution (e.g. API guard)
EventLog::resolveActorWith(function ($app) {
return auth('api')->id();
});
// Custom causer type logic
EventLog::determineCauserTypeWith(function ($app) {
if ($app->runningInConsole()) {
return 'cron';
}
return 'user';
});
}This is useful in multi‑tenant, API‑only, or multi‑guard applications.
4. Event transactions (grouping events)
Sometimes multiple events belong to a single, atomic business operation. WithEventTransaction lets you group them and assign a shared transaction_id:
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]);
});Every event logged inside the closure shares the same transaction identifier. That makes it easy to reconstruct what happened within a particular operation, even across multiple models.
5. Correlation IDs & cross‑service tracing
The package ships with middleware and HTTP client helpers to keep a correlation ID flowing through your system.
Global middleware
use \AyupCreative\EventLog\Http\Middleware\EventCorrelationMiddleware;
// bootstrap/app.php or app/Http/Kernel.php
->withMiddleware(function (Middleware $middleware) {
$middleware->append(EventCorrelationMiddleware::class);
})This middleware:
- Reads an incoming correlation ID header (if present)
- Or generates a new one
- Attaches it to the request and response
- Shares it with the event logger
Propagating to other services:
use Illuminate\Support\Facades\Http;
Http::withEventContext()->post('https://api.other-service.com/data');withEventContext() makes sure your correlation ID travels with outbound HTTP calls, so you can follow a single logical action through multiple services and their event logs.
6. Human‑readable descriptions for timelines
Event names are intentionally machine‑friendly (user.created, payment.failed), but you often want something more readable for admins or end‑users.
You can register a formatter via the facade:
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,
};
});
}For config caching and cleaner boot code you can move that into a dedicated class:
namespace App\Support;
class MyEventFormatter
{
public function __invoke($eventLog)
{
return "Action: " . $eventLog->event;
}
}And register it in config/event-log.php:
'event_formatter' => \App\Support\MyEventFormatter::class,Once a formatter is registered, you can use:
$eventLog = EventLog::getFor($user)->first();
echo $eventLog->description;Perfect for building timelines or audit views.
Querying the event log
The package exposes helpers for building timelines around any model.
use AyupCreative\EventLog\Facades\EventLog;
// Unified timeline where $organisation is either subject or related
$events = EventLog::getFor($organisation);
foreach ($events as $log) {
echo "{$log->description} caused by {$log->causerLabel()}";
echo $log->meta->error_reason ?? '';
}
// Paginated version
$paginated = EventLog::getForPaginated($organisation);The EventLog model provides:
causerLabel()– a friendly label based on causer typemeta– a convenient accessor for metadata as a small object/collection:
echo $log->meta->error_reason;You still have access to the raw metadata relationship if you want full control.
Advanced behaviours
- Idempotency
A deterministicidempotency_keyper event, plus a database uniqueness constraint, ensures queue retries don’t produce duplicate log records. - OpenTelemetry bridge
If you haveopen-telemetry/opentelemetryinstalled, the logger can automatically create spans for events and tie them into your existing traces using the correlation ID. - Test suite
composer test
# or
vendor/bin/phpunitThe package ships with a full test suite verifying async persistence, idempotency, and relational behaviours.