Skip to content

日志与归档系统协同重构计划

背景

现行 db:archive-logs 对配置白名单执行 pt-archiver --file --purge。它有表白名单、路径校验、全局文件锁、每表 PID、执行报告、SHA-256 和 archive_audit_logs,但归档文件直接写入最终路径,源记录可在文件完成持久化前被清除;哈希和文件头只在命令结束后检查。该流程无法把“归档物已可靠落盘”作为物理删除的前置条件。

同步审查发现,operation_logsactivity_logs 正在双写,管理端 channel 依赖多张数据库表和 Laravel 文件日志,且管理员清理接口可绕过归档直接删除。automation_logs 是自动化幂等状态,gateway_logs 可能是支付证据,二者不能再被当作默认普通日志处理。本计划先收敛日志真源与分类,再实施 V2 归档。

本地归档根还保留了旧实现产生的 JSON/JSONL 元数据。当前清理器只匹配 .log,因此不会处理这些历史文件。它们不是本计划可直接删除的对象。

本计划的目标是将归档改造成可中断、可恢复、可校验的两阶段流程,同时保持普通日志与财务/支付/审计数据的边界。

目标

  1. 每一批源记录只有在对应归档物完成校验、持久化和发布后才能被物理删除。
  2. 中断、磁盘写入失败、重复触发和发布后删除失败均可通过持久化状态恢复,不静默遗漏数据。
  3. 归档物、审计记录、执行报告和恢复命令之间有一一对应、可验证的关联。
  4. 使用 Laravel 心跳中的每日归档任务;增加可供外部监控读取的健康检查,并保持 schedule:run 为唯一系统调度入口。
  5. 每个可归档日志流都有唯一在线真源、明确读模型和经过批准的保留策略;归档不再改变管理端结果的完整性语义。
  6. 移除管理端直接删除与文件重写旁路;automation_logs、财务/支付/审计数据和未批准流不参与通用归档。

非目标

  • 不改变普通日志的业务保留期限;当前默认 30 天源数据、180 天归档文件,任何期限调整须由数据责任人与合规负责人确认。
  • 不处理财务流水、账单、支付、支付回调、失败队列和归档审计表。
  • 不自动删除、迁移或伪造旧归档根中的 JSON/JSONL 元数据。
  • 不把归档文件目录配置为 NAS、外部挂载或后台可编辑路径。
  • 不在本计划内执行生产归档、OPTIMIZE TABLE、生产定时任务变更或历史数据恢复。
  • 不在未完成对账前删除 operation_logs,也不保留长期双写或“表为空才回退”的运行时兼容路径。

目标协议与边界

V2 归档状态机

每个表的每个 ID 分块作为一个独立归档物,状态只能按下列方向推进:

text
planned -> staging -> verified -> published -> purging -> purged
    \-> failed / needs_recovery
  1. planned:记录固定的表名、截止时间、ID 下限/上限和预期行数;仅允许白名单中的普通日志表。
  2. stagingpt-archiver 不带 --purge 写入 V2 根目录内的 .part 文件;同一批次绝不追加到既有最终文件。
  3. verified:流式校验 CSV 表头、行数、ID 边界、文件大小和 SHA-256;文件句柄 flush/fsync 完成后才可进入下一步。
  4. published.part 以同目录原子 rename 成最终 CSV,并以相同方式发布包含版本、表、ID 边界、截止时间、行数、大小和哈希的 manifest。
  5. purging:只按已发布 manifest 的固定表、截止时间和 ID 边界删除源记录;删除操作以小事务分块并记录实际删除数。
  6. purged:确认该选择范围内无剩余候选记录,写入最终审计、执行报告和事件日志。任一异常留下可诊断状态,不根据“命令退出”推断成功。

新目录固定为 storage/app/private/log-archives/v2/{YYYY-MM}/{batch-id}/。目录及文件名只由服务生成,禁止命令参数或管理端提交任意路径。仓库内未发现当前归档目录的读取调用方;上线前仍须由运维负责人确认没有仓库外消费者依赖旧目录或 .log 文件名。

