romalytar/yammi-audit-log-laravel 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

romalytar/yammi-audit-log-laravel

Composer 安装命令:

composer require 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.

README 文档

README

Latest Version on Packagist Total Downloads CI Coverage Tested on License

The audit log that answers not only what changed, but who really changed it, and why. Every Eloquent create/update/delete/restore is recorded with a real actor (user, queued job, Artisan command, scheduler), the person who started the cascade, field-level diffs with secret redaction, and a correlation id that ties a whole request → job → job chain together. History integrity is verifiable: an optional hash chain links every record to the previous one, and audit-log:verify proves nothing was edited.

Zero per-model setup. Install, migrate, done, the dashboard is optional and off by default.

$user->update(['email' => 'new@example.com']);

is recorded, with no code on your side, as:

{
    "event": "updated",
    "auditable": "App\\Models\\User #1",
    "actor": {"type": "user", "label": "John Doe"},
    "origin": null,
    "correlation_id": "550e8400-e29b-41d4-a716-446655440000",
    "changes": {
        "email": {"old": "old@example.com", "new": "new@example.com"}
    }
}

And when a queued job changes it instead, actor becomes the job class and origin keeps the user who dispatched it.

Audit log dashboard

Install

composer require romalytar/yammi-audit-log-laravel
php artisan migrate

# optional, only if you want the bundled admin dashboard at /audit-log:
php artisan audit-log:ui enable

That's it, changes are being recorded. No publishing needed: the package config and migrations are auto-discovered and loaded automatically; defaults are safe (UI off, 180-day retention, secrets redacted). Run vendor:publish only when you want to customize the config or the views, see Publishing assets.

In 30 seconds

  • Automatic capture of every Eloquent create / update / delete / restore, no traits or observers to register
  • Real actors: user, queued job, Artisan command, scheduler, attribution survives the queue
  • Correlation tracing: one id per request → job → job cascade, drawn as a chain in the UI
  • Optional hash-chain integrity, verified with one command
  • GDPR tooling: retention, archive, redaction, subject access reports
  • Anomaly detection with Slack / webhook / mail alerts
  • Dashboard is optional; everything is also available as facades and a JSON API

How it works

  • One global listener subscribes to Eloquent's created / updated / deleted / restored events; nothing to register per model.
  • Each change runs through a small pipeline (diff + redaction, actor and origin resolution, label snapshotting, optional request metadata) and ends in a single insert into one audit_log table, optionally on a dedicated connection.
  • Each audit record stores:
    • model reference and event type
    • field-level before/after diff
    • actor and origin (type, id, label)
    • correlation id and chain depth
    • request and tenant context
    • optional integrity hash, timestamps
  • Writes are synchronous by default; AUDIT_LOG_WRITE_ASYNC=true defers the insert to the queue while attribution and redaction are still resolved at the moment of the change.
  • Fail-open: a failed audit insert is logged and never breaks the host operation (see Security).

Performance

  • One insert per audited change, nothing else on the hot path; the capture services are resolved once per request, not per event.
  • No additional queries during capture, with two opt-in exceptions: FK label lookups for columns you explicitly map, and one chain-head select when integrity is enabled.
  • AUDIT_LOG_WRITE_ASYNC=true moves the insert itself to the queue.
  • A dedicated database connection keeps audit I/O away from your primary database (with a built-in transfer command).
  • Reads stay bounded: dashboard and export ranges are capped at one year, exports at 10k rows, retention deletes in chunks.

Requirements

  • PHP ^8.1
  • Laravel ^9.0 || ^10.0 || ^11.0 || ^12.0 || ^13.0
  • Any database supported by Laravel

Features

Actor attribution & change chains

Many audit solutions focus primarily on authenticated users, which can make changes triggered by queued jobs, Artisan commands or the scheduler harder to attribute. Here attribution is first-class:

  • Actor types: user, job, command, scheduler, system, resolved by a chain of providers you can extend.
  • Origin survives the queue. A job dispatched by a user keeps that user attached, serialized into the queue payload, proven by tests against a real database queue worker.
  • Correlation per unit of work. Every change made by one request, command or job cascade shares one id; the trace page draws the cascade as a ladder, indented by job nesting depth. Coming from a record? The entry you came from is highlighted and scrolled into view.
  • Impersonation-proof. When an admin works as another user (login-as), the label names both: Jane Doe (impersonated by Support Admin). Works with lab404/laravel-impersonate out of the box; session keys are configurable.

Change chain trace

Record view

One page per record: its full history on the left, every connected change on the right, records changed by the same action (correlation chains) and diffs of other models whose <model>_id points at this record. Honest data only: no guessed relationships.

