日志与归档协同重构方案
- 文档性质:日志写入、检索、保留、归档与删除的一体化技术设计
- 对应规格:日志检索与归档协同
- 对应计划:日志归档系统可靠性重构方案
- 当前状态:方案已基于代码完成静态审查;尚未变更任何数据、生产调度或保留策略。
结论
日志系统不能与归档系统分开重构。当前业务和 API 访问会通过 OperationLogService 同步写入 operation_logs 与 activity_logs,而管理端不同 channel 又读取数据库表、调度表和 Laravel 文件日志。若直接按表归档或由管理端直接删除,会产生重复、缺页、破坏幂等状态和缺失归档证据的风险。
目标不是把全部内容塞入一张表,而是建立一个受版本控制的“日志流目录”:每一类数据都有唯一写入真源、检索投影、保留策略和允许的归档状态。只有被目录明确标为可归档的不可变事件,才能进入 V2 两阶段归档协议。
当前审查结果
| 区域 | 当前实现 | 与归档的冲突或风险 |
|---|---|---|
| API 与业务操作 | 全局 LogOperation 中间件记录绝大多数 /api/* 请求;OperationLogService 先写 operation_logs,再容错双写 activity_logs。 | 同一事件有两份 ID,双写失败不阻断主流程;管理员查询的切源条件不能证明两表完整一致。 |
| 管理端检索 | /v2/admin/logs/{channel} 通过 AdminLogV2QueryService 提供 activity、api、admin-logins、短信、邮件、网关、runtime、schedule、tasks、system 十个 channel。 | 查询层同时依赖 operation_logs、activity_logs、schedule_run_logs、插件运行日志和 laravel.log,不存在统一的冷热边界。 |
| 调度记录 | ScheduleRunLogService 同时写 schedule_run_logs 和 activity_logs 的 cron 镜像;心跳任务另写 schedule_task_runs。 | 同一执行有镜像和独立运行状态,按整表归档会导致页面重复或影响进行中任务的去重。 |
| 管理端清理 | POST /v2/admin/log-cleanups 可直接删除短信、邮件、API、管理员登录、业务审计、调度记录,并会重写 storage/logs/laravel.log。 | 该旁路不生成归档物、manifest 或可恢复审计,不能继续与归档任务并存。 |
| 当前归档 | db:archive-logs 的默认白名单含 8 表,使用 pt-archiver --file --purge,默认在线 30 天、文件 180 天。心跳任务 log-archive 每日 02:00 已注册并显式传入 --execute。 | 外部工具写入最终文件与物理删除耦合,文件校验在删除后;白名单把幂等状态与支付网关证据当作普通日志处理。 |
| 文件运行日志 | Laravel 使用 daily channel,默认保留 14 天;管理员直接从文件解析 runtime/task 内容。 | 这不是数据库归档物,按行重写活动日志文件会与 Monolog 轮转产生并发和完整性风险。 |
必须先调整的分类
automation_logs 的 recordOnce()、hasRecord() 与 markExecuted() 被自动化流程用于幂等控制,不能按通用日志期限归档或删除。gateway_logs 关联账单和支付网关交互,虽然当前未列入财务排除清单,但在数据责任人确认前也不能作为普通日志删除。二者从 V2 默认白名单移除。
目标架构
请求、业务服务、调度、插件
|
v
日志流目录(类型、真源、保留、归档资格)
|
+------+-----------------+------------------+
| | |
v v v
在线事件真源 业务状态表 Laravel 文件轮转
activity_logs 等 automation_logs daily / 14 天
|
v
V2 归档协调器:暂存 -> 校验 -> 发布 -> 清除
| |
v v
归档物 + manifest + 审计元数据 在线检索 / 冷检索目录1. 日志流目录与数据等级
新增仅由后端读取的 LogStreamRegistry(配置加测试,不开放管理端任意修改)。每个流至少声明:stream、在线真源、是否不可变、是否可归档、选择时间列、热数据期限、归档文件期限、冷检索能力和数据责任人。运行时不允许命令参数绕过该目录指定表名或文件路径。
| 分类 | 当前来源 | V2 处置 | 前置条件 |
|---|---|---|---|
| API、认证、业务活动 | activity_logs;operation_logs 为过渡双写来源 | activity_logs 成为唯一在线事件真源;历史 operation_logs 只做一次迁移与清退。 | 用事件 ID、行数和抽样内容完成回填对账;API 与管理员登录查询改读新真源后才停止旧写入。 |
| 短信、邮件投递 | message_logs | 候选可归档流,不进入默认删除。 | 确认通知追溯期限、可检索字段及对个人数据的保留要求。 |
| 传统 Cron 执行历史 | schedule_run_logs 与 activity_logs 镜像 | 保留一个规范检索投影,终态记录才允许归档。 | 明确镜像的事件关联字段,查询层不得把同次运行显示两次。 |
| 心跳任务运行状态 | schedule_task_runs | queued、running 永久留在线;仅终态历史可在单独期限下归档。 | 归档选择条件必须排除活跃状态,且不影响 activeRunForTask()。 |
| 自动化幂等状态 | automation_logs | 不归档、不纳入管理员清理。 | 如未来需要压缩,另行设计可证明不破坏幂等性的状态归并。 |
| 插件运行诊断 | integration_plugin_runtime_logs | 候选可归档流。 | 完成请求/响应元数据字段审查、冷热检索和插件边界回归。 |
| 支付网关交互 | gateway_logs | 默认排除。 | 财务与合规确认其是否属于支付审计证据及最长保留期。 |
| 归档审计、财务、支付、回调、失败队列 | archive_audit_logs 等 | 永久排除。 | 只能依照各自业务/合规策略处理。 |
| Laravel 文件日志 | storage/logs/laravel-*.log | 继续由 Monolog 日轮转处理;不进入数据库归档命令。 | 删除管理端逐行重写,改为轮转、只读检索和独立运维策略。 |
2. 单一事件真源迁移
activity_logs 承接访问、认证和业务活动,保留已有 actor_*、subject_*、module、action、description、context、ip_address 与 created_at 语义。新迁移仅追加以下稳定检索字段,历史内容不改写:
event_id:ULID,唯一标识一个逻辑事件,供回填、归档和冷检索引用。stream:受LogStreamRegistry约束的access、auth、business、schedule等流名。trace_id:从现有 context 提升的可索引关联键;没有时保持NULL。occurred_at:事件发生时间;过渡期与created_at一致,禁止用归档时间替代。
新 ActivityEventWriter 负责落库。OperationLogService 在迁移期间只作为调用面适配器,最终删除其对 operation_logs 的写入与 ActivityLog 容错双写实现;它不再保留没有调用方的双表兼容路径。全局中间件继续生成 X-Request-Id,并将成功轮询跳过规则迁入日志流目录,改动必须由路由级测试覆盖。
3. 在线与冷数据检索契约
管理端现有 channel 名称和分页响应在在线查询阶段保持不变。切换完成后,AdminLogV2QueryService 不再按“表是否有数据”选择来源,而按 channel 显式映射到唯一在线真源。
归档完成的历史数据通过归档目录和 manifest 建立只读目录索引。新增的冷检索只接受时间范围、channel/stream、事件 ID、trace ID 和受限关键词;不接受物理路径、文件名或原始 SQL。跨冷热边界采用时间游标而非数据库 offset,返回带来源的不可变 ID:hot:{event_id} 或 archive:{archive_item_id}:{event_id}。当范围跨越热数据截止点时,服务按 occurred_at,event_id 合并去重;若归档物未通过校验,结果必须显式标注不可用而不是静默缺页。
冷检索首期仅开放 manifest 已验证的可归档流。管理员详情页显示数据来源、归档批次、哈希和恢复状态,不允许在线编辑归档内容。
4. V2 归档协议
对每个已批准的表和固定 ID 分块执行如下状态机:
planned -> staging -> verified -> published -> purging -> purged
\-> failed / needs_recovery- 创建审计项,固定表、流、截止时间、ID 边界和预期行数。
pt-archiver只导出同目录.part,不带--purge,不追加已有文件。- 流式校验 CSV 表头、行数、ID 边界、大小和 SHA-256;flush/fsync 成功后原子发布数据文件与 manifest。
- 仅按已发布 manifest 的表、截止时间和 ID 边界分块删除源记录;记录实际删除数。
- 无候选残留后标记
purged,写入归档审计、心跳任务摘要与可观测事件。
归档根固定为 storage/app/private/log-archives/v2/{YYYY-MM}/{batch-id}/。manifest、审计记录和报告必须能双向关联。已发布但未清除的批次只能继续清除或显式恢复,不能再导出第二份文件。
5. 定时任务与删除入口
生产只保留每分钟 schedule:run 作为系统入口,由 scheduler:heartbeat 在 15 分钟槽位内派发 log-archive 队列任务。每日 02:00 是默认触发时点,不是保留策略本身。任务运行使用现有 schedule_task_runs 互斥、超时和失败记录;V2 上线后还需写入批次 ID、候选数、发布数、删除数和滞留状态。
当前注册的任务仍会调用旧的 --execute 直删协议,因此它不能作为 V2 上线前的生产执行路径。实施前由运维在环境层面确认暂停该任务或仅运行 dry-run;本方案不授权在未完成预检时执行物理删除。
POST /v2/admin/log-cleanups 不得再直接删除数据库日志或重写当前 Laravel 日志文件。切换期将其改为只读预览与引导说明;V2 稳定后可改为受权限保护的“创建归档保留变更申请”,由数据策略和定时任务执行,不能绕过 manifest 与审计记录。
迁移顺序
- 盘点实库行数、最早时间、索引、增长率、现有归档文件和外部消费者;确认每个流的数据责任人与期限。
- 先冻结直接清理的物理删除能力,并从归档候选中排除
automation_logs、gateway_logs和所有未批准流。 - 新增事件字段、日志流目录与唯一写入器;按窗口回填
operation_logs到activity_logs,以事件映射和对账报告验证。 - 将管理端 API、管理员登录与业务 activity 查询切到
activity_logs,在完整观察期内验证新旧结果;随后停止并删除双写路径。 - 实现 V2 元数据、暂存发布、恢复、健康检查和冷热查询,先在单一低风险流灰度。
- 将每日任务切换到 V2;稳定后才允许按获批的流清退旧
operation_logs与旧归档格式。
验收与不可妥协项
- 每个可归档流有唯一写入真源、责任人和经批准的在线/归档期限;未分类或状态型数据不能被默认删除。
- 归档文件或 manifest 未校验发布时,源记录数不得减少。
- 同一 API/业务事件在切换后只出现一条逻辑记录;管理员查询无“表为空才回退”的隐式切源。
automation_logs删除或归档回归测试必须证明不会改变recordOnce()、hasRecord()、markExecuted()的幂等结果;默认验收是该表完全不参与归档。- 心跳重试、进程中断、磁盘满、哈希失败、清除中断和归档物篡改均可定位到批次并恢复或告警。
- 管理端直删路径、文件逐行重写和绕过归档的 Crontab 在切换完成后不存在。
- 每次迁移均运行受影响 PHP 测试、Pint、PHPStan 与
pnpm run docs:check;涉及管理端冷检索时追加端到端测试。
开放决策
- 普通访问/业务日志、消息日志、调度终态和插件诊断的在线与归档保留期限,需要数据责任人与合规负责人书面确认;当前 30/180 天只作为代码默认值,不作为批准政策。
gateway_logs是否可归档、可检索和保留多久,必须由财务和合规确认,确认前保持排除。- 外部工具、备份或人工脚本是否依赖现有
.log文件名及目录,需由运维盘点后才能切换 V2 目录。 - 冷检索的关键词范围和权限模型需在管理端设计评审中确定,首期不得提供任意文件下载或路径读取。