日志真源与检索切换

  • activity_logs 是 API、认证与业务活动的目标唯一在线真源;新增事件 ID、流名、链路 ID 和发生时间后,用窗口化回填与对账替代当前 operation_logs 双写。
  • 管理端保留既有 channel 与在线响应结构,但 AdminLogV2QueryService 改为显式 source 映射,禁止以“表存在/有数据”隐式切源。归档历史通过 manifest 目录做受限冷热检索,不能暴露文件路径。
  • schedule_run_logs 的历史与 activity_logs cron 镜像必须能关联去重;schedule_task_runs 仅处理终态历史,queued/running 永远留在线。
  • automation_logs 永久移出通用归档与清理,gateway_logs 在财务/合规批准前移出默认白名单;消息和插件运行日志先完成数据分类再决定是否进入首批。
  • POST /v2/admin/log-cleanups 先停止其物理删除及 laravel.log 重写能力,后续只能创建受审计的保留变更申请;它不再是归档或清理执行入口。

数据与恢复契约

  • 仅允许 config/log_archive.php 的 8 张表;财务、支付和审计表继续在服务层拒绝。
  • 归档前通过 created_at < cutoff 与固定 ID 边界选择记录。实施前必须以实库 EXPLAIN 和索引实况确认每张表按 created_at 筛选的代价;缺失索引才新增迁移,不能凭计划猜测索引。
  • 新增迁移只追加字段或表。建议扩展 archive_audit_logs,为每个归档物记录协议版本、ID 边界、截止时间、预期/验证/删除行数、临时/最终/manifest 相对路径、哈希、大小和各阶段时间;历史行保持不改写。
  • 恢复命令只能按审计记录定位 V2 manifest,默认 dry-run,且必须显式确认后执行。它校验根目录白名单、文件大小、哈希、CSV 头和表结构;按主键只插入缺失记录,遇到同 ID 不同内容时失败并输出冲突清单,绝不覆盖现有记录。
  • 不为旧目录实现透明运行时兼容。旧产物先只读盘点、备份与分类;如需清理或迁移,另建经过授权的历史数据处置计划。

范围与验收

编号验收项
A18 张白名单表可按 30 天默认值和显式 --retain-days 生成 V2 归档物;受排除表始终在启动外部工具前失败。
A2任何 .part 写入、CSV/哈希校验或 manifest 发布失败时,源表记录数不减少。
A3已发布、未清除的批次重试时只进入清除和收尾,不重复导出、不追加表头、不产生第二份最终归档物。
A4清除过程中中断后能根据审计状态恢复,并准确报告已删、未删和异常记录;不把不完整批次标记为成功。
A5篡改、丢失、大小不符或路径越界的归档物无法恢复或清除,并产生可定位告警状态。
A6恢复 dry-run、成功恢复、重复恢复和 ID 冲突均有自动化测试;恢复不能写入非白名单表。
A7归档文件保留清理仅处理已验证的 V2 终态归档物;旧 JSON/JSONL 和孤立 .part 默认不删除,只告警和列入盘点。
A8Linux 演练验证 pt-archiver、凭据权限、并发、Laravel 心跳任务、健康检查和首轮报告;生产切换前不以本地静态测试代替。

实施步骤

Phase 0:确认边界与基线

编号工作项交付物验收
P0-0在生产变更单中确认暂停现有 --execute 直删任务或切为 dry-run;未完成预检不得执行物理删除。运维确认与任务状态证据V2 未发布期间不存在无 manifest 的生产删除。
P0-1在隔离或获授权的只读环境盘点 8 表的行数、最早时间、EXPLAIN、索引和增长速率。基线报告每张表的选择条件与索引选择有实证。
P0-2运维确认 30/180 天是否满足实际保留要求,并确认无外部程序消费旧目录、旧文件名或旧 manifest。已批准的政策与消费者清单未确认时不切换产物布局。
P0-3盘点旧归档根:按版本、日期、大小、哈希和可读性分类,单独备份清单。只读清单不删除、不移动、不修改旧文件。
P0-4明确日志表归档后不可更新的契约;不满足时改为记录精确主键列表或停止该表进入 V2。选择稳定性决策删除谓词不会误删后来变更的记录。
P0-5为所有管理端 channel 建立日志流目录,标出唯一写入真源、镜像、状态表、数据责任人、可归档资格与读模型。已批准的日志流清单不存在按表名猜测为普通日志的删除行为。
P0-6operation_logs/activity_logs 双写、调度镜像和管理员直删路径做行数、事件 ID、时间范围与调用面基线。对账基线与下线清单能量化迁移缺口并证明直删入口已冻结。

