PHPackages                             romalytar/yammi-audit-log-laravel - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. romalytar/yammi-audit-log-laravel

ActiveLibrary[Database &amp; ORM](/categories/database)

romalytar/yammi-audit-log-laravel
=================================

Universal change history and audit log for Laravel. Tracks who changed what and when across Eloquent models, with rich actor attribution (user, job, command, scheduler), field-level diffs, human-readable relationship labels and a timeline dashboard.

v2.2.0(4w ago)611373MITPHPPHP ^8.1CI passing

Since Jun 12Pushed 4w agoCompare

[ Source](https://github.com/RomaLytar/yammi-audit-log)[ Packagist](https://packagist.org/packages/romalytar/yammi-audit-log-laravel)[ Docs](https://github.com/RomaLytar/yammi-audit-log)[ RSS](/packages/romalytar-yammi-audit-log-laravel/feed)WikiDiscussions dev Synced 1w ago

READMEChangelog (6)Dependencies (24)Versions (9)Used By (0)

Yammi Audit Log - Laravel Change History &amp; Audit Trail
==========================================================

[](#yammi-audit-log---laravel-change-history--audit-trail)

[![Latest Version on Packagist](https://camo.githubusercontent.com/a414da1e25779f536d0233a9a91709c896c29d66706469d6a8d8d8e4a36f1a02/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f726f6d616c797461722f79616d6d692d61756469742d6c6f672d6c61726176656c2e7376673f763d31)](https://packagist.org/packages/romalytar/yammi-audit-log-laravel)[![Total Downloads](https://camo.githubusercontent.com/90ab3768385518cf0586d6d0659d63f2c22f7dd91360a52796d7c419bfc76864/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f726f6d616c797461722f79616d6d692d61756469742d6c6f672d6c61726176656c2e7376673f763d31)](https://packagist.org/packages/romalytar/yammi-audit-log-laravel)[![CI](https://github.com/RomaLytar/yammi-audit-log/actions/workflows/ci.yml/badge.svg?branch=dev)](https://github.com/RomaLytar/yammi-audit-log/actions/workflows/ci.yml)[![Coverage](https://camo.githubusercontent.com/4707f9b08bd9eb97968293945a9230e087c50e9d8d9a33dbbd6bd17dadde107b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f636f7665726167652d39352e352532352d627269676874677265656e)](https://github.com/RomaLytar/yammi-audit-log/actions/workflows/ci.yml)[![Tested on](https://camo.githubusercontent.com/f458a45ff311f3f0ca81076379e4cc77851de2a5621ab73046a2a37d57239e30/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f74657374732d504850253230382e31253230253743253230382e32253230253743253230382e332d3737374242343f6c6f676f3d706870266c6f676f436f6c6f723d7768697465)](https://github.com/RomaLytar/yammi-audit-log/actions/workflows/ci.yml)[![License](https://camo.githubusercontent.com/ac52b4d399a142a00b0c726f01ed4c11a35a2ba76a35257184ad3a40b3b73ed0/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f726f6d616c797461722f79616d6d692d61756469742d6c6f672d6c61726176656c2e7376673f763d31)](https://packagist.org/packages/romalytar/yammi-audit-log-laravel)

**Change history and execution tracing for distributed, queue-heavy Laravel apps.** Every change carries:

- **Actor**: who executed it
- **Origin**: who started it
- **Correlation id**: ties the whole cascade together

That is what separates it from most audit packages, which record only *what* changed.

Install
-------

[](#install)

```
composer require romalytar/yammi-audit-log-laravel
php artisan migrate
php artisan audit-log:ui enable          # optional dashboard at /audit-log
```

Capture is global from the first migration: no traits, no interfaces, no per-model registration. Defaults are safe out of the box (UI off until you enable it, 180-day retention, secrets redacted).

Try it
------

[](#try-it)

```
User::first()->update(['name' => 'Test']);
```

Open `/audit-log`. The change is already there, with its actor, origin and correlation id filled in. To explore richer data without writing any code, the in-app **Playground** (`/audit-log/settings/playground`) generates realistic sample cascades you can trace.

[![Audit log dashboard](screenshots/dashboard.png)](screenshots/dashboard.png)

Contents
--------

[](#contents)

- [Why this exists](#why-this-exists)
- [The provenance chain](#the-provenance-chain)
- [Zero model setup](#zero-model-setup)
- [What makes it different](#what-makes-it-different)
- [What's new in v2.2](#whats-new-in-v22)
- [How Yammi differs from traditional audit logs](#how-yammi-differs-from-traditional-audit-logs)
- [How it works](#how-it-works)
- [Requirements](#requirements)
- [Performance](#performance)
- [Advanced features](#advanced-features)
- [Security](#security)
- [Comparison](#comparison)
- [Non-goals](#non-goals)
- [Configuration](#configuration)
- [Documentation](#documentation)

Why this exists
---------------

[](#why-this-exists)

Most Laravel audit tools record *what* changed on a model. Real systems are distributed:

```
HTTP request  →  service  →  queue  →  job  →  model

```

By the time the row is written, the question that matters during an incident is hard to answer: *who actually triggered this change, and through what chain?* This package records the full execution context of every change, not just the final write.

The provenance chain
--------------------

[](#the-provenance-chain)

A user clicks "pay". A queued job makes the write. A traditional audit log records the job (or "system") and loses the user. Yammi keeps the whole chain:

```
User: John Doe
  ↓  dispatches
Job: ProcessPayment
  ↓  dispatches
Job: ChargeOrder
  ↓  writes
Order #42   status: pending → paid

actor         ChargeOrder (job)
origin        John Doe (user)
correlation   550e8400-e29b-41d4-a716-446655440000

```

That is the moat: **actor** (who executed the change), **origin** (who started it), and a **correlation id** that ties the whole cascade together. Read more in [Provenance](docs/provenance.md).

In a production incident, that is the difference:

```
Order #42 became "paid" at 14:02.

Traditional audit log:
  actor = ChargeOrderJob               (who triggered it? unknown)

Yammi:
  actor       = ChargeOrderJob
  origin      = John Doe
  correlation = 550e8400-...
  chain       = ProcessPayment  →  ChargeOrder  →  Order #42

```

The dashboard draws that whole chain as a tree you can expand and pan, so you see at a glance who started it and which change caused the next:

[![Change chain drawn as a causation tree](screenshots/trace.png)](screenshots/trace.png)

Zero model setup
----------------

[](#zero-model-setup)

No traits. No interfaces. No observers. No per-model registration.

```
// Nothing added to your models. This is already audited:
User::first()->update(['name' => 'Test']);
```

Install, migrate, done. Capture is global from the first migration. The optional traits exist only for special cases (pivot writes, read access).

Testing
-------

[](#testing)

Assert what your code audited without hitting the database:

```
AuditLog::fake();

$order->update(['status' => 'paid']);

AuditLog::assertRecorded(Order::class, $order->id, 'updated',
    fn ($record) => $record->diff()->field('status')?->new === 'paid');
AuditLog::assertRecordedCount(1);
```

`fake()` routes both automatic (Eloquent) and manual (`AuditLog::record`) changes into an in-memory fake; `assertNotRecorded()` and `assertNothingRecorded()` round out the set.

What makes it different
-----------------------

[](#what-makes-it-different)

**Core: provenance.** Actor, origin and a correlation id on every change, with no per-model setup (the chain above). This is the part that sets it apart from most audit packages.

**Optional add-ons**, each off or zero-cost until you use it: a time machine, a tamper-evident hash chain, SIEM streaming, anomaly detection, GDPR tooling and multi-tenancy. They build on the core; they are not the point of it. Details in [Advanced features](#advanced-features).

What's new in v2.2
------------------

[](#whats-new-in-v22)

The provenance core is unchanged; v2.2 deepens completeness, observability and DX.

**Bridge to your APM.** An incoming W3C `traceparent` is captured as a `trace_id` alongside the correlation id and shown on the chain as an **Open distributed trace** link straight into Datadog, Jaeger or Tempo, so you cross from who-changed-what to the distributed trace that drove it. Set the URL template in [Configuration](docs/configuration.md) or the Settings UI.

[![Trace bridge with an Open distributed trace link](screenshots/trace-bridge.png)](screenshots/trace-bridge.png)

**Proven completeness.** The write path is fail-open, so a failed audit insert never breaks your request. Those failures are now recorded and surfaced (a Stats banner and a nav badge), so a gap in the trail can no longer pass unnoticed, answering the auditor's "is the log actually complete?".

[![Capture-health banner on the statistics page](screenshots/capture-health.png)](screenshots/capture-health.png)

**More in this release:**

- **Testing API** - `AuditLog::fake()` with `assertRecorded()` / `assertNotRecorded()` / `assertRecordedCount()`, so you assert what your code audited without touching the database (see [Testing](#testing)).
- **Token &amp; client attribution** - a change made through a Sanctum token or Passport client names it on the actor ("Jane Doe via mobile-app").
- **Per-model trail** - an opt-in `HasAuditTrail` trait for `$order->auditTrail()` and `$order->auditStateAt($date)`.
- **Legal holds** - `AuditLog::placeLegalHold($subject)` (or `php artisan audit-log:legal-hold`) exempts a subject from retention for litigation; held data is never pruned.
- **Postman export** - import the read API into Postman from the docs page or `php artisan audit-log:postman`.

How Yammi differs from traditional audit logs
---------------------------------------------

[](#how-yammi-differs-from-traditional-audit-logs)

Most audit packages answer one question:

- ✅ What changed?

Yammi answers the whole story:

- ✅ What changed?
- ✅ Who initiated it?
- ✅ Who executed it?
- ✅ Did it happen in a request, job, command or scheduler?
- ✅ Which queued jobs were part of the same workflow?
- ✅ What is the complete execution chain behind this change?

ScenarioYammiTypical audit packageUser updates a model✅✅User triggers a queued job that updates a model✅ Origin preserved❌ User context lostScheduled task updates a model✅ Scheduler recorded❌ SystemAdmin impersonates a user✅ Both identities recorded❌ Current user onlyMulti-job workflow investigation✅ Full trace❌ Individual events onlyIncident root-cause analysis✅ Execution chain❌ Final write onlyHow it works
------------

[](#how-it-works)

1. One global listener catches Eloquent's `created` / `updated` / `deleted` / `restored` events, nothing to register per model.
2. A small pipeline builds the diff, redacts secrets, resolves actor, origin and correlation, and snapshots foreign-key labels.
3. It writes one row into the `audit_log` table.

Optionally defer that write to the queue (`AUDIT_LOG_WRITE_ASYNC=true`) or move the table to a dedicated connection.

Requirements
------------

[](#requirements)

- PHP `^8.1`, Laravel `^9.0 || ^10 || ^11 || ^12 || ^13`, any database Laravel supports.
- Capture is built on Eloquent model events. Changes made by `Query Builder ->update()` or raw SQL are not seen automatically; record those explicitly with `AuditLog::record()`.
- Migrations create auto-loaded tables (`audit_log`, the settings table, and integrity tables when enabled), which can live on a dedicated connection. Full list in [Configuration](docs/configuration.md).

Performance
-----------

[](#performance)

A log package lives on your hot path, so the cost is kept deliberate:

- One insert for the record, plus a single batched insert for its changed-field index.
- No extra queries during capture, except opt-in label lookups and one chain-head select when integrity is on.
- Field searches (`field('status')`) seek an indexed table instead of scanning the JSON of every row.
- Capture is **fail-open**: a failed audit insert is logged and never blocks the host operation.
- Reads are bounded: one-year ranges, 10k-row exports, chunked retention.

[![Statistics: growth, top cascades and change hotspots](screenshots/stats.png)](screenshots/stats.png)

Advanced features
-----------------

[](#advanced-features)

Optional subsystems, each off or zero-cost until you use it:

- **[Capture policy &amp; sampling](docs/governance.md)**: ignore fields, capture conditionally, sample high-churn models.
- **[Forensics](docs/forensics.md)**: time machine, tamper-evident hash chain, signed integrity digests.
- **[Operations](docs/operations.md)**: anomaly detection (and rules as code), SIEM streaming, alerts.
- **[Compliance](docs/compliance.md)**: GDPR subject reports, retention and archive, access logging, multi-tenancy.
- **[Analytics &amp; dashboard](docs/analytics-and-dashboard.md)**: statistics, top cascades, hotspots, facades, JSON API, pivot auditing.
- **[Governance](docs/governance.md)**: an `event_version` schema contract, a fluent query DSL and value-transition search.

Security
--------

[](#security)

Defaults aim to be safe; you keep control of the trade-offs:

- **Secret redaction** runs before the diff is stored (`password`, `token`, `api_key`, and more, including nested JSON).
- **Fail-open by design**: if the audit insert fails, the error is logged and your business operation continues. For the opposite guarantee, wrap both in your own transaction.
- **UI off by default**, behind configurable middleware (`web`, `auth`), an optional Gate and a rate limit, serving its own assets (no external CDN).
- **API fails closed**: the read-only JSON API will not register without an auth guard in its middleware.
- **Input is validated and bounded**; CSV export escapes formula characters; retention is on by default because audit data is PII.

Comparison
----------

[](#comparison)

Existing Laravel audit packages, [spatie/laravel-activitylog](https://github.com/spatie/laravel-activitylog) and [owen-it/laravel-auditing](https://github.com/owen-it/laravel-auditing) among them, focus on model changes and user activity. This one focuses on queue-heavy, distributed apps that need execution traceability.

CapabilityYammiSpatieModel change history✅✅Actor tracking✅✅Origin survives queues✅❌Correlation tracing✅❌Execution chain reconstruction✅❌If your current setup covers your needs, keep it. This package earns its place when changes flow through queues and you need to trace them back to a person.

Non-goals
---------

[](#non-goals)

Permanent boundaries. Each would force the audit log to become a *source of truth* or a *real-time system*, and that breaks the invariant that makes it safe to install: capture stays off your write path, fails open, is additive, and never changes your data.

- **No event sourcing or state replay**: the time machine is read-only forensics, not the system you rebuild your app from.
- **No backpressure engine**: sampling governs volume; broker-grade load shaping is Kafka/SaaS territory.
- **No in-package search engine**: search goes outward to your SIEM/Elastic, inward through the indexed changed-keys table.
- **No distributed observability platform**: metrics and traces at that scale belong to Datadog, Splunk or Pulse.
- **No query profiler**: we surface write-side cascades from data already captured; read-path profiling is Telescope/Pulse territory.

Configuration
-------------

[](#configuration)

It works with zero config. The common switches:

```
// config/audit-log.php (publish with vendor:publish --tag=audit-log-config)
'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)],
'integrity' => ['enabled' => env('AUDIT_LOG_INTEGRITY', false)],
'ui'        => ['enabled' => env('AUDIT_LOG_UI_ENABLED', false)],
```

Settings are also editable from the Settings UI without a redeploy (resolution order: DB row, then config value, then package default). Full reference in [Configuration](docs/configuration.md).

Documentation
-------------

[](#documentation)

- [Provenance](docs/provenance.md): actor, origin, correlation, impersonation, trace
- [Governance](docs/governance.md): capture policy, sampling, event\_version, query DSL, value transitions
- [Forensics](docs/forensics.md): time machine, tamper evidence, signed digests
- [Operations](docs/operations.md): anomaly detection, rules as code, SIEM streaming, alerts
- [Compliance](docs/compliance.md): GDPR reports, retention, archive, access logging, multi-tenancy
- [Analytics &amp; dashboard](docs/analytics-and-dashboard.md): statistics, top cascades, hotspots, facades, JSON API, pivot auditing
- [Configuration](docs/configuration.md): full config reference and the Settings UI

The dashboard also ships an in-app documentation page at `/audit-log/settings/docs`.

License
-------

[](#license)

MIT

###  Health Score

48

—

FairBetter than 94% of packages

Maintenance94

Actively maintained with recent releases

Popularity28

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity48

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~3 days

Total

6

Last Release

28d ago

Major Versions

v1.0.0 → v2.0.02026-06-14

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/20987016?v=4)[Roma](/maintainers/RomaLytar)[@RomaLytar](https://github.com/RomaLytar)

---

Top Contributors

[![RomaLytar](https://avatars.githubusercontent.com/u/20987016?v=4)](https://github.com/RomaLytar "RomaLytar (139 commits)")

---

Tags

activity-logauditaudit-logaudit-trailchange-trackingcompliancedashboardeloquentforensicsgdprhistorylaravellaravel-packagemulti-tenancyobservabilityphpprovenancequeue-tracingsecuritytamper-evidentphplaraveleloquentlaravel-packageversioningAudithistorydashboardaudit-trailactivity-loggdprcomplianceaudit-logchange-tracking

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/romalytar-yammi-audit-log-laravel/health.svg)

```
[![Health](https://phpackages.com/badges/romalytar-yammi-audit-log-laravel/health.svg)](https://phpackages.com/packages/romalytar-yammi-audit-log-laravel)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.3M348](/packages/psalm-plugin-laravel)[laravel/cashier

Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.

2.5k30.2M151](/packages/laravel-cashier)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k15.1M137](/packages/laravel-pulse)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9762.4M134](/packages/roots-acorn)[api-platform/laravel

API Platform support for Laravel

58174.6k17](/packages/api-platform-laravel)[moonshine/moonshine

Laravel administration panel

1.3k253.1k86](/packages/moonshine-moonshine)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
