Yammi Audit Log: Track Who Really Made a Change Across Jobs and Queues
Last updated on by Yannick Lyn Fatt
Most audit packages can tell you when a field changed and who changed it. They struggle once the change happens, two queue hops away from the request that triggered it. Yammi Audit Log by RomaLytar addresses that gap: alongside the before and after values, it records who executed a change, who initiated it, and a correlation ID that ties the request, job, command, or scheduled task into a single trace.
Auditing With No Per-Model Changes
There are no traits to add, no interface to implement, and no observers to register. After installing the package and running the migrations, Eloquent writes are recorded across every model:
User::first()->update(['name' => 'Eric']);
That update is already in the audit log without touching the User model. Capture defaults to every model, but you can flip it to opt-in mode in config/audit-log.php if you only want to track a defined set:
'capture' => ['mode' => env('AUDIT_LOG_CAPTURE_MODE', 'all')], // all | opt_in'retention' => ['days' => env('AUDIT_LOG_RETENTION_DAYS', 180)],'write' => ['async' => env('AUDIT_LOG_WRITE_ASYNC', false)],
One caveat worth noting up front: it hooks into Eloquent model events, so direct Query Builder updates are not captured automatically and must be recorded explicitly.
Keeping the Actor Through Queued Work
This is the part that sets Yammi apart from a typical changelog. When a user dispatches a job that later modifies a model, a normal audit trail attributes the change to the queue worker, because that is the process that ran the write. Yammi carries the original identity through the chain, so the record distinguishes between the actor who executed the change and the person who initiated it. The correlation ID then groups every entry that belongs to the same workflow, letting you reconstruct a cascade of changes spanning a controller, a queued job, and a scheduled command rather than treating them as unrelated rows.
The recorded context also notes where the change ran, whether that was an HTTP request, a queued job, an artisan command, or the scheduler, which is useful when the same model is written from several entry points.
Built to Stay Out of the Way
The write path is designed so that auditing does not cause a request to fail or slow down. Each change is a single insert plus a batched index of the fields that changed, and those field entries go into a dedicated indexed table, so searching by field does not mean scanning JSON columns. Writes can be pushed onto a queue when you would rather not pay for them in-line:
'write' => ['async' => env('AUDIT_LOG_WRITE_ASYNC', false)],
The capture path is also fail-open, so if the audit write itself errors, the underlying operation still goes through instead of taking the request down with it.
Optional Forensics and Compliance Layers
Beyond the core trail, Yammi ships a set of subsystems that stay inactive until you turn them on. Integrity mode adds a tamper-evident hash chain so you can detect after the fact whether stored entries were altered, and a time machine view reconstructs a record's state at a past point. There is GDPR tooling for subject reports and retention handling, anomaly detection, streaming to a SIEM, Slack and webhook alerts, and multi-tenancy support. An optional dashboard lives behind its own command:
composer require romalytar/yammi-audit-log-laravelphp artisan migratephp artisan audit-log:ui enable
The dashboard and the integrity, forensics, and compliance features are all opt-in, which keeps the default install close to a plain change log and lets you add the heavier machinery only where an application actually needs it.
Yammi runs on PHP 8.1+ and Laravel 9 through 13, and works with any database Laravel supports. You can read the documentation and browse the source on GitHub.