Phase 1:持久化归档协议

编号工作项交付物验收
P1-0新增日志流目录、ActivityEventWriteractivity_logs 追加字段;窗口化回填旧操作日志并做可重复对账。唯一写入器、映射迁移、对账报告迁移窗口内一个逻辑事件只保留一个可查询真源。
P1-1新增 V2 批次/归档物元数据迁移,保留现有 archive_audit_logs 历史;状态、边界、哈希与错误信息可索引查询。新增迁移、模型与状态约束不改写历史审计;状态转移非法时失败。
P1-2LogArchiveService 拆分为选择、暂存、校验发布、清除、收尾与恢复协调器;移除无调用方的旧直接 --file --purge 路径。小职责服务与命令编排生产运行时只有 V2 协议可被调用。
P1-3为 CSV 流实现写入后 flush/fsync、哈希/行数/ID 边界校验、原子 rename 和 manifest 原子发布。文件协议实现最终文件与 manifest 永不指向未校验内容。
P1-4pt-archiver 改为仅导出暂存文件;实现受 manifest 约束的分块清除和恢复扫描。受控清除器清除前不存在可绕过的发布校验。
P1-5将报告和逐事件日志改为引用 batch ID 与归档物 ID;敏感配置和凭据仍不写入报告。报告契约任一归档物可从报告反查审计和 manifest。

Phase 2:恢复、清理与可观测性

编号工作项交付物验收
P2-1增加受确认的恢复命令及 dry-run,封装 manifest 校验、逐行导入、ID 冲突检测和结果审计。恢复服务、命令与测试恢复不覆盖现有数据,篡改文件必被拒绝。
P2-2增加只读健康检查:最近成功批次、滞留状态、候选积压、未处理 .part 与文件保留到期情况。JSON 健康检查输出监控无需读取归档内容或凭据即可发现失败。
P2-3重写 V2 清理器:只清理 manifest 校验通过且已过政策期限的终态归档物;失败或未知文件隔离告警。保留清理实现不再仅按扩展名递归删除。
P2-4更新运维真源,补充 V2 目录、状态含义、健康检查、恢复演练与旧文件处置边界。更新后的参考资料不再宣称中断时必然不会遗漏。
P2-5将管理端 channel 查询切到显式真源映射,增加受限冷热检索、manifest 来源标识与跨边界去重。查询服务、接口契约、管理端交互不再因表是否有数据回退,归档不可用可见。
P2-6删除管理端直接物理清理与 Laravel 文件逐行重写;保留只读容量预览和受审计的保留变更申请。API/权限调整与回归任意删除均由归档协议或独立文件轮转策略执行。

Phase 3:验证、灰度与切换

编号工作项交付物验收
P3-1增加测试替身,模拟外部归档成功、写满磁盘、哈希失败、rename 失败、进程中断、清除中断和 PID/全局锁冲突。Feature/Unit 回归测试A1 至 A7 自动化覆盖。
P3-2在 Linux 隔离环境对小表执行 V2 dry-run、暂存、发布、清除、恢复和篡改拒绝演练。演练报告文件/审计/源表行数逐项一致。
P3-3生产确认每分钟 schedule:run 与默认队列消费正常,完成预检后仅灰度 gateway_logs;观察完整周期与健康检查,再逐表放开。变更单、首轮报告未出现滞留、重复或缺失,且运维确认。
P3-4V2 全量稳定后,保留 log-archive 心跳任务作为唯一归档入口;移除旧的直接归档 Crontab。运维配置与验收记录至少连续 7 天健康检查正常。
P3-5以低风险、已批准日志流灰度冷热检索;确认后再迁移 operation_logs 历史并清除双写代码。灰度报告与下线变更十个 channel 的在线行为和归档边界符合规格。

质量门禁与测试矩阵