$view = AuditLog::recordView(Order::class, 42);

Record view

Time machine

Pick a record and a date, see the exact attribute state it had at that moment, folded from its recorded diffs, with the applied history underneath. Strictly read-only: this package shows real history, it never restores or rewrites anything.

$state = AuditLog::stateAt(Order::class, 42, '2026-03-03');

if ($state->existed) {
    echo $state->attributes['status'];
}

Time machine

Noise diagnostics

An update that changed nothing real, only ignored attributes such as timestamps, is recorded and flagged as a no-op. These usually mean the same record is saved twice. The Noise page lists them and the nav badge counts them, so double writes are easy to hunt down instead of silently inflating your log.

Noise page

Statistics

Volume cards (total, last 30 days, per-day average, projection at your retention), breakdowns by event / actor type / model, and a GitLab-style 30-day activity heatmap. Every dashboard filter applies.

Statistics

Anomaly detection

The audit log watches itself. Three rules, all tunable from Settings:

Rule What fires it
Change burst One actor made more than N changes inside the window
Mass delete One actor deleted more than N records inside the window
Off-hours User activity inside a configured hour range (e.g. 0,5)

Run on demand from the Anomalies page (1 hour – 30 days window), from the console (audit-log:detect-anomalies), or on a cron. Every finding fires the AnomalyDetected event and goes to the alert channels below.

Alerts: Slack, webhook, mail

Declare rules, model, optionally attributes and events, and a matching change fires everywhere at once:

  • Slack incoming webhook: Block Kit message with the diff summary and a deep link to the filtered feed.
  • Generic JSON webhook for incident routers and automation hubs, signed with HMAC-SHA256 (X-Audit-Log-Signature) so receivers can verify authenticity. One retry on 5xx.
  • Mail to configured recipients.
  • SensitiveChangeRecorded event for anything custom.
// config/audit-log.php
'alerts' => [
    'rules' => [
        ['model' => App\Models\User::class, 'attributes' => ['role'], 'events' => ['updated']],
    ],
    'mail_to' => ['security@your.app'],
    'slack_webhook_url' => env('AUDIT_LOG_SLACK_WEBHOOK'),
    'webhook' => ['url' => env('AUDIT_LOG_WEBHOOK_URL'), 'secret' => env('AUDIT_LOG_WEBHOOK_SECRET')],
],

Every channel is fail-soft: a dead webhook never breaks the write path it piggybacks on.

GDPR & compliance

  • Subject access reports (GDPR Art. 15) in one command, every change made to the record plus every change made by it:

    php artisan audit-log:subject-report "App\Models\User" 5 --format=html
  • Retention on by default (180 days, clamped 7–9999) with a scheduled prune, audit data is PII.

  • Archive before delete: audit-log:archive --then-prune writes expiring records as NDJSON to any filesystem disk (S3 included).

  • Recursive secret redaction before anything touches the database: password, token, api_key, …, including inside nested JSON values. Configurable patterns and placeholder.

  • Export of any filtered view as CSV/JSON, capped by rows and a one-year range, with CSV-injection protection.

Tamper evidence

Turn on AUDIT_LOG_INTEGRITY=true and every record is hash-chained (SHA-256) to the previous one inside a locked transaction, concurrent writers cannot fork the chain.

php artisan audit-log:verify
# Integrity OK: 169 hashed record(s) verified.

If someone edits or deletes a row in the table, verify names the first broken record. Pruning stores a chain anchor so verification stays strict even after retention deletes old rows.

Multi-tenancy

Running a SaaS? Implement one tiny contract and point the config at it:

final class CurrentTenantResolver implements \Yammi\AuditLog\Application\Contract\TenantResolver
{
    public function resolve(): ?string
    {
        return tenant()?->id;
    }
}

// config/audit-log.php
'tenancy' => ['resolver' => CurrentTenantResolver::class],

Every new record is stamped with the tenant at capture time (it survives the queue), and every read, dashboard, facades, JSON API, exports, is scoped to the current tenant automatically. Retention, archive and integrity verification always run across all tenants. Returning null means single-tenant: nothing changes.

Embed it your way

The dashboard is optional. Everything it shows is available as plain DTOs through one facade:

