Laravel Audit Logger is a powerful and flexible package designed to provide detailed audit logging for Laravel applications. It enables tracking of all changes to your Eloquent models with comprehensive source tracking, ensuring compliance with regulatory requirements, aiding in debugging, and maintaining data integrity. Built with modern PHP and Laravel practices, this package adheres to strict typing, PSR-12 coding standards, and leverages dependency injection for maximum testability and maintainability.
The package uses a high-performance direct logging architecture while maintaining flexibility through optional event integration, making it suitable for both small applications and enterprise-scale systems.
- Features
- Requirements
- Installation
- Configuration
- Usage
- Customizing Audit Logging
- Retrieving Audit Logs
- Performance Optimization
- Testing
- Security Best Practices
- Troubleshooting
- Contributing
- License
- Entity-Specific Audit Tables: Automatically creates dedicated tables for each audited model to optimize performance and querying.
- Comprehensive Change Tracking: Logs all CRUD operations (create, update, delete, restore) with old and new values.
- Source Tracking: Automatically tracks the source of changes (console commands, HTTP routes, etc.) for enhanced debugging and compliance.
- Customizable Field Logging: Control which fields to include or exclude from auditing.
- User Tracking: Automatically identifies and logs the user (causer) responsible for changes.
- Direct Logging Architecture: Uses direct service calls for high-performance, synchronous audit logging with optional event integration.
- Batch Processing: Supports batch operations for high-performance logging in large-scale applications.
- Type Safety: Built with PHP 8.1+ strict typing and modern features like
readonly
properties and enums. - Extensible Drivers: Supports multiple storage drivers (currently MySQL) with the ability to implement custom drivers.
- Automatic Migration: Seamlessly creates audit tables for new models when enabled.
- PHP: 8.1 or higher
- Laravel: 10.x, 11.x, or 12.x
- Database: MySQL 8.0+ (for the default driver)
Install the package via Composer:
composer require iamfarhad/laravel-audit-log
After installation, publish the configuration file to customize settings:
php artisan vendor:publish --tag=audit-logger-config
This will create a configuration file at config/audit-logger.php
where you can adjust settings like the storage driver, table naming conventions, and more.
The configuration file config/audit-logger.php
allows you to customize the behavior of the audit logger. Below are the key configuration options:
return [
// Default audit driver
'default' => env('AUDIT_DRIVER', 'mysql'),
// Driver-specific configurations
'drivers' => [
'mysql' => [
'connection' => env('AUDIT_MYSQL_CONNECTION', config('database.default')),
'table_prefix' => env('AUDIT_TABLE_PREFIX', 'audit_'),
'table_suffix' => env('AUDIT_TABLE_SUFFIX', '_logs'),
],
],
// Enable automatic migration for audit tables
'auto_migration' => env('AUDIT_AUTO_MIGRATION', true),
// Global field exclusions
'fields' => [
'exclude' => [
'password',
'remember_token',
'api_token',
],
'include_timestamps' => false,
],
// Batch processing settings for performance
'batch' => [
'enabled' => env('AUDIT_BATCH_ENABLED', false),
'size' => env('AUDIT_BATCH_SIZE', 100),
],
// Causer (user) identification settings
'causer' => [
'guard' => null, // Use default auth guard if null
'resolver' => null, // Custom causer resolver class
],
];
Ensure you review and adjust these settings based on your application's needs, especially for sensitive data exclusion and performance optimization.
The audit logger creates tables with the following structure for each audited model:
CREATE TABLE audit_{model_name}_logs (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
entity_id VARCHAR(255) NOT NULL,
action VARCHAR(255) NOT NULL,
old_values JSON NULL,
new_values JSON NULL,
causer_type VARCHAR(255) NULL,
causer_id VARCHAR(255) NULL,
metadata JSON NULL,
created_at TIMESTAMP NOT NULL,
source VARCHAR(255) NULL,
INDEX idx_entity_id (entity_id),
INDEX idx_causer (causer_type, causer_id),
INDEX idx_created_at (created_at),
INDEX idx_source (source)
);
The source
field is automatically populated to track the origin of changes:
- Console commands: Command name (e.g.,
app:send-emails
) - HTTP requests: Controller action (e.g.,
App\Http\Controllers\UserController@update
) - Background jobs: Job class name
To make a model auditable, simply add the Auditable
trait to your Eloquent model. Ensure strict typing is enabled as per the engineering rules.
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use iamfarhad\LaravelAuditLog\Traits\Auditable;
final class Order extends Model
{
use Auditable;
protected $fillable = ['customer_id', 'total', 'status'];
}
Once the trait is added, any changes to the model (create, update, delete, restore) will be automatically logged to a dedicated audit table (e.g., audit_order_logs
).
To exclude specific fields from being audited, define the $auditExclude
property:
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use iamfarhad\LaravelAuditLog\Traits\Auditable;
final class User extends Model
{
use Auditable;
protected array $auditExclude = [
'password',
'remember_token',
];
}
Alternatively, you can specify only the fields to audit using $auditInclude
:
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use iamfarhad\LaravelAuditLog\Traits\Auditable;
final class Invoice extends Model
{
use Auditable;
protected array $auditInclude = [
'amount',
'status',
'due_date',
];
}
You can enrich audit logs with custom metadata by implementing the getAuditMetadata
method in your model:
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use iamfarhad\LaravelAuditLog\Traits\Auditable;
final class Transaction extends Model
{
use Auditable;
public function getAuditMetadata(): array
{
return [
'ip_address' => request()->ip() ?? 'unknown',
'user_agent' => request()->userAgent() ?? 'unknown',
'request_id' => request()->header('X-Request-Id', 'n/a'),
];
}
}
The audit logger automatically tracks the source of changes to help with debugging and compliance. The source field captures:
- Console Commands: Artisan command names (e.g.,
app:send-emails
,migrate
,queue:work
) - HTTP Routes: Controller action names or route names for web requests
- Background Jobs: Job class names when changes occur within queued jobs
// Example audit log entries with source tracking:
// From console command: php artisan app:send-emails
[
'entity_id' => '1',
'action' => 'updated',
'old_values' => '{"email": "old@example.com"}',
'new_values' => '{"email": "new@example.com"}',
'source' => 'app:send-emails',
'created_at' => '2024-01-15 10:30:00'
]
// From HTTP request
[
'entity_id' => '1',
'action' => 'updated',
'old_values' => '{"status": "pending"}',
'new_values' => '{"status": "approved"}',
'source' => 'App\\Http\\Controllers\\OrderController@approve',
'created_at' => '2024-01-15 10:35:00'
]
You can query audit logs by source using convenient scopes:
use iamfarhad\LaravelAuditLog\Models\EloquentAuditLog;
// Using dedicated source scopes for cleaner queries
// Find all changes made by a specific console command
$commandLogs = EloquentAuditLog::forEntity(User::class)
->fromCommand('app:send-emails')
->get();
// Find all changes made through console commands
$consoleLogs = EloquentAuditLog::forEntity(User::class)
->fromConsole()
->get();
// Find all changes made through HTTP requests
$webLogs = EloquentAuditLog::forEntity(User::class)
->fromHttp()
->get();
// Find changes from a specific controller
$controllerLogs = EloquentAuditLog::forEntity(User::class)
->fromController('UserController')
->get();
// Find changes by exact source match
$exactSourceLogs = EloquentAuditLog::forEntity(User::class)
->forSource('app:send-emails')
->get();
// Combine with other scopes
$filteredLogs = EloquentAuditLog::forEntity(User::class)
->fromConsole()
->forAction('updated')
->dateBetween(now()->subWeek(), now())
->orderBy('created_at', 'desc')
->get();
For specific operations where auditing is not required, you can disable it temporarily:
$user = User::find(1);
$user->disableAuditing();
$user->update(['email' => 'new.email@example.com']); // This change won't be logged
$user->enableAuditing();
To log custom actions beyond standard CRUD operations, use the fluent API provided by the audit()
method for direct, high-performance logging:
<?php
declare(strict_types=1);
use App\Models\Order;
$order = Order::find(1);
$order->audit()
->custom('status_changed')
->from(['status' => 'pending'])
->to(['status' => 'shipped'])
->withMetadata(['ip' => request()->ip(), 'user_agent' => request()->userAgent()])
->log();
This approach provides better performance than event-driven architectures since it logs directly to the database without the overhead of event dispatching and listening.
For a more intuitive and readable way to log custom actions, use the fluent API provided by the audit()
method:
<?php
declare(strict_types=1);
use App\Models\Order;
$order = Order::find(1);
$order->audit()
->custom('status_transition')
->from(['status' => 'pending'])
->to(['status' => 'shipped'])
->withMetadata(['ip' => request()->ip(), 'user_agent' => request()->userAgent()])
->log();
This fluent interface allows you to chain methods to define the custom action, old and new values, and additional metadata before logging the event. It respects the model's auditable attributes and merges any default metadata defined in getAuditMetadata()
with custom metadata provided.
If you need to extend the audit logging functionality, you can implement a custom driver by adhering to the AuditDriverInterface
. Register your custom driver in a service provider:
<?php
declare(strict_types=1);
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use iamfarhad\LaravelAuditLog\Contracts\AuditDriverInterface;
use App\Audit\CustomAuditDriver;
final class AuditServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->bind(AuditDriverInterface::class, CustomAuditDriver::class);
}
}
Audit logs are accessible via a relationship on the audited model. Use the auditLogs()
method to query logs:
$user = User::find(1);
// Retrieve all audit logs
$allLogs = $user->auditLogs()->get();
// Filter by specific action
$updateLogs = $user->auditLogs()->where('action', 'updated')->get();
// Get the most recent logs
$recentLogs = $user->auditLogs()->orderBy('created_at', 'desc')->take(5)->get();
For advanced querying, you can directly use the EloquentAuditLog
model with various scopes to filter audit logs based on specific criteria:
<?php
declare(strict_types=1);
use iamfarhad\LaravelAuditLog\Models\EloquentAuditLog;
// Get logs for a specific entity type and ID
$logs = EloquentAuditLog::forEntity(User::class)
->where('entity_id', 1)
->where('action', 'updated')
->where('created_at', '>=', now()->subDays(7))
->orderBy('created_at', 'desc')
->get();
// Use scope to filter by action type
$createdLogs = EloquentAuditLog::forEntity(User::class)
->forAction('created')
->where('entity_id', 1)
->get();
// Use scope to filter by date range
$lastMonthLogs = EloquentAuditLog::forEntity(User::class)
->dateBetween(now()->subDays(30), now())
->where('entity_id', 1)
->orderBy('created_at', 'desc')
->get();
// Use scope to filter by causer (user who performed the action)
$adminLogs = EloquentAuditLog::forEntity(User::class)
->forCauserId(1) // Assuming causer_id 1 is the admin
->where('entity_id', 1)
->get();
// Filter by source using dedicated scopes
$consoleLogs = EloquentAuditLog::forEntity(User::class)
->fromCommand('app:send-emails')
->where('entity_id', 1)
->get();
$webLogs = EloquentAuditLog::forEntity(User::class)
->fromHttp()
->where('entity_id', 1)
->get();
$controllerLogs = EloquentAuditLog::forEntity(User::class)
->fromController('UserController')
->where('entity_id', 1)
->get();
// Combine multiple scopes for precise filtering
$filteredLogs = EloquentAuditLog::forEntity(User::class)
->forAction('updated')
->forCauserId(1)
->fromCommand('app:send-emails')
->dateBetween(now()->subDays(7), now())
->where('entity_id', 1)
->orderBy('created_at', 'desc')
->take(10)
->get();
These scopes allow for flexible and efficient querying of audit logs, making it easier to analyze changes based on action type, date range, or the user responsible for the change.
The EloquentAuditLog
model provides several convenient scopes for filtering audit logs:
forEntity(string $entityClass)
- Filter by entity type (e.g.,User::class
)forEntityId($entityId)
- Filter by entity IDforAction(string $action)
- Filter by action (created
,updated
,deleted
, etc.)forCauser(string $causerClass)
- Filter by causer typeforCauserId($causerId)
- Filter by causer ID
forCreatedAt($createdAt)
- Filter by exact creation datedateGreaterThan($date)
- Filter for logs after a specific datedateLessThan($date)
- Filter for logs before a specific datedateBetween($startDate, $endDate)
- Filter for logs within a date range
forSource(string $source)
- Filter by exact source matchfromConsole()
- Filter for logs from console commandsfromHttp()
- Filter for logs from HTTP requestsfromCommand(string $command)
- Filter by specific console commandfromController(string $controller = null)
- Filter by controller (or all controllers if no parameter)
// Examples of scope combinations
use iamfarhad\LaravelAuditLog\Models\EloquentAuditLog;
// Complex filtering example
$logs = EloquentAuditLog::forEntity(User::class)
->forAction('updated')
->fromConsole()
->dateBetween(now()->subWeek(), now())
->forCauserId(1)
->orderBy('created_at', 'desc')
->paginate(20);
// Find all user updates from a specific command in the last 24 hours
$recentCommandLogs = EloquentAuditLog::forEntity(User::class)
->forAction('updated')
->fromCommand('app:sync-users')
->dateGreaterThan(now()->subDay())
->get();
- Batch Processing: Enable batch processing in the configuration to reduce database transactions for high-frequency operations.
'batch' => [ 'enabled' => true, 'size' => 100, ],
- Selective Auditing: Limit audited fields to only those necessary for compliance or debugging to minimize storage and processing overhead.
- Database Indexing: Ensure audit tables have appropriate indexes for frequent queries (handled automatically by the package).
This package includes a robust test suite. To run the tests locally:
composer test
When writing tests for your application, ensure you cover audit logging behavior, especially for critical models. Mock the audit driver if necessary to isolate tests from actual logging.
- Exclude Sensitive Data: Always exclude fields containing personally identifiable information (PII) or sensitive data using
$auditExclude
. - Access Control: Implement authorization checks (using Laravel Gates or Policies) to restrict access to audit logs.
- Data Retention: Consider implementing a retention policy to purge old audit logs, balancing compliance needs with data privacy.
- Audit Tables Not Created: Ensure
'auto_migration' => true
in your configuration. If disabled, manually create tables usingAuditLogger::driver()->createStorageForEntity(Model::class)
. - Missing Logs: Verify that fields aren't excluded globally or in the model, and ensure auditing isn't disabled for the operation.
- Causer Not Recorded: Confirm that authentication is set up correctly and the user is logged in during the operation.
- Source Field Empty: The source field should automatically populate. If it's null, check that:
- For console commands:
$_SERVER['argv']
is available and contains the command name - For HTTP requests: The route is properly registered and accessible via
Request::route()
- The application is running in the expected context (console vs. HTTP)
- For console commands:
- Migration Issues: If upgrading from a previous version, ensure your existing audit tables include the
source
column. You may need to add it manually:ALTER TABLE audit_your_model_logs ADD COLUMN source VARCHAR(255) NULL; CREATE INDEX idx_source ON audit_your_model_logs (source);
Contributions are welcome! Please follow the guidelines in CONTRIBUTING.md for submitting pull requests, reporting issues, or suggesting features.
This package is open-sourced software licensed under the MIT license.