场景最低验证
选择8 表白名单、排除表、保留天数覆盖、空候选、不同 ID 分块与真实索引 EXPLAIN
文件协议.part 不可见、fsync 后校验、原子发布、重复运行、重复表头、哈希/大小/头部失败。
清除发布前零删除、精确 ID/截止时间约束、部分删除中断、重试幂等、全局锁和每表 PID 冲突。
恢复dry-run、成功导入、重复导入、ID 内容冲突、路径越界、哈希篡改、非白名单表。
运维pt-archiver、不可读凭据、不可写目录、过期成功批次、滞留批次和心跳任务未执行。
回归php artisan test tests/Feature/DatabaseArchiveLogsCommandTest.php 及新增恢复/健康检查测试、Pint、PHPStan、pnpm run docs:check
日志迁移OperationLogServiceLogOperationAdminLogV2QueryServiceScheduleRunLogService 与管理端日志 API 的定向测试;双写回填、去重、冷热边界和直删拒绝。
状态保护AutomationLog 幂等、ScheduleTaskRunRepository::activeRunForTask() 与支付网关排除的定向回归。

上线、监控与回滚

  1. 上线前完成数据库备份可恢复性确认、磁盘容量评估、归档账号最小权限复核和 Linux 隔离演练。
  2. 新迁移仅新增;旧归档记录和文件不改写。切换前移除旧的直接归档 Crontab,确认不存在 stagingpublishedpurging 等非终态 V2 批次。
  3. 灰度期间,每轮比对候选行数、验证行数、实际删除数、最终文件哈希和 manifest;任一不一致立即停止后续表。
  4. 回滚只移除 log-archive 任务注册并停止后续批次。已经 published 的归档物不得删除;已进入 purging 的批次必须用 V2 恢复协调器收敛到终态,不能恢复旧的直接 --purge 触发方式。
  5. 健康检查应告警:超过约定周期无成功批次、非终态批次超时、候选积压持续增长、文件/manifest 校验失败和存储空间低于阈值。具体阈值由运维确定,不在代码中硬编码。

进度

  • [x] 完成当前实现、调度、配置、文档与本地归档根的只读审计。
  • [x] 建立 V2 两阶段协议、恢复与灰度的实施方案。
  • [x] 将现行归档命令接入 log-archive 心跳任务:每日 02:00 入队、显式执行、不可手动触发,并完成定向回归与静态检查。
  • [x] 完成日志写入、管理端检索、调度镜像、直接清理与归档白名单的静态协同审查。
  • [x] 固化日志流目录、唯一真源、冷热检索和状态表保护的目标方案。
  • [ ] Phase 0:等待数据保留责任人、运维及外部消费者确认。
  • [ ] Phase 1:等待方案评审通过后开始实现。
  • [ ] Phase 2:等待 V2 归档物协议可用后开始实现。
  • [ ] Phase 3:等待隔离 Linux 演练与生产变更授权。

决策日志

日期决策原因
2026-08-01采用“暂存、校验发布、再清除”的 V2 协议当前直接 --file --purge 不能把持久化校验作为删除前置条件。
2026-08-01V2 使用独立目录与 manifest,不为旧目录保留运行时兼容层避免新旧产物混写或误判;旧文件先只读盘点。
2026-08-01将归档接入 Laravel 心跳任务用户要求统一使用定时任务;每分钟 schedule:run 已是生产唯一调度入口,任务运行记录、队列锁与失败状态可复用。
2026-08-01将归档任务限制为 1 小时、锁定 61 分钟当前数据库队列的 retry_after 为 3900 秒;不得通过增大任务超时破坏现有任务的可见性与防重复契约。
2026-08-01不在方案中变更 30/180 天保留参数保留期限属于数据政策,应由责任人确认而非由技术重构隐式改变。
2026-08-01activity_logs 作为访问、认证与业务活动的目标唯一真源当前双写与查询回退无法证明完整性;迁移对账后删除旧写入路径。
2026-08-01automation_logs 不进入通用归档,gateway_logs 暂停默认归档前者承担幂等控制,后者关联支付网关证据,均不能按普通日志期限猜测删除。
2026-08-01管理端直接清理改为受审计保留变更流程当前数据库删除和日志文件重写绕过归档物、manifest、恢复与审计。

基于 AGPL-3.0-or-later 发布