AuditLog::for(Order::class, 42);          // timeline of one record
AuditLog::changes(['event' => 'updated', 'actor_type' => 'job']);
AuditLog::noise();                        // flagged double writes
AuditLog::chain($correlationId);          // the full cascade
AuditLog::stats(['model' => Order::class]);
AuditLog::anomalies(1440);                // the scan as data
AuditLog::recordView(Order::class, 42);   // history + related changes
AuditLog::subjectReport(User::class, 5);  // GDPR report as data
AuditLog::stateAt(Order::class, 42, '2026-03-03');
AuditLog::record(...);                    // manual write for mass updates / raw SQL
  • Facade Playground at /audit-log/settings/playground: browse every method, fill the auto-generated form, run it, see the JSON.
  • Opt-in JSON API (AUDIT_LOG_API_ENABLED=true, bring your own auth middleware): changes, noise, chain/{uuid}, stats, timeline, state, record-view, subject-report, anomalies.
  • JobsMonitor bridge: set AUDIT_LOG_JOBS_MONITOR_URL and every job actor links straight to Yammi Jobs Monitor.

Settings UI

Operational config lives in the database so operators can tune the package without a redeploy: capture toggle, retention and prune schedule, async writes, integrity, ignored attributes, redaction, alert channels, anomaly thresholds, display timezone and more, across 7 groups, with built-in documentation covering every feature.

Resolution order: DB row → config value → package default. Bootstrap-critical values (database connection, route path, middleware, gate) and secrets stay in config/.env on purpose.

Settings

Choosing what gets audited

// Everything is captured by default. Narrow it per model:
class Document extends Model
{
    public array $auditExclude = ['internal_notes'];   // never stored
    // or the inverse:
    public array $auditInclude = ['status', 'price'];  // only these
}

// Or flip to opt-in mode: only models implementing the marker are recorded.
// AUDIT_LOG_CAPTURE_MODE=opt_in
class Order extends Model implements \Yammi\AuditLog\Contracts\ShouldAudit {}

Honest limitations

Eloquent events never fire for mass Query Builder ->update(), raw SQL or pivot sync(), no event-based audit package sees those. Record them explicitly through the same pipeline (redaction, attribution, correlation included):

Order::where('status', 'pending')->update(['status' => 'cancelled']);

AuditLog::record(Order::class, $order->id, 'updated',
    before: ['status' => 'pending'],
    after:  ['status' => 'cancelled'],
);

Why another audit package?

The Laravel ecosystem already provides excellent audit packages, spatie/laravel-activitylog and owen-it/laravel-auditing among them. This package was built for projects that additionally needed:

  • actor attribution across queued jobs, Artisan commands and the scheduler, not only authenticated users
  • correlation traces across request → queue → queue cascades
  • tamper-evident history that can be verified after the fact
  • anomaly detection and alerting on the audit stream itself
  • a bundled dashboard, retention and archive tooling, and an operational settings UI

Rather than combining several packages with custom infrastructure, it aims to provide these capabilities in a single, self-contained solution. If your current audit setup covers your needs, keep it; if the list above reads like your requirements, this package was built for you.

Configuration

// config/audit-log.php (publish with vendor:publish --tag=audit-log-config)
return [
    'enabled' => env('AUDIT_LOG_ENABLED', true),

    'capture' => [
        'mode' => env('AUDIT_LOG_CAPTURE_MODE', 'all'),   // all | opt_in
        'ignore_attributes' => ['created_at', 'updated_at'],
        'request_context' => env('AUDIT_LOG_REQUEST_CONTEXT', false),
    ],

    'redaction' => [
        'keys' => ['password', 'token', 'secret', 'api_key', /* … */],
        'placeholder' => '[redacted]',
    ],

    '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),
        'path' => 'audit-log',
        'middleware' => ['web', 'auth'],
        'gate' => env('AUDIT_LOG_UI_GATE'),
    ],

    'database' => [
        // Optional dedicated connection + audit-log:transfer-data command.
        'connection' => env('AUDIT_LOG_DB_CONNECTION'),
    ],
];

Publishing assets

php artisan vendor:publish --tag=audit-log-config
php artisan vendor:publish --tag=audit-log-views
php artisan vendor:publish --tag=audit-log-migrations

Security

  • Secrets never reach the database. Recursive redaction runs before the diff is stored; changed secret values are still audited, as [redacted].
  • Fail-open write path, by design. Auditing must never take your application down: if the audit insert fails, the error goes to your log and the business operation continues. If you need the opposite guarantee (no audit record, no write), wrap both in your own transaction.
  • UI off by default, behind configurable middleware (web, auth), an optional Gate and a rate limit.
  • Read-only JSON API, off by default, with host-chosen auth; the one write method (record()) is deliberately PHP-only.
  • Signed outbound webhooks (HMAC-SHA256) so receivers can verify alerts are genuine.
  • Strict input parsing everywhere: filters validated and bounded (max one-year range), LIKE patterns escaped, route params constrained, CSV export neutralises formula injection.
  • Retention is on by default, audit data is PII.

License

MIT

统计信息

  • 总下载量: 0
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 2
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-06-12

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固