Search by

laraarabdev / recordkeeper

salemB88

Headless audit trail and rollback for any Laravel app — PHP 8 attributes, route auditing, privacy protection, and Artisan tools. No Filament required.

Package info

github.com/LaraArabDev/recordkeeper

pkg:composer/laraarabdev/recordkeeper

Statistics

Installs: 13

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 5

v0.1.1 2026-07-11 11:17 UTC

This package is auto-updated.

Last update: 2026-08-21 10:15:15 UTC


README

Recordkeeper Banner

Recordkeeper

Headless audit trail, rollback & data protection for Laravel

Latest Version Total Downloads License PHP Laravel

Tests codecov Static Analysis Security Audit Mutation Testing Code Style

Track every model change, route hit, queued job, Artisan command, and application event — with one-click rollback, privacy controls, and zero config arrays.
Built on owen-it/laravel-auditing · PHP 8.2 – 8.4 · Laravel 11 / 12

LaraArabDev — We build, develop, empower, and contribute. An Arab open-source community crafting production-grade Laravel packages.
LaraArabDev — نبني، نطوّر، نُمكّن، ونُساهم. مجتمع عربي مفتوح المصدر يصنع حزم Laravel احترافية وجاهزة للإنتاج.

Quick Start · Features · Customization · FAQ · العربية

What is Recordkeeper?

Every production application needs an answer to "what happened?" — whether it's a customer disputing a charge, an admin accidentally deleting records, or an auditor asking who changed what and when.

Recordkeeper gives your Laravel app a complete audit trail with zero configuration. Install the package, add one trait to your models, and you're done. Every create, update, delete, and restore is automatically logged with before/after values, who made the change, and when it happened.

But model changes are just the beginning. Recordkeeper can also track:

  • Every HTTP request hitting your routes (who accessed what endpoint, response times, status codes)
  • Every outbound API call your app makes (Stripe charges, webhook deliveries, third-party integrations)
  • Every queued job from dispatch through completion or failure
  • Every Artisan command with duration, memory usage, and anomaly detection
  • Every application event with full payload capture

You decide what to monitor. Want to audit only model changes? Just add the trait. Need full API visibility? Turn on route auditing. Want to track outbound HTTP calls, jobs, commands, and events too? Enable them one by one. Or turn everything on at once. Every feature is independent — enable exactly the combination that fits your application, and disabled features have zero overhead.

It also comes with built-in privacy protection (auto-redact passwords, encrypt sensitive fields), one-click rollback (undo any change with a dry-run preview), and 8 Artisan commands for searching, tailing, and managing your audit trail from the terminal.

Built on owen-it/laravel-auditing — the most popular audit package in the Laravel ecosystem. Recordkeeper installs it automatically. If you already use laravel-auditing, Recordkeeper is a drop-in enhancement — your existing auditable models keep working.

🚀 Quick Start

Three commands. That's it.

composer require laraarabdev/recordkeeper
php artisan recordkeeper:install
php artisan migrate

Now add the trait to any model you want to audit:

use LaraArabDev\Recordkeeper\Concerns\AuditsChanges;

class Order extends Model implements \OwenIt\Auditing\Contracts\Auditable
{
    use AuditsChanges;
}

Every create, update, delete, and restore on that model is now tracked automatically. No configuration files to edit, no arrays to maintain, no boilerplate.

Requirement Version
PHP 8.2, 8.3, or 8.4
Laravel 11 or 12
owen-it/laravel-auditing ^13.0 or ^14.0 (auto-installed)

🧭 How It Works

Recordkeeper is designed around a simple philosophy: works out of the box, customize when you need to.

The moment you add the AuditsChanges trait, Recordkeeper automatically tracks all CRUD events, discovers fields from $fillable and $casts, excludes passwords, auto-redacts sensitive patterns with ***, stores audits with no expiry, and enables rollback. You don't need to touch a config file.

When you're ready to customize, you have three ways to do it — pick whichever fits your workflow:

config/recordkeeper.php   →  Global defaults (all models, all features)
       ↓
#[Auditable(...)]         →  Per-model overrides via PHP 8 Attributes
       ↓
Traits + Methods          →  Fine-grained control via overridable methods

Priority: Attribute > Trait > Config. When multiple are present, the most specific one wins.

For example, your config might set a 365-day retention globally, but a specific model can override that to 90 days with #[Auditable(retentionDays: 90)]. Or you can enable job tracking globally in config but exclude specific jobs. Mix and match as needed.

📦 Features

Model Auditing

Automatically tracks every create, update, delete, and restore on your Eloquent models. Each audit records the old values, new values, who made the change, their IP address, and when it happened.

Minimal setup — just add the trait:

class Order extends Model implements \OwenIt\Auditing\Contracts\Auditable
{
    use AuditsChanges;
}

Customize with attributes when you need more control:

use LaraArabDev\Recordkeeper\Attributes\Auditable;
use LaraArabDev\Recordkeeper\Attributes\AuditExclude;
use LaraArabDev\Recordkeeper\Attributes\Redact;
use LaraArabDev\Recordkeeper\Attributes\Encrypt;

