RomaLytar/yammi-audit-log
GitHub: RomaLytar/yammi-audit-log
一款 Laravel 审计日志包,通过自动追踪完整调用链与真实操作者归属,解决分布式队列应用中数据变更溯源困难的问题。
Stars: 8 | Forks: 0
# Yammi Audit Log - Laravel 变更历史与审计追踪
[](https://packagist.org/packages/romalytar/yammi-audit-log-laravel)
[](https://packagist.org/packages/romalytar/yammi-audit-log-laravel)
[](https://github.com/RomaLytar/yammi-audit-log/actions/workflows/ci.yml)
[](https://github.com/RomaLytar/yammi-audit-log/actions/workflows/ci.yml)
[](https://github.com/RomaLytar/yammi-audit-log/actions/workflows/ci.yml)
[](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)进行脱敏处理。

## 快速开始
```
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`),或者将该表迁移到专用的数据库连接。

## 环境要求与安装内容
- 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 数据,因此在大型表中依然能保持高效。
- 异步模式将插入操作本身移出了请求周期。
- 写入采用失败放行设计:如果审计插入失败,会将错误记录下来,但不会阻断宿主业务操作。
- 读取操作有上限限制:一年的查询范围,一万行的导出限制,以及分块的保留策略。
统计页面将这些数据以图形化方式呈现:数据增长和预计大小,最繁重的关联级联,以及变更最频繁的模型和字段。

## 平台功能(可选)
基于核心构建的独立功能,在您使用之前均处于关闭或零开销状态。
### 捕获策略与采样
默认设置是安全的:捕获一切。当您需要控制某个模型的数据量时,可以在同一个地方声明一个策略,以忽略嘈杂的字段、仅在特定条件下捕获,或者对高频变更的模型进行采样。
```
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');
```

### 防篡改证据
对每一条记录进行哈希链处理(SHA-256),并在事后验证数据是否被编辑或删除。
```
# AUDIT_LOG_INTEGRITY=true
php artisan audit-log:verify
```
### 异常检测与告警
日志会按需或通过 cron 自行监控,并将发现的问题发送到 Slack、已签名的 webhook 或邮件中。内置规则包括:
- **变更突发**:一个操作者在时间窗口内进行了超过 N 次变更。
- **批量删除**:一个操作者删除了超过 N 条记录。
- **非工作时间活动**:在配置的时间段内出现的用户活动。
- **级联权重**:一个关联(单个 request → job → job 链)在多个模型中产生了异常大量的变更。由于审计日志已经知晓完整的执行链,这能作为附带信号揭示潜在的写入放大或 N+1 式级联,无需使用性能分析器。
您也可以通过代码添加自定义规则(参见企业版部分),并从设置界面中调整所有阈值。

### 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`。
## 企业版(可选子系统)
独立的子系统,下面已折叠显示。按需展开。
$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 的形式接收窗口内的变更,无需数据库,也不依赖框架。抛出异常的规则会被隔离,不会中断扫描过程。
## 安全性
默认配置旨在确保安全;您始终掌控着各项利弊权衡。
- **敏感信息脱敏**在差异比对存储之前运行,确保配置的敏感字段不会进入数据库(显示为 `[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
流式传输至您的 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带有签名的完整性摘要(检测整段删除)
哈希链可以捕获对已存储数据行的篡改。带有签名的摘要则更进一步,它能在某一时刻使用您的非对称密钥进行签名认证,证明链头、记录数和时间跨度,从而使得删除整段数据(或整张表)的行为也能被检测到,并且归档的摘要可以独立于数据库进行验证。 ``` 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 天热力图的统计信息,以及无需重新部署即可调整包配置的设置界面。标签:ffuf, Laravel, 审计日志, 异常检测, 数据合规, 行为追踪