RomaLytar/yammi-audit-log

GitHub: RomaLytar/yammi-audit-log

一款 Laravel 审计日志包,通过自动追踪完整调用链与真实操作者归属,解决分布式队列应用中数据变更溯源困难的问题。

Stars: 8 | Forks: 0

# Yammi Audit Log - Laravel 变更历史与审计追踪 [![Packagist 最新版本](https://img.shields.io/packagist/v/romalytar/yammi-audit-log-laravel.svg?v=1)](https://packagist.org/packages/romalytar/yammi-audit-log-laravel) [![总下载量](https://img.shields.io/packagist/dt/romalytar/yammi-audit-log-laravel.svg?v=1)](https://packagist.org/packages/romalytar/yammi-audit-log-laravel) [![CI](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/06616a80ea065441.svg)](https://github.com/RomaLytar/yammi-audit-log/actions/workflows/ci.yml) [![覆盖率](https://img.shields.io/badge/coverage-95.5%25-brightgreen)](https://github.com/RomaLytar/yammi-audit-log/actions/workflows/ci.yml) [![测试环境](https://img.shields.io/badge/tests-PHP%208.1%20%7C%208.2%20%7C%208.3-777BB4?logo=php&logoColor=white)](https://github.com/RomaLytar/yammi-audit-log/actions/workflows/ci.yml) [![许可证](https://img.shields.io/packagist/l/romalytar/yammi-audit-log-laravel.svg?v=1)](https://packagist.org/packages/romalytar/yammi-audit-log-laravel) **为分布式、重度依赖队列的 Laravel 应用提供的变更历史与执行追踪:记录每一次变更背后的真实操作者、来源与关联。** ## 为什么需要它 大多数 Laravel 审计工具只记录了模型上发生了*什么*变化。而真实的系统是分布式的: ``` HTTP request → service → queue → job → model ``` 当数据行被写入时,在事件排查期间最关键的问题往往很难回答:*究竟是谁触发了这次变更,经过了怎样的调用链?* 这个包记录了每次变更的完整执行上下文,而不仅仅是最终的写入操作。 ## 核心理念 在每次变更时自动捕获,无需针对每个模型进行设置。每条记录包含: - **操作者**:谁执行了变更(`user`、`job`、`command`、`scheduler`、`system`),由可扩展的 provider 链解析。同样支持代登录场景:`Jane Doe (impersonated by Support Admin)`。 - **来源**:谁启动了调用链,被序列化到队列 payload 中,因此能在异步 job 中保留。 - **关联 ID (Correlation id)**:贯穿 request → service → queue → job → model 的唯一 ID。 ``` { "event": "updated", "auditable": "App\\Models\\Order #42", "actor": {"type": "job", "label": "App\\Jobs\\ChargeOrder"}, "origin": {"type": "user", "label": "John Doe"}, "correlation_id": "550e8400-e29b-41d4-a716-446655440000", "changes": {"status": {"old": "pending", "new": "paid"}} } ``` 用户点击了“支付”,一个队列 job 完成了写入,而日志保留了两者的记录。每条记录还会存储字段级别的前后差异,并在存入数据库前对敏感信息(如 `password`、`token`、`api_key` 等,包括嵌套的 JSON)进行脱敏处理。 ![审计日志仪表盘](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/c49bbd910b065447.png) ## 快速开始 ``` composer require romalytar/yammi-audit-log-laravel php artisan migrate php artisan audit-log:ui enable # optional dashboard at /audit-log ``` ``` User::first()->update(['name' => 'Test']); ``` 打开 `/audit-log`。变更记录已经存放在那里了,并且填充了对应的操作者、来源和关联 ID。基础模型审计无需任何设置(下文提到的可选特性仅用于特殊场景)。默认配置开箱即用且安全(UI 默认关闭,180 天保留期,敏感信息已脱敏)。 ## 工作原理 上述三个字段背后的机制如下: 1. 一个全局监听器捕获 Eloquent 的 `created` / `updated` / `deleted` / `restored` 事件,无需为每个模型单独注册。 2. 一个小型 pipeline 负责构建差异比对、脱敏敏感信息、解析操作者 + 来源 + 关联 ID,并为外键标签生成快照。 3. 它将一行记录写入 `audit_log` 表。 可选地,你可以将写入操作推迟到队列中执行(`AUDIT_LOG_WRITE_ASYNC=true`),或者将该表迁移到专用的数据库连接。 ![变更链追踪](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/773c9b0e3c065452.png) ## 环境要求与安装内容 - PHP `^8.1`,Laravel `^9.0 || ^10 || ^11 || ^12 || ^13`,支持 Laravel 兼容的任何数据库。 - 捕获机制基于 Eloquent 模型事件构建。通过 `Query Builder ->update()` 或原生 SQL 进行的更改不会被自动捕获;请使用 `AuditLog::record()` 显式记录这些更改。 - 迁移文件会创建四个自动加载的表:`audit_log`(记录数据),`audit_log_settings`(设置 UI),以及 `audit_log_digests` + `audit_log_chain_state`(仅在启用完整性校验时使用)。它们可以存放在专用的数据库连接中。 - 每条记录都带有 `event_version`(记录 schema 版本),在写入时标记,并包含在 JSON API 和 SIEM payload 中,这样消费端就可以依赖其数据结构,并在未来 schema 发生变化时进行逻辑分支处理。 - 仅当您选择开启异步写入或 SIEM 流式传输时才需要队列;默认的写入路径是同步的。 ## 性能表现 日志包运行在应用的关键路径上,因此其开销被刻意保持在可控且可预测的范围内: - 在写入路径上,仅需一次记录插入,外加一次针对其变更字段索引的批量插入。 - 捕获服务在每个请求中只解析一次,而不是每个事件解析一次。 - 捕获期间没有额外的查询,有两个可选的例外情况:您映射的外键标签查找,以及开启完整性校验时的一次链头查询。 - 字段级别的搜索(`field('status')`)会查找一个已索引的变更键值表,而不是扫描每一行的 JSON 数据,因此在大型表中依然能保持高效。 - 异步模式将插入操作本身移出了请求周期。 - 写入采用失败放行设计:如果审计插入失败,会将错误记录下来,但不会阻断宿主业务操作。 - 读取操作有上限限制:一年的查询范围,一万行的导出限制,以及分块的保留策略。 统计页面将这些数据以图形化方式呈现:数据增长和预计大小,最繁重的关联级联,以及变更最频繁的模型和字段。 ![统计信息:增长情况、顶级级联和变更热点](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/d089b9d894065458.png) ## 平台功能(可选) 基于核心构建的独立功能,在您使用之前均处于关闭或零开销状态。 ### 捕获策略与采样 默认设置是安全的:捕获一切。当您需要控制某个模型的数据量时,可以在同一个地方声明一个策略,以忽略嘈杂的字段、仅在特定条件下捕获,或者对高频变更的模型进行采样。 ``` AuditLog::policy(Order::class) ->ignore(['updated_at']) // drop noisy fields ->when(fn ($order) => $order->tenant_id === 'acme') // capture conditionally ->sample(0.1); // keep ~10% of the changes ``` 采样是基于每个关联 ID 决定的,而不是基于单行数据,因此一个工作单元内某条记录的完整历史会被整体保留或丢弃,绝不会留下残缺不全的数据。 ### 时间机器 重建一条记录在过去任意时刻的精确状态,由其差异记录折叠汇总而成。只读模式:它展示真实的历史,绝不重写任何内容。 ``` AuditLog::stateAt(Order::class, 42, '2026-03-03'); ``` ![时间机器](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/3bfb7c342e065504.png) ### 防篡改证据 对每一条记录进行哈希链处理(SHA-256),并在事后验证数据是否被编辑或删除。 ``` # AUDIT_LOG_INTEGRITY=true php artisan audit-log:verify ``` ### 异常检测与告警 日志会按需或通过 cron 自行监控,并将发现的问题发送到 Slack、已签名的 webhook 或邮件中。内置规则包括: - **变更突发**:一个操作者在时间窗口内进行了超过 N 次变更。 - **批量删除**:一个操作者删除了超过 N 条记录。 - **非工作时间活动**:在配置的时间段内出现的用户活动。 - **级联权重**:一个关联(单个 request → job → job 链)在多个模型中产生了异常大量的变更。由于审计日志已经知晓完整的执行链,这能作为附带信号揭示潜在的写入放大或 N+1 式级联,无需使用性能分析器。 您也可以通过代码添加自定义规则(参见企业版部分),并从设置界面中调整所有阈值。 ![异常检测](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/a2254bfb10065509.png) ### GDPR 与合规性 只需一条命令即可生成主体访问报告,默认开启保留策略,支持删除前归档到任意磁盘,以及递归的敏感信息脱敏。 ``` php artisan audit-log:subject-report "App\Models\User" 5 --format=html ``` ### Facades 与 JSON API 仪表盘是可选的,它展示的所有内容都可以通过一个 facade(以及可选的 JSON API)以纯 DTO 的形式获取: ``` AuditLog::for(Order::class, 42); // timeline of one record AuditLog::chain($correlationId); // the full cascade AuditLog::changes(['event' => 'updated', 'actor_type' => 'job']); AuditLog::record(...); // manual write for mass updates / raw SQL // Or the same query as a fluent builder (sugar over changes(), same results): AuditLog::query()->field('status')->from('pending')->to('paid')->actorType('job')->get(); ``` 完整的功能集(`stateAt`、`noise`、`stats`、`recordView`、`subjectReport`、`anomalies` 等)以及 JSON API 端点都列在应用内的文档中,路径为 `/audit-log/settings/docs`。 ## 企业版(可选子系统) 独立的子系统,下面已折叠显示。按需展开。
流式传输至您的 SIEM(Splunk / Datadog / Elastic / HTTP) 将所有记录的变更传输到您的 SIEM,脱离请求路径,通过队列进行且具备容错能力,因此即使接收端缓慢或宕机也不会影响宿主业务的写入: ``` // config/audit-log.php → 'stream' 'enabled' => true, 'driver' => 'splunk', // splunk | datadog | elastic | http 'endpoint' => 'https://splunk.example:8088/services/collector/event', 'token' => env('AUDIT_LOG_STREAM_TOKEN'), 'source' => 'orders-api', ``` 标准化的事件只构建一次,并被封装在每个平台的信封格式中。交付过程是一个排队的 job,并在收到 5xx 错误时进行一次重试。
多租户 实现一个契约并将配置指向它即可: ``` final class CurrentTenantResolver implements \Yammi\AuditLog\Application\Contract\Resolver\TenantResolver { public function resolve(): ?string { return tenant()?->id; } } // config/audit-log.php 'tenancy' => ['resolver' => CurrentTenantResolver::class], ``` 记录在捕获时会被打上租户的标签(该标签在队列中依然保留),每次读取都会自动限制在当前租户范围内,并且保留、归档和完整性校验也会跨所有租户运行。返回 `null` 表示单租户模式:一切照旧。
访问日志记录(谁*查看*了记录) 在 HIPAA/GDPR 要求下,*谁查看了这些 PII* 与谁修改了它同样重要。通过 `LogsAccess` trait 为指定模型开启,或者在任意位置手动记录: ``` use Yammi\AuditLog\Concerns\LogsAccess; class Patient extends Model { use LogsAccess; } $patient->recordAccess(); // event: accessed, attributed to the viewer AuditLog::recordAccess(Patient::class, $id); // or without the trait ``` 读取操作的量通常很大,启用此功能时请将 `retention_days` 设置得尽可能短。
Pivot 审计(attach / detach / sync) Eloquent 在写入 pivot 数据时不会触发模型事件,因此普通的 `$user->roles()->sync([...])` 不会留下任何痕迹。添加 `AuditsPivots` trait 并使用已审计的包装器,它们会通过完整的 pipeline 记录前后的数据集: ``` use Yammi\AuditLog\Concerns\AuditsPivots; $user->auditAttach('roles', $roleId); // event: attached $user->auditDetach('roles', [$a, $b]); // event: detached $user->auditSync('roles', [$a, $c]); // event: synced ```
代码定义的异常规则 针对特定领域的检测需求,您可以编写规则类,在 Git 中进行版本控制,独立进行单元测试,它将与内置规则一起在相同的时间窗口内运行: ``` use Yammi\AuditLog\Application\Contract\AnomalyRule; use Yammi\AuditLog\Application\DTO\Anomaly\AnomalyData; use Yammi\AuditLog\Application\DTO\Anomaly\AnomalyWindow; use Yammi\AuditLog\Application\DTO\Audit\TimelineEntryData; final class PriceDropRule implements AnomalyRule { public function key(): string { return 'price_drop'; } /** @param list $entries @return list */ public function evaluate(array $entries, AnomalyWindow $window): array { // inspect $entries, return AnomalyData findings with your own severity } } // config/audit-log.php → 'anomalies' => ['rules' => [PriceDropRule::class]] ``` 规则以纯 DTO 的形式接收窗口内的变更,无需数据库,也不依赖框架。抛出异常的规则会被隔离,不会中断扫描过程。
带有签名的完整性摘要(检测整段删除) 哈希链可以捕获对已存储数据行的篡改。带有签名的摘要则更进一步,它能在某一时刻使用您的非对称密钥进行签名认证,证明链头、记录数和时间跨度,从而使得删除整段数据(或整张表)的行为也能被检测到,并且归档的摘要可以独立于数据库进行验证。 ``` php artisan audit-log:digest # record a signed snapshot php artisan audit-log:verify # checks the chain AND the latest signed digest ```
更多功能:变更原因、值转换、按主体查看动态 ``` // Stamp every change inside a unit of work with a reason (covered by the integrity chain): AuditLog::withReason('ticket #4521', fn () => $order->update(['status' => 'refunded'])); // "Who moved an order to cancelled?" is one filter, not a scan: AuditLog::changes(['field' => 'status', 'value_from' => 'pending', 'value_to' => 'cancelled']); // Hand a user a signed, read-only "Account activity" page, scoped to one subject, no login: $url = AuditLog::activityUrl($user, $user->id, minutes: 30); ``` 仪表盘还提供记录视图、噪声诊断(标记重复写入)、带有 30 天热力图的统计信息,以及无需重新部署即可调整包配置的设置界面。
## 安全性 默认配置旨在确保安全;您始终掌控着各项利弊权衡。 - **敏感信息脱敏**在差异比对存储之前运行,确保配置的敏感字段不会进入数据库(显示为 `[redacted]`)。您可以自定义键名匹配模式。 - **设计上采用失败放行。**写入路径力求不阻塞您的应用程序:如果审计插入失败,错误会被记录下来,而业务操作会继续进行。如果您需要相反的保证(即“无记录则不写入”),请在您自己的事务中同时包裹这两者。 - **UI 默认关闭**,由可配置的中间件(`web`、`auth`)、可选的 Gate 和速率限制进行保护。仪表盘自行托管其静态资源,因此不会发起任何外部 CDN 请求,也不需要配置 CSP 例外。 - **API 可配置为失败阻断。**如果中间件中没有包含 auth guard,只读的 JSON API 将不会被注册;仪表盘的破坏性操作(数据库传输、写入测试沙盒)可以放在由宿主定义的 Gate 之后。 - **带有签名的出站 Webhook**(HMAC-SHA256)允许接收方验证告警的真实性。 - **输入验证与限制**:过滤器受到约束,LIKE 模式被转义,路由参数强制类型化,CSV 导出会转义公式字符。 - **默认开启数据保留**,因为审计数据属于 PII。 ## 对比分析 现有的 Laravel 审计包,包括 [spatie/laravel-activitylog](https://github.com/spatie/laravel-activitylog) 和 [owen-it/laravel-auditing](https://github.com/owen-it/laravel-auditing),主要侧重于模型变更和用户活动。而本包专注于需要执行可追溯性的、重度依赖队列的分布式应用:在一个独立的包中提供跨层级的归因(request → job → job)、能够在异步环境中存续的来源信息、关联追踪,以及用于事件调查的防篡改历史。如果您当前的配置已经满足需求,请继续保持。 ## 非 - 本包不会做的事情 这些都是经过深思熟虑后设定的永久界限。这其中的任何一项都会迫使审计日志成为*事实来源*或*实时系统*,而这将破坏让它能安全安装的核心不变量:捕获过程是失败闭合的、脱离于您的写入关键路径的、仅作附加的,且永远不会修改您的数据。 - **不提供事件溯源或状态重放。**时间机器仅用于取证,以只读方式重建过去的状态。它永远不会成为您用来重建应用程序的记录系统。 - **不提供背压引擎或优先级队列。**采样机制负责控制数据量;Broker 级别的负载均衡属于 Kafka/SaaS 的领域,不应由一个包来承担。 - **包内部不提供搜索引擎。**搜索功能向外指向您的 SIEM/Elastic(内置流式传输支持),向内则通过已索引的变更键表进行,而不是捆绑一个 Meilisearch 或 Elastic 适配器。 - **不提供分布式可观测平台。**这种规模下的指标、追踪和仪表盘应该交给 Datadog / Splunk / Pulse 处理。我们的核心护城河是应用级别的溯源信息,而不是与他们竞争。 - **不提供查询分析器或分布式追踪。**我们根据已经捕获的数据揭示写入端的级联;读取路径的查询性能分析属于 Telescope / Pulse 的范畴。 ## 配置说明 ``` // 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 '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), 'middleware' => ['web', 'auth']], 'database' => ['connection' => env('AUDIT_LOG_DB_CONNECTION')], // optional dedicated connection ]; ``` 运维设置(保留策略、脱敏、告警渠道、异常阈值等)也可以直接在设置界面中修改,无需重新部署。解析优先顺序:**数据库配置行 → 配置文件值 → 包默认值**。关键引导值和敏感信息应始终保存在 `config`/`.env` 中。 ## 许可证 MIT
标签:ffuf, Laravel, 审计日志, 异常检测, 数据合规, 行为追踪