#[Auditable(
    events: ['created', 'updated', 'deleted'],  // skip 'restored' if you don't need it
    tags: ['payments'],                          // tag all audits for easy filtering
    retentionDays: 180,                          // auto-prune after 6 months
    threshold: 500,                              // max audits per model instance
)]
#[AuditExclude('internal_notes')]   // never store this field
#[Redact('cvv')]                    // replace with *** before storage
#[Encrypt('national_id')]           // AES-encrypt, auto-decrypt on rollback
class Payment extends Model implements \OwenIt\Auditing\Contracts\Auditable
{
    use AuditsChanges;
}
Available model attributes
Attribute Description
#[Auditable] Configure auditing: events, only, exclude, redact, encrypt, retentionDays, threshold, tags
#[AuditExclude('field')] Never store this field in audits (repeatable)
#[Redact('field')] Replace value with *** before storage (repeatable)
#[Encrypt('field')] AES-encrypt value, auto-decrypt on rollback (repeatable)

Route & API Auditing

Track every HTTP request coming into your application — who hit which endpoint, when, and what happened. Audits capture the route name, HTTP method, status code, response time, authenticated user, IP address, and user agent.

Per-route — add middleware to specific routes:

Two middleware aliases are registered automatically: audit (web guard) and audit.api (API guard with multi-guard resolution for Sanctum, Passport, etc.).

// Web routes
Route::middleware('audit')->group(function () {
    Route::post('/pay', PayController::class);
});

// API routes
Route::middleware(['auth:sanctum', 'audit.api'])->group(function () {
    Route::apiResource('orders', OrderApiController::class);
});

Global — audit every route with one env var:

RECORDKEEPER_ROUTES=true

This pushes audit middleware into the web and api middleware groups automatically. Routes with explicit audit or audit.api middleware are never double-audited. Common tool paths (Horizon, Telescope, Debugbar, etc.) are excluded by default.

Fine-grained control on controller methods:

use LaraArabDev\Recordkeeper\Attributes\Audit;

#[Audit(tag: 'checkout', body: true, response: true, sample: 0.5)]
public function store(Request $request) { ... }

Incoming webhooks (Stripe, GitHub, Twilio) are just regular HTTP requests — they're audited the same way, no special setup required.

Route configuration options
// config/recordkeeper.php
'routes' => [
    'enabled' => env('RECORDKEEPER_ROUTES', false),
    'web'     => true,        // audit web routes
    'api'     => true,        // audit api routes
    'exclude' => [            // skip these paths
        'horizon/*', 'telescope/*', '_debugbar/*',
        '_ignition/*', 'sanctum/*', 'livewire/*', 'health',
    ],
    'body'   => false,        // capture request body
    'sample' => 1.0,          // 1.0 = 100%, 0.1 = 10%
    'tag'    => null,          // optional tag for all global audits
],

Outbound HTTP Tracking

Track every HTTP request your application makes to external services — API calls to Stripe, payment gateways, third-party integrations, outgoing webhooks. Each request records the URL, host, HTTP method, status code, round-trip time, and whether the connection failed.

Setup — one env var:

RECORDKEEPER_HTTP=true

That's it. Recordkeeper hooks into Laravel's built-in HTTP client events (RequestSending, ResponseReceived, ConnectionFailed) — no middleware or code changes needed. Outbound requests are stored in a separate audit_http_requests table, linked to a parent audit record.

Note: Only requests made through Laravel's Http:: facade are tracked. Raw cURL or direct Guzzle calls bypass Laravel's events and are not captured.

Per-job control with attributes:

use LaraArabDev\Recordkeeper\Attributes\TrackHttp;

#[TrackHttp(includeHosts: ['api.stripe.com', 'api.paypal.com'])]
class ProcessPaymentJob implements ShouldQueue { ... }
HTTP tracking configuration
'http' => [
    'enabled' => env('RECORDKEEPER_HTTP', false),
    'mode' => 'auto',              // 'auto' = all, 'manual' = opt-in only
    'queue' => false,              // async persistence
    'capture_headers' => false,    // store request/response headers
    'capture_body' => false,       // store response body
    'body_limit' => 1000,          // truncate body at N characters
    'exclude_hosts' => [],         // skip these hosts
],
Mode Behavior
auto (default) All outbound HTTP calls are tracked automatically
manual Only track calls from jobs/contexts that explicitly opt in

Job Tracking

Follow queued jobs through their entire lifecycle: dispatched, processing, completed, or failed. Each transition is recorded as a separate audit entry, giving you a complete timeline of every job.

Opt in per job — pick your style:

// With a trait (quick, overridable methods)
use LaraArabDev\Recordkeeper\Concerns\AuditsJob;

class ProcessPayment implements ShouldQueue
{
    use AuditsJob;
}

// With an attribute (declarative, all config in one place)
use LaraArabDev\Recordkeeper\Attributes\AuditJob;

#[AuditJob(tags: ['payments'])]
class ProcessPayment implements ShouldQueue { ... }

