Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Audit Log Laravel Package

iamfarhad/laravel-audit-log

A lightweight, easy-to-use audit logging package for Laravel that automatically records model creations, updates, and deletions. It tracks who changed what and when, supports custom events and tags, and makes querying your audit trail simple and reliable.

View on GitHub
Deep Wiki
Context7

Getting Started

The package wires an Auditable trait into Eloquent models and records create/update/delete events into an audit table. Day-to-day, you'll spend your time in three places: the published config/audit-log.php, the models using the trait, and the audit log model/table you query for history.

Minimal setup:

  1. Install and publish assets:
    composer require iamfarhad/laravel-audit-log
    php artisan vendor:publish --provider="IamFarhad\AuditLog\AuditLogServiceProvider"
    php artisan migrate
    
  2. Publish the config if it isn't auto-published, and skim it before touching any model — especially the global enable flag added in 1.7.1, the tracked events list, and any user-resolver/attribute filtering options.
  3. Attach the trait to a model you actually care about auditing first, e.g. Invoice rather than User, so you can eyeball the rows it produces:
    use IamFarhad\AuditLog\Traits\Auditable;
    
    class Invoice extends Model
    {
        use Auditable;
    }
    
  4. Trigger a create/update/delete via Tinker and inspect the audit table plus the model's audit relation. That confirms the user resolver and event configuration are correct before you roll the trait out broadly.

First realistic use case: an admin-facing "history" tab on a single resource. Fetch the logs for one record, render event, created_at, causer, and a diff of changed attributes. That's the fastest path to value and surfaces config mistakes immediately.

The 1.7.1 addition — a global audit disable switch — is the most relevant new lever for local work. Use it to silence auditing during seeding, imports, and test setup instead of stripping traits or mocking.

Implementation Patterns

Trait per model, config for policy. Keep the trait as the only model-level change. Put decisions about what gets logged (events, excluded attributes, causer resolution) in config so you can adjust behaviour without editing dozens of models.

Global kill switch for bulk/non-interactive paths. New in 1.7.1, wrap seeders, data migrations, CSV imports, and backfill commands:

config(['audit-log.enabled' => false]); // verify exact key against published config

Invoice::withoutAuditing(function () {
    // bulk import
});

Even without a package-provided withoutAuditing helper, toggling the config value before the operation and restoring it in a finally block is the reliable pattern. Do this at the outermost boundary of the command/job, not scattered through the code.

Prefer the global flag over per-model disabling. If auditing is disabled per model, the intent is invisible to the next developer. If it's disabled globally in a seeder or test, the intent is obvious and scoped.

Query logs as a relation. Expose history through the trait's relation rather than raw queries, and eager-load the causer to avoid N+1s on history screens:

$invoices = Invoice::with(['auditLogs.causer'])->paginate();

For a single record's timeline, paginate the relation — audit tables grow fast on hot models.

Causer resolution. Make sure the user resolver returns the authenticated user in HTTP contexts and null in console/queue contexts, and that your UI handles the null causer ("System") rather than crashing on a missing relation.

Filter on write, not on read. Exclude noisy or sensitive columns (session tokens, remember_token, large JSON blobs, updated_at) at the config level. Filtering in views means the sensitive data is already in the database.

Integrate with existing activity surfaces. Rather than building a separate audit UI, feed the audit relation into whatever admin panel or Nova/Filament resource already shows the record. The value of audit logs is proximity to the thing being audited.

Test the audit contract, not the package. Assert that updating an invoice produces an audit row with the expected event and changed keys. Don't assert on the package's internal row shape, which changes between versions.

Gotchas and Tips

The new global flag will silently break existing tests. Once enabled defaults to true but something in your test bootstrap, .env.testing, or a seeder flips it off, assertions on audit rows fail with no error — just empty results. Add one explicit test that asserting auditing is on, so a stray config change fails loudly.

Config caching. After changing the audit config, run php artisan config:clear in local/dev. A stale cached config is the most common reason "the disable flag doesn't work".

Eloquent events are bypassed by bulk operations. Model::where(...)->update(), upsert, and raw SQL produce no audit rows. If a critical path uses query-builder updates, audit it explicitly or convert to model-level saves. The global disable flag doesn't help here — it's the opposite problem.

Queued auditing and disabled config interact. If the package supports queueing writes, disabling globally in a request doesn't retroactively cancel already-dispatched jobs. In tests, use Queue::fake() or sync drivers so you're not asserting on stale config state from a previous test.

Morph maps. If you use Relation::enforceMorphMap(), old audit rows referencing FQCNs break when you introduce aliases. Decide on morph aliases before you generate production audit data — retrofitting is painful.

Log volume and retention. Audit tables are append-only and unbounded. Plan a pruning command or partition strategy on day one for high-traffic models. Note that pruning is itself an unaudited (or recursively audited) operation — be deliberate about it.

Timezones and created_at. Audit timestamps follow the app timezone config. When correlating audit rows with application logs or external systems, normalise to UTC in queries, not in the stored data.

Extension points to prefer. If the causer resolver or attribute formatter is configurable/overridable, customise there. Avoid subclassing the package's audit model unless necessary — it couples you to internal column names and complicates upgrades. For anything exotic (e.g. logging to an external SIEM), hook the package's events or add a listener on the audit model rather than forking.

Version pinning. 1.7.1 is a small additive change, but this is a single-maintainer package with early-stage API churn. Pin to ^1.7 and read the changelog before minor bumps; treat audit data and column names as something you may need to migrate.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator