deltawhydev / laravel-audit-logging
Unified audit logging system for Laravel with Nova support
Package info
github.com/DeltaWhyDev/laravel-audit-logging
pkg:composer/deltawhydev/laravel-audit-logging
Requires
- php: ^8.2
- illuminate/console: ^10.0|^11.0
- illuminate/database: ^10.0|^11.0
- illuminate/support: ^10.0|^11.0
Requires (Dev)
- laravel/nova: ^4.0
Suggests
- laravel/nova: Required to use the Nova Audit Log resource and visualizer.
README
A robust, configurable audit logging system for Laravel applications with seamless Nova integration.
Features
- Automatic Logging: easily track changes via the
Auditabletrait. - Transaction-aware: logs are only committed when the database transaction succeeds.
- Nova Integration: includes a ready-to-use Nova Resource and a
ChangelogFieldfor resource detail views. - Highly Configurable: customize database connections, actors, subject models, and more.
- Formatting: built-in support for Enums, Dates, Booleans, and Relation links.
- Security: strict read-only access in Nova, sensitive field masking.
Installation
- Require the package via Composer:
composer require deltawhydev/laravel-audit-logging
- Publish the configuration and migrations:
php artisan vendor:publish --tag=audit-log-config php artisan vendor:publish --tag=audit-log-migrations
- Run migrations:
php artisan migrate
Configuration
The config/audit-log.php file allows you to customize almost every aspect of the package.
Database Connection
You can store audit logs in a separate database (SQL or NoSQL) by setting the connection name:
// config/audit-log.php 'connection' => env('AUDIT_LOG_CONNECTION', 'sqlite_logs'),
Namespace Customization
Decouple the package from your specific app structure:
'model_namespace' => 'App\\Models', 'nova' => [ 'namespace' => 'App\\Nova', // ... ],
Actor Resolution
Define how the "Actor" (the user performing the action) is resolved. The package automatically retrieves the user's ID and fetches them based on this configured model, without needing to save a separate actor_type column:
'actor' => [ 'user_model' => \App\Models\User::class, ],
Entity Type Resolution
By default the package stores the entity_type column using the model's getMorphClass() value, which returns the fully qualified class name (e.g., App\Models\User) or its morph-map alias if one is defined globally.
There are two ways to control what value is stored:
Option A: Package-level entity_type_map (Recommended)
Use this when you want short aliases only for audit logs without affecting the rest of your application. This is especially useful when you cannot (or don't want to) register a global Relation::morphMap().
// config/audit-log.php 'entity_type_map' => [ \App\Models\User::class => 'user', \App\Models\Pallet\Pallet::class => 'pallet', \App\Models\Packaging\Packaging::class => 'packaging', ],
The package resolves types in this order:
- Write (model → alias): checks
entity_type_mapfirst, then falls back togetMorphClass() - Read (alias → model): checks
entity_type_map, then Laravel's global morph map, then treats the value as a FQCN
This means both old FQCN records and new alias records resolve correctly — no data migration needed when you add the map.
Option B: Laravel's global Relation::morphMap()
If your application already uses a global morph map, the package respects it automatically. No additional configuration is needed — the entity_type_map can stay empty.
// AppServiceProvider::boot() use Illuminate\Database\Eloquent\Relations\Relation; Relation::morphMap([ 'user' => \App\Models\User::class, 'product' => \App\Models\Product::class, ]);
Note:
Relation::morphMap()is global and affects all polymorphic columns in your application, not just audit logs. If you only want aliases in the audit log table, use Option A instead.
Usage
1. Add Trait to Models
Simply add the Auditable trait to any Eloquent model you want to track:
use DeltaWhyDev\AuditLog\Traits\Auditable; use Illuminate\Database\Eloquent\Model; class Product extends Model { use Auditable; }
This will automatically log created, updated, deleted, and restored events, including changed attributes and relations (pivot events). Wait! If you are using Soft Deletes, the deleted event naturally triggers instead of forceDeleted. To capture restored events, make sure your model uses the Illuminate\Database\Eloquent\SoftDeletes trait.
2. Nova Integration
Audit Log Resource
Ensure nova.resource_enabled is set to true in config. The Audit Logs resource will appear in your Nova sidebar.
The built-in resource includes a highly-optimized Index View that shows a clean summary of the actor, action, and entity with clickable links, and a dedicated Modified Fields column that strictly lists what fields were modified without crowding the table.
Built-In Filters: You can easily filter your audit logs directly in Nova using the dynamic filters included in the package:
AuditLogActorFilter: Dynamically populates with users who have actually performed actions, directly retrieving their configured display names.AuditLogEntityFilter: Dynamically fetches all the different Eloquent models that have been historically audited in the system.
(These filters are already applied to the default AuditLog Nova resource, but you can also use them in your own custom lenses).
Changelog Field
To show a history of changes directly on a resource's detail page, add the ChangelogField:
use DeltaWhyDev\AuditLog\NovaComponents\ChangelogField\ChangelogField; public function fields(Request $request) { return [ // ... other fields ChangelogField::make('History') ->onlyOnDetail(), ]; }
The ChangelogField automatically handles pagination and provides a beautiful, collapsible row for every logged action. The headers cleanly show a 30-character truncated summary of what fields were changed (e.g., Modified: status, password_hash).
Advanced Usage
Custom Transformers
You can define custom transformers to format specific field values difference:
// config/audit-log.php 'transformers' => [ \App\Models\User::class => [ 'permissions' => \DeltaWhyDev\AuditLog\Transformers\JsonDiffTransformer::class, ], ],
Pro Tip: Custom HTML Layouts (
is_raw_html) If you want a transformer to take full control over its rendering (e.g. returning a raw HTML table instead of standard red/green text bubbles) without using Blade views, you can add'is_raw_html' => trueto the array returned by yourtransformDiff()method. TheChangelogFieldfrontend will output your HTML unescaped spanning the full width of the row.
Custom View Components (Blade Templates)
If you have complex HTML layouts that you do not want to hardcode inside your Transformer's PHP strings, you can delegate the rendering to standard Laravel Blade views.
Use the custom_views block in the configuration file to map a key to a specific Blade template:
// config/audit-log.php 'custom_views' => [ 'my_custom_table' => 'audit-log.custom.my-table', ],
Inside your Transformer's transformDiff() function, skip returning old or new strings or raw html entirely. Instead, return the exact layout configuration:
public function transformDiff($old, $new, array $context = []): array|string|null { return [ [ 'label' => 'Entities', 'custom_view' => config('audit-log.custom_views.my_custom_table', 'audit-log.custom.my-table'), 'view_data' => [ 'diff' => $new, // Passing variables to the Blade view ], ], ]; }
The Changelog component will automatically parse and inject this Blade view directly into the Nova UI.
Hidden Fields
To completely hide specific attributes or relationships from the Changelog UI display, add them to the hidden_fields configuration array. This is especially useful for hiding technical fields like polymorphic _type identifiers.
// config/audit-log.php 'hidden_fields' => [ \App\Models\Event\AlertReport::class => [ 'action_type', // Hides this attribute from the UI display entirely ], ],
Search Configuration
By default, searching the Audit Log index is disabled to prevent performance issues on large datasets. Enable specific columns via config:
// config/audit-log.php 'searchable_columns' => ['entity_type', 'action'],
Queueing (Async Logging)
To prevent audit logging from slowing down your application (especially with remote databases), enable queueing:
// config/audit-log.php 'queue' => [ 'enabled' => true, 'connection' => 'redis', 'queue' => 'audit-logs', ],
Pruning (Auto-Cleanup)
The package uses Laravel's Prunable trait to automatically delete old logs.
- Configure retention days:
// config/audit-log.php 'pruning' => [ 'retention_days' => 365, ],
- Schedule the pruning command in
app/Console/Kernel.php:
$schedule->command('model:prune', [ '--model' => [\DeltaWhyDev\AuditLog\Models\AuditLog::class], ])->daily();
BelongsToMany Relation Tracking (track_relations)
The package can automatically log additions and removals of BelongsToMany relations.
How it works
There are two mechanisms for detecting pivot changes:
-
pivotDetachedevent — fired on$model->relation()->detach(...). Logged automatically. No configuration needed. -
Pivot model
created/deletedevents — fired when using->using(PivotModel::class). The observer resolves both sides of the pivot and usestrack_relationsto decide which parent is the owner of the log entry.
Configuration
Add $auditConfig with a track_relations array to the owning model:
use DeltaWhyDev\AuditLog\Traits\Auditable; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\Pivot; // The custom Pivot model (optional, needed for event-based tracking) class OrderTag extends Pivot { protected $table = 'order_tag'; } // The owning model class Order extends Model { use Auditable; protected $auditConfig = [ // List the relation method names whose changes should appear in this model's log 'track_relations' => ['tags'], ]; public function tags() { return $this->belongsToMany(Tag::class, 'order_tag') ->using(OrderTag::class) ->withTimestamps(); } } // The related model — no $auditConfig needed unless it also owns logged relations class Tag extends Model { // NOT declaring track_relations here prevents inverse duplicate logs }
Rule: Values in
track_relationsmust exactly match the method names of thebelongsToManyrelations on the model (e.g.tags,items,categories).
Why track_relations is required for ->using() relations
Without it, both sides of the pivot are treated as "owners" and the observer logs on both models, producing a double entry:
Without track_relations |
With track_relations |
|---|---|
❌ Order → "tags removed: Tag #5" |
✅ Order → "tags removed: Tag #5" |
❌ Tag → "orders removed: Order #12" |
(skipped — not in track_relations) |
Tracking multiple relations
protected $auditConfig = [ 'track_relations' => ['tags', 'categories', 'assignees'], ];
Inheritance
A base model's $auditConfig is inherited by child models via standard PHP property inheritance. A child model can override it by re-declaring $auditConfig, which takes full precedence.
License
MIT