Or enable globally for all jobs:

RECORDKEEPER_JOBS=true
AuditsJob trait — overridable methods
Method Default Description
auditJobTags() [] Tags for this job's audits
shouldAuditQueued() true Track when job is dispatched
shouldAuditProcessing() true Track when job starts processing
shouldAuditProcessed() true Track when job completes
shouldAuditFailed() true Track when job fails

Command Tracking

Track Artisan command execution with duration, memory usage, exit code, and optional anomaly detection. Anomaly detection flags runs where duration or audit count significantly exceeds the historical average — useful for catching runaway commands.

use LaraArabDev\Recordkeeper\Attributes\AuditCommand;

#[AuditCommand(tags: ['maintenance'])]
class PruneInactiveUsers extends Command { ... }

Or enable globally: RECORDKEEPER_COMMANDS=true. Long-running system commands (schedule:run, queue:work, horizon:work, etc.) are excluded by default.

Command metrics configuration
'commands' => [
    'enabled' => env('RECORDKEEPER_COMMANDS', false),
    'exclude' => ['schedule:run', 'queue:work', ...],
    'metrics' => [
        'memory' => true,              // track peak memory
        'audit_count' => true,         // count audits during command
        'anomaly' => false,            // enable anomaly detection
        'anomaly_multiplier' => 2.0,   // flag if > 2x average
        'anomaly_min_runs' => 5,       // minimum runs before detection
    ],
],

Event Tracking

Capture application events with optional payload. Useful for tracking domain events like OrderShipped, UserRegistered, or PaymentFailed alongside your model and route audits.

use LaraArabDev\Recordkeeper\Attributes\AuditEvent;

#[AuditEvent(capturePayload: true, tags: ['shipping'])]
class OrderShipped { ... }

You can also register events to track in your config's listen array, or enable globally with RECORDKEEPER_EVENTS=true. Framework events (Illuminate namespace) are always skipped automatically.

Privacy & Data Protection

Sensitive data is transformed before it reaches the audit store — never stored in plaintext. Recordkeeper provides three layers of protection that work together:

Layer How Recoverable? Example
Global exclusion Fields listed in privacy.global_exclude are never stored at all N/A password, remember_token
Auto-redaction Fields matching privacy.sensitive_patterns are replaced with *** No Any field containing secret, token, cvv, ssn, iban
Explicit redaction #[Redact('field')] attribute on your model No #[Redact('date_of_birth')]
Encryption #[Encrypt('field')] attribute — AES-encrypted in storage Yes — auto-decrypted on rollback #[Encrypt('national_id')]
#[Redact('ssn', 'date_of_birth')]
#[Encrypt('api_secret')]
class Patient extends Model implements \OwenIt\Auditing\Contracts\Auditable
{
    use AuditsChanges;
}

Auto-redaction works out of the box with no configuration. The default patterns cover the most common sensitive fields. You can customize the pattern list and the redaction mask (*** by default) in your config.

Rollback

Undo any audited model change — single or batch — with an optional dry-run preview so you can see exactly what will change before committing.

use LaraArabDev\Recordkeeper\Facades\Recordkeeper;

// Preview what will change (nothing is modified)
$preview = Recordkeeper::rollback($auditId, dryRun: true);

// Apply the rollback
Recordkeeper::rollback($auditId);

// Roll back an entire batch — atomic, in reverse order
Recordkeeper::rollbackBatch('nightly-import');
Original Event Rollback Action
created Model is force-deleted
updated Old values are restored
deleted Model is restored (supports SoftDeletes)

Encrypted values are auto-decrypted before restoration. Auditing is automatically disabled during rollback to prevent recursive audit entries.

Via Artisan:

php artisan recordkeeper:rollback 1842 --dry-run    # preview
php artisan recordkeeper:rollback 1842 --yes         # apply
php artisan recordkeeper:rollback --batch=nightly     # batch rollback

Batch Auditing

Group related changes under a single batch ID for atomic rollback. When you roll back a batch, every audit in the group is reverted in reverse order within a database transaction.

Recordkeeper::batch('nightly-import-2025-01', function () {
    $order1 = Order::create([...]);
    $order2 = Order::create([...]);
    $inventory->decrement('stock', 50);
    // All 3 audits share the same batch_id
});

// Later, undo the entire import in one call:
Recordkeeper::rollbackBatch('nightly-import-2025-01');

Manual Logging

Record custom events outside the Eloquent or middleware flow. Useful for tracking business events, external interactions, or anything that doesn't fit neatly into model/route auditing.

Recordkeeper::log('payment.gateway.timeout', context: [
    'gateway' => 'stripe',
    'attempt' => 3,
]);

Recordkeeper::log('export.triggered', subject: $order, context: [
    'format' => 'csv',
    'rows'   => 1500,
]);

Querying Audits

Recordkeeper provides two ways to query your audit trail: Eloquent scopes for simple lookups and a fluent query builder for complex searches.

Eloquent scopes:

use LaraArabDev\Recordkeeper\Models\Audit;

Audit::forModel('Order')->latest()->get();
Audit::forSubject($order)->get();
Audit::forActor($admin)->get();
Audit::forGuard('api')->get();
Audit::forBatch('nightly-import')->get();
Audit::rollbackable()->get();
Audit::routeHits()->whereDate('created_at', today())->get();
Audit::jobAudits()->latest()->get();

Fluent query builder:

use LaraArabDev\Recordkeeper\Support\AuditQuery;

$audits = app(AuditQuery::class)
    ->model('Order')
    ->event(['created', 'updated'])
    ->actor(42, 'Admin')
    ->guard('api')
    ->tag('finance')
    ->since('-7 days')
    ->rollbackable()
    ->latest()
    ->limit(50)
    ->builder()
    ->get();
All query builder methods
Method Description
->model(string) Filter by model (short name or FQCN)
->subjectId(int|string) Filter by auditable ID
->event(string|array) Filter by event name(s)
->actor(id, type?) Filter by user_id + optional user_type
->actorType(string) Filter by actor type only
->onlyAuthenticated() Exclude system/anonymous audits
->guard(string) Filter by auth guard
->tag(string|array) Filter by tag(s)
->batch(string) Filter by batch_id
->between(from, until) Date range filter
->since(from) Created after a date
->search(string) Free-text search
->rollbackable() Only model-change events
->jobs() Only job lifecycle events
->commands() Only command events
->events() Only application events
->latest() Order by newest first
->limit(int) Limit results
->offset(int) Offset for pagination
->builder() Get the underlying Eloquent Builder

Artisan Commands

Eight commands for managing your audit trail from the terminal:

Command Description
recordkeeper:install Publish config and migrations
recordkeeper:search Search audits with rich filters (model, event, actor, tag, date range)
recordkeeper:show {id} Display a single audit with color-coded before/after diff
recordkeeper:tail Live-follow new audits in real time (like tail -f)
recordkeeper:stats Dashboard with counts by event, top models, top actors
recordkeeper:models List all auditable models with their configuration
recordkeeper:prune Delete old records based on retention policy
recordkeeper:rollback Revert a single audit or an entire batch
php artisan recordkeeper:search --model=Order --event=updated --since="-7 days"
php artisan recordkeeper:show 1842
php artisan recordkeeper:tail --model=Order --guard=api
php artisan recordkeeper:stats --since="-30 days"
php artisan recordkeeper:prune --days=365 --dry-run
php artisan recordkeeper:rollback 1842 --dry-run
php artisan recordkeeper:rollback --batch=nightly-import --yes
php artisan recordkeeper:models --json

Storage Drivers

Choose the storage backend that fits your needs. The default database driver works for most applications. Switch drivers with a single env var.

Driver Best For Rollback Queries
database Full-featured auditing (default) Yes Yes
redis High-throughput, temporary tracking No Limited
log Observability and debugging No No
null Tests and disabled environments No No
RECORDKEEPER_DRIVER=database

Custom drivers: Implement the AuditDriver contract and register via AuditDriverManager::extend().

🔧 Customization

Recordkeeper is designed so you never have to customize — but when you want to, every aspect is configurable.

Custom Actor Resolver

By default, Recordkeeper uses auth()->user(). Override this to resolve actors from multiple guards or custom sources:

Recordkeeper::resolveActorUsing(function () {
    return auth()->guard('admin')->user()
        ?? auth()->guard('api')->user()
        ?? auth()->user();
});

Context Enrichment

Attach extra metadata to audits — useful for tracking deployments, server identity, or business reasons for changes:

// Global context (applies to all audits)
Recordkeeper::pushContext([
    'deployment' => config('app.version'),
    'server'     => gethostname(),
]);

// Per-model context (applies to the next audit on this instance)
$order->auditContext(['reason' => 'Customer requested change']);
$order->update(['status' => 'refunded']);

Tags

Tag audits for easy filtering and grouping:

// Via attribute (static, per-model)
#[Auditable(tags: ['billing', 'critical'])]
class Invoice extends Model { ... }

// At runtime (dynamic, scoped to current request)
Recordkeeper::withTags(['nightly-sync']);

React to Audit Writes

Listen for the ChangeRecorded event to trigger actions after any audit is written:

use LaraArabDev\Recordkeeper\Events\ChangeRecorded;

class NotifyOnCriticalChange
{
    public function handle(ChangeRecorded $event): void
    {
        if (str_contains($event->audit->tags, 'critical')) {
            // Send Slack alert, create ticket, etc.
        }
    }
}

Async Queue Writes

For high-traffic applications, offload audit writes to a background queue:

RECORDKEEPER_QUEUE=true
RECORDKEEPER_QUEUE_NAME=audits

Run a dedicated worker for audit writes so they don't compete with your application jobs:

php artisan queue:work --queue=audits

📋 Configuration Reference

Full configuration table
Key Default Description
General
enabled true Global on/off switch
events ['created','updated','deleted','restored'] Default model events to track
strict false Throw exceptions on audit write failure (enable in tests)
driver 'database' Storage backend: database, redis, log, null
Privacy
privacy.mode 'redact' redact / encrypt / off
privacy.mask '***' Redaction replacement string
privacy.sensitive_patterns ['password','secret','token','api_key','authorization','card','cvv','ssn','iban'] Auto-redacted field name patterns
privacy.global_exclude ['password','remember_token'] Fields never stored in any audit
Rollback
rollback.enabled true Enable rollback feature
rollback.permission 'rollback_audits' Required permission gate
rollback.restore_deleted true Restore soft-deleted models on rollback
rollback.track true Record an audit entry for the rollback itself
Retention
retention.default_days 0 Auto-prune after N days (0 = keep forever)
retention.per_model [] Per-model overrides: ['App\Models\Order' => 90]
Routes (incoming)
routes.enabled false Audit all web/api routes globally
routes.web true Include web routes in global auditing
routes.api true Include API routes in global auditing
routes.exclude ['horizon/*','telescope/*',...] URL patterns to skip
routes.body false Capture request body
routes.sample 1.0 Sampling rate (0.0 – 1.0)
routes.tag null Tag for all globally-audited routes
Queue
queue.enabled false Write audits asynchronously via queue
queue.connection null Queue connection name
queue.queue 'audits' Queue name
Jobs
jobs.enabled false Track job lifecycle globally
jobs.exclude [] Job classes to skip
Commands
commands.enabled false Track Artisan commands globally
commands.exclude ['schedule:run','queue:work',...] Commands to skip
commands.metrics.memory true Track peak memory usage
commands.metrics.audit_count true Count audits created during command
commands.metrics.anomaly false Enable anomaly detection
commands.metrics.anomaly_multiplier 2.0 Flag if metric > N × average
commands.metrics.anomaly_min_runs 5 Minimum runs before detection activates
HTTP (outbound)
http.enabled false Track outbound HTTP calls
http.mode 'auto' auto = all, manual = opt-in only
http.queue false Persist HTTP records asynchronously
http.capture_headers false Store request/response headers
http.capture_body false Store response body
http.body_limit 1000 Truncate response body at N characters
http.exclude_hosts [] Hosts to skip entirely
Events
events_tracking.enabled false Track application events globally
events_tracking.exclude [] Event classes to skip
listen [] Specific event classes to track (when global is disabled)
Other
guards ['web' => true, 'api' => true] Guards to resolve actors from
discovery.paths ['app/Models'] Paths for model discovery command
pipeline [] Custom pipeline stages for audit processing

❓ FAQ

Does this require a lot of configuration?

No. Install, migrate, add the AuditsChanges trait — that's it. Recordkeeper works with sensible defaults out of the box. It automatically tracks CRUD events, discovers fields from $fillable and $casts, excludes passwords, and auto-redacts sensitive patterns. You only configure things when you want to change the defaults.

Will this slow down my app?

No. A synchronous model audit adds about 1-2ms. For high-traffic apps, enable queue.enabled to drop that to under 0.5ms (the audit is written asynchronously). You can also use sample: 0.1 on busy routes to only audit 10% of requests, or use the redis driver for maximum write throughput. Features that are disabled have zero overhead — they don't even register their event listeners.

Is it compatible with existing laravel-auditing?

Yes. Recordkeeper installs owen-it/laravel-auditing as a dependency and builds on top of it. Your existing auditable models keep working. To unlock Recordkeeper's extra features (privacy protection, attributes, rollback, CLI tools), replace the Auditable trait with AuditsChanges on any model.

What happens if an audit write fails?

By default, failures are logged silently and your application continues normally — an audit failure should never break your app. If you want strict behavior (useful in tests), set RECORDKEEPER_STRICT=true and failures will throw exceptions instead.

How do Attributes, Traits, and Config interact?

Priority is Attribute > Trait > Config. The most specific configuration wins. For example, if your global config sets retentionDays: 365 but a model has #[Auditable(retentionDays: 90)], that model uses 90 days. If neither an attribute nor a trait specifies a value, the global config is used. This lets you set sensible defaults globally and override only where needed.

Can I write audits asynchronously?

Yes. Enable queue.enabled and audits are dispatched to a dedicated audits queue. Run a separate worker for isolation:

php artisan queue:work --queue=default    # your app jobs
php artisan queue:work --queue=audits     # audit writes

For full isolation, set RECORDKEEPER_QUEUE_CONNECTION=redis-audits to use a different queue connection entirely.

How does sensitive data protection work?

Three layers: (1) Global exclusion — fields like password and remember_token are never stored at all. (2) Auto-redaction — any field whose name contains patterns like secret, token, cvv, ssn, or iban is automatically replaced with ***. (3) Explicit encryption — use #[Encrypt('field')] for fields that need to be recoverable on rollback (AES-encrypted in storage). All of this happens before data reaches the audit store.

Does it support multi-tenancy?

Yes. Use tags and context enrichment to scope audits per tenant:

Recordkeeper::withTags(['tenant:'.$tenant->id]);
Audit::forTag('tenant:42')->get();

You can also use a custom actor resolver to track tenant-specific users across different auth guards.

What about audit storage growth?

Several options: (1) Set retention.default_days to auto-expire old audits and schedule recordkeeper:prune daily. (2) Set per-model retention with #[Auditable(retentionDays: 90)]. (3) Use the redis driver with a TTL for temporary audit data. (4) Use sample on high-traffic routes to reduce volume. (5) Use threshold on models to cap audits per instance.

Can I track incoming webhooks?

Yes. Incoming webhooks are just regular HTTP requests hitting your routes. Add audit.api middleware to your webhook routes, or enable RECORDKEEPER_ROUTES=true to audit all routes globally. No special webhook-specific setup is needed.

Can I track outgoing webhooks?

Yes. If your app sends webhooks using Laravel's Http:: facade, they're tracked automatically when RECORDKEEPER_HTTP=true. The URL, status code, duration, and any failures are recorded in the audit_http_requests table.

How do I search audits from the terminal?

Use recordkeeper:search with filters:

php artisan recordkeeper:search --model=Order --event=updated --since="-7 days" --tag=billing

For real-time monitoring, use recordkeeper:tail which live-follows new audits as they're written.

Can I use a custom storage driver?

Yes. Implement the AuditDriver contract and register it:

use LaraArabDev\Recordkeeper\Support\AuditDriverManager;

app(AuditDriverManager::class)->extend('custom', fn ($app) => new MyCustomDriver);

Then set RECORDKEEPER_DRIVER=custom in your .env.

What's the difference between route auditing and model auditing?

Model auditing tracks data changes — what fields were modified, old vs new values, who changed them. Route auditing tracks HTTP requests — who accessed what endpoint, response time, status code. They complement each other: route auditing tells you "User 42 hit POST /api/orders", while model auditing tells you "User 42 created Order #789 with these field values."

Does auditing work with saveQuietly() or withoutEvents()?

No. Recordkeeper relies on Eloquent model events (created, updated, deleted, restored) to capture changes. Methods like saveQuietly(), deleteQuietly(), Model::withoutEvents(), and raw DB queries suppress these events, so no audit is recorded and rollback is not possible.

If you need to bypass other observers but still want auditing, use Recordkeeper::log() to manually record the change:

$oldValues = $model->getOriginal();
$model->saveQuietly();

Recordkeeper::log('manual.update', subject: $model, context: [
    'old' => $oldValues,
    'new' => $model->toArray(),
]);

Note that manual log entries are not rollbackable — they are informational records only. For full audit and rollback support, use standard save(), update(), delete(), and restore() methods.

⚡ Performance

Operation Overhead
Model audit (sync) ~1-2ms
Model audit (async) < 0.5ms
Route middleware ~1-2ms
Job/Command/Event ~0.5-1ms
HTTP tracking ~0.3ms

Why it's fast: opt-in architecture means disabled features have zero overhead, early exits on every check, no reflection in hot paths, lightweight DTOs for data transfer, and sampling support for high-traffic routes.

Scenario Recommendation
High-traffic API (1000+ req/s) Enable queue.enabled, use sample: 0.1
Write-heavy app Use redis driver
Testing Use null driver
Large audit tables Set retention.default_days, schedule recordkeeper:prune daily
# Run benchmarks
composer bench           # full suite
composer bench:quick     # fast run

🧪 Testing & Quality

composer test           # Pest test suite
composer test:coverage  # With code coverage
composer analyse        # PHPStan (level 6)
composer format         # Laravel Pint
composer infection      # Mutation testing
PHP Laravel Status
8.2 11 / 12 Supported
8.3 11 / 12 Supported
8.4 11 / 12 Supported

🔐 Security

Please review our security policy on how to report security vulnerabilities.

📄 Credits & License

MIT License — see LICENSE for details.

🇸🇦 العربية

Recordkeeper بانر

ما هو Recordkeeper؟

Recordkeeper حزمة Laravel تمنح تطبيقك سجل تدقيق كامل بدون أي إعداد. ثبّت الحزمة، أضف trait واحد، وكل إنشاء وتعديل وحذف واستعادة يُسجّل تلقائياً. مبنية على owen-it/laravel-auditing وتوسّعها بشكل كبير.

لكن تتبع الموديلات هو البداية فقط. يمكن لـ Recordkeeper أيضاً تتبع:

  • كل طلب HTTP يصل لمساراتك (من وصل لأي نقطة نهاية، أوقات الاستجابة، أكواد الحالة)
  • كل استدعاء API صادر من تطبيقك (مدفوعات Stripe، إرسال webhooks، خدمات خارجية)
  • كل Job من الإرسال حتى الاكتمال أو الفشل
  • كل أمر Artisan مع المدة واستهلاك الذاكرة وكشف الشذوذ
  • كل حدث تطبيق مع التقاط البيانات الكاملة

أنت تختار ما تراقبه. تريد تدقيق الموديلات فقط؟ أضف الـ trait. تحتاج رؤية كاملة لـ API؟ فعّل تدقيق المسارات. تريد تتبع HTTP الصادر والـ Jobs والأوامر والأحداث أيضاً؟ فعّلها واحدة تلو الأخرى. أو فعّل كل شيء مرة واحدة. كل ميزة مستقلة — فعّل المزيج الذي يناسب تطبيقك بالضبط، والميزات المعطّلة ليس لها أي تأثير على الأداء.

تأتي أيضاً مع حماية خصوصية مدمجة (إخفاء تلقائي لكلمات المرور، تشفير الحقول الحساسة)، تراجع بضغطة واحدة (استرجاع أي تغيير مع معاينة قبل التطبيق)، و8 أوامر Artisan للبحث والتتبع وإدارة سجل التدقيق من الطرفية.

الفلسفة: تعمل فوراً بإعدادات ذكية — خصّص فقط عندما تحتاج.

التثبيت

composer require laraarabdev/recordkeeper
php artisan recordkeeper:install
php artisan migrate

البداية السريعة

use LaraArabDev\Recordkeeper\Concerns\AuditsChanges;

class Order extends Model implements \OwenIt\Auditing\Contracts\Auditable
{
    use AuditsChanges;  // يتتبع الإنشاء والتعديل والحذف والاستعادة تلقائياً
}

الميزات الرئيسية

الميزة الوصف التفعيل
تتبع الموديلات يسجّل كل تغيير مع القيم القديمة والجديدة أضف AuditsChanges trait
تدقيق المسارات يتتبع كل طلب HTTP وارد مع المستخدم والتوقيت audit middleware أو RECORDKEEPER_ROUTES=true
تتبع HTTP الصادر يسجّل كل استدعاء API خارجي عبر Http:: RECORDKEEPER_HTTP=true
تتبع الـ Jobs يتابع دورة حياة الـ Job كاملة AuditsJob trait أو RECORDKEEPER_JOBS=true
تدقيق الأوامر يتتبع أوامر Artisan مع المدة والذاكرة #[AuditCommand] أو RECORDKEEPER_COMMANDS=true
تتبع الأحداث يلتقط أحداث التطبيق مع البيانات #[AuditEvent] أو RECORDKEEPER_EVENTS=true
حماية الخصوصية إخفاء تلقائي للبيانات الحساسة + تشفير AES يعمل تلقائياً
التراجع استرجاع أي تغيير فردي أو مجموعة مع معاينة Recordkeeper::rollback($id)
8 أوامر Artisan بحث، تتبع مباشر، إحصائيات، تراجع، تنظيف من سطر الأوامر
4 محركات تخزين قاعدة بيانات، Redis، سجلات، Null RECORDKEEPER_DRIVER=...

حماية البيانات والخصوصية

البيانات الحساسة تُعالج قبل وصولها لمخزن التدقيق — لا تُخزّن بنص صريح أبداً.

الطبقة الآلية قابل للاسترجاع؟
استبعاد عام حقول مثل password و remember_token لا تُخزّن أبداً
إخفاء تلقائي الحقول المطابقة لأنماط مثل secret، token، cvv، ssn، iban تُستبدل بـ *** لا
إخفاء صريح #[Redact('field')] على الموديل لا
تشفير #[Encrypt('field')] — تشفير AES في التخزين نعم — يُفك تلقائياً عند التراجع
#[Redact('cvv', 'date_of_birth')]
#[Encrypt('national_id')]
class Payment extends Model implements \OwenIt\Auditing\Contracts\Auditable
{
    use AuditsChanges;
}

التراجع عن التغييرات

استرجاع أي تغيير — فردي أو مجموعة — مع معاينة اختيارية قبل التطبيق.

Recordkeeper::rollback($auditId, dryRun: true);  // معاينة (لا يتغير شيء)
Recordkeeper::rollback($auditId);                 // تطبيق التراجع
Recordkeeper::rollbackBatch('nightly-import');     // تراجع مجموعة كاملة
الحدث الأصلي إجراء التراجع
created يُحذف الموديل
updated تُستعاد القيم القديمة
deleted يُستعاد الموديل (يدعم SoftDeletes)

تجميع التدقيق (Batch)

تجميع التغييرات المرتبطة تحت معرّف واحد للتراجع الذري:

Recordkeeper::batch('nightly-import-2025-01', function () {
    Order::create([...]);
    Order::create([...]);
    $inventory->decrement('stock', 50);
    // جميع سجلات التدقيق تشترك بنفس batch_id
});

// لاحقاً، التراجع عن الاستيراد بالكامل:
Recordkeeper::rollbackBatch('nightly-import-2025-01');

التسجيل اليدوي

تسجيل أحداث مخصصة خارج تدفق Eloquent أو الـ Middleware:

Recordkeeper::log('payment.gateway.timeout', context: [
    'gateway' => 'stripe',
    'attempt' => 3,
]);

Recordkeeper::log('export.triggered', subject: $order, context: [
    'format' => 'csv',
    'rows'   => 1500,
]);

الاستعلام عن سجلات التدقيق

عبر Eloquent Scopes:

Audit::forModel('Order')->latest()->get();
Audit::forSubject($order)->get();
Audit::forActor($admin)->get();
Audit::forBatch('nightly-import')->get();
Audit::rollbackable()->get();

عبر Query Builder:

$audits = app(AuditQuery::class)
    ->model('Order')
    ->event(['created', 'updated'])
    ->tag('finance')
    ->since('-7 days')
    ->latest()
    ->limit(50)
    ->builder()
    ->get();

محركات التخزين

المحرك الأفضل لـ التراجع الاستعلام
database تدقيق كامل (الافتراضي) نعم نعم
redis كتابة عالية الأداء لا محدود
log المراقبة والتصحيح لا لا
null الاختبارات لا لا

التخصيص

// محلل ممثل مخصص
Recordkeeper::resolveActorUsing(function () {
    return auth()->guard('admin')->user()
        ?? auth()->guard('api')->user()
        ?? auth()->user();
});

// إثراء السياق
Recordkeeper::pushContext([
    'deployment' => config('app.version'),
    'server'     => gethostname(),
]);

// وسوم
#[Auditable(tags: ['billing', 'critical'])]
class Invoice extends Model { ... }

// في وقت التشغيل
Recordkeeper::withTags(['nightly-sync']);

ثلاث طرق للإعداد

config/recordkeeper.php   →  إعدادات عامة (كل الموديلات)
       ↓
#[Auditable(...)]         →  إعدادات لكل موديل عبر Attributes
       ↓
Traits + Methods          →  تحكم دقيق عبر methods قابلة للتعديل

الأولوية: Attribute > Trait > Config — الأكثر تحديداً يفوز دائماً.

أوامر Artisan

php artisan recordkeeper:search --model=Order --event=updated
php artisan recordkeeper:show 1842
php artisan recordkeeper:tail --model=Order
php artisan recordkeeper:stats --since="-30 days"
php artisan recordkeeper:rollback 1842 --dry-run
php artisan recordkeeper:prune --days=365 --yes
php artisan recordkeeper:models

الأداء

العملية التكلفة
تدقيق موديل (متزامن) ~1-2ms
تدقيق موديل (غير متزامن) < 0.5ms
Route middleware ~1-2ms
Job/أمر/حدث ~0.5-1ms
تتبع HTTP ~0.3ms

الميزات المعطّلة ليس لها أي تأثير على الأداء. لتقليل التكلفة في التطبيقات عالية الحركة: فعّل queue.enabled، استخدم sample: 0.1 على المسارات المزدحمة، أو استخدم محرك redis.

الأسئلة الشائعة

هل يحتاج إعداد كثير؟

لا. ثبّت، نفّذ التهجير، أضف AuditsChanges trait — هذا كل شيء. يعمل بإعدادات ذكية افتراضياً.

هل يبطئ تطبيقي؟

لا. ~1-2ms لكل تدقيق متزامن. فعّل queue.enabled لتقليلها إلى أقل من 0.5ms. الميزات المعطّلة ليس لها أي تكلفة.

هل يتوافق مع laravel-auditing الموجود؟

نعم. Recordkeeper يثبّت laravel-auditing كاعتماد ويبني فوقه. موديلاتك الحالية تستمر بالعمل. استبدل Auditable trait بـ AuditsChanges لفتح الميزات الإضافية.

هل يعمل التدقيق مع saveQuietly()؟

لا. Recordkeeper يعتمد على أحداث Eloquent. saveQuietly() و deleteQuietly() و Model::withoutEvents() تمنع هذه الأحداث، فلا يُنشأ سجل تدقيق ولا يمكن التراجع. استخدم save() و update() و delete() العادية للتدقيق الكامل.

كيف أتحكم بنمو بيانات التدقيق؟

عدة خيارات: اضبط retention.default_days لحذف السجلات القديمة تلقائياً، أو #[Auditable(retentionDays: 90)] لكل موديل، أو sample على المسارات المزدحمة، أو threshold لتحديد عدد السجلات لكل موديل.

هل يمكن تتبع الويب هوكس؟

نعم. الويب هوكس الواردة هي طلبات HTTP عادية — أضف audit.api middleware أو فعّل RECORDKEEPER_ROUTES=true. الويب هوكس الصادرة عبر Http:: تُتبع تلقائياً عند تفعيل RECORDKEEPER_HTTP=true.

مقارنة مع laravel-auditing

الميزة laravel-auditing فقط + Recordkeeper
تتبع الموديلات نعم نعم
تدقيق المسارات وAPI لا نعم
تتبع HTTP الصادر لا نعم
تتبع Jobs/أوامر/أحداث لا نعم
الإعداد مصفوفات PHP PHP 8 Attributes + Traits + Config
حماية الخصوصية أساسية إخفاء تلقائي + تشفير AES
التراجع يدوي بضغطة واحدة مع معاينة
التجميع (Batch) لا نعم — تراجع ذري لمجموعة
أدوات سطر الأوامر لا 8 أوامر
محركات التخزين قاعدة بيانات فقط 4 محركات

للتوثيق الكامل ومرجع الإعدادات التفصيلي، راجع الأقسام الإنجليزية أعلاه.