修复上线之后:拿什么证明它真的生效

改完代码、跑通测试、部署上线——到这一步很多人就收工了。

但这个月有四次,“看起来修好了”和”真的修好了”之间差着一次取证:一次是日志里还有旧报错、一次是打包显示成功但关键步骤没执行、一次是审计报告把数据问题写成了代码 bug、还有一次是构建失败其实是本机网络的问题。

这篇记一下取证的具体做法。

一、先区分「我的网络」和「构建产物」

最省时间的一条原则:出问题先分清是环境还是产物。

  • 发布时本机 dnf5 --refresh repoquery 访问 packages.redhat.com 一直超时,看起来像构建挂了。但 dnf repolist 秒回、Copr API 那边构建状态是 SUCCESS——是本机到那个镜像的网络问题,不是产物问题。判据要以构建侧(CI 状态、Copr API)为准。
  • 联调环境里首页一直跳到升级页,一开始怀疑代码;核对后发现是数据库停在旧版本、代码是新版本。版本不一致本身就是”现象”,不是 bug。

这两件事的共同点:现象发生在你面前,原因不一定在你面前。先确认”我这边看到的”代表不代表”真实状态”。

二、A/B 对照:回退一次,让日志说话

线上刷了 7 条 stripslashes(): Passing null to parameter #1 ($string) of type string is deprecated ... on line 492。

改之前先做对照实验:

1
2
3
4
git stash          # 回退到修复前
请求 ?p=banlist # 日志里出现与线上一字不差的报错
git stash pop # 恢复修复
再请求 # 200,零 Deprecated / Warning / Notice

回退能复现、恢复能消失,这才是修复生效的证据。只看”我改完访问了一下是好的”,说服不了任何人——包括三个月后的自己。

顺带一个判断:那 7 条报错正好对应列表页里 7 条没有对应管理员的封禁记录。数字能对上,根因才算落地:管理员被删 → LEFT JOIN sb_admins 返回 admin_name = NULL → PHP 8.4+ 把 null 传给 string 参数就报 Deprecated。

三、复现条件要跟线上对齐

同一个报错,本地死活复现不出来,有两个原因,都值得记:

① 数据条件不具备。 本地备份库里没有”孤儿记录”(线上是后来删管理员才产生的)。做法是临时把一条封禁记录的 aid 指向一个不存在的 999999,请求后立刻还原——不是造一套假数据,而是只改触发条件那一个字段。

② 分支条件不一致。 第一次测试压根没走到第 492 行:本地 banlist.hideadminname=1,匿名请求走的是 admin=false 分支,根本不会调用 stripslashes。测试前先把触发条件对齐线上配置,否则你会得到一个”没问题”的假结论。

测完必须还原:数据改回原值、模板缓存清空、临时容器删掉。否则下次有人在这个环境上测试,会拿到你留下的脏数据。

四、日志要能证明「步骤真的执行了」

这一条最容易忽略:“打包成功”不等于”关键步骤执行了”。

一个跨平台打包项目里有个关键步骤——把第三方预编译库绑定的 glib 符号隔离掉,否则应用一加载就崩。但这步如果没执行,打包依然是绿的。

所以验收方式是回看日志确认证据:

  • Linux x64 与 arm64 各自出现 isolated sharp's bundled glib in 2 file(s)(提升副本与 .pnpm 副本各一),patchelf 0.19.2 安装成功;
  • FreeBSD 侧日志里没有这一步 —— 这是正常的:它走独立构建脚本、按平台直接跳过该修补,而且用的库是 WASM 实现、根本不链接 glib。

同一个判据在两个平台上结论相反,是因为平台分支不同。要求”两边日志一致”反而会误判。验收要按平台分别定判据,而不是套一个模板。

五、审计报告不能照单全收

有一份审计报告说:”12 条 supersede 边对应 6 条仍然 active,是写边时没回写 status”。

听起来是个明确的代码 bug。但核对之后发现两件事:

  • 更可能是手工 SQL、导入、或旧版本写入留下的数据,当前正常写入路径没有这个逻辑;
  • 而且审计快照是 308 条,手上库是 306 条 —— 两边根本不是同一份数据。

结论:不能照着审计的 SQL 批量执行。先备份,先查记录来源,只做”能确定来源”的那部分修复。

同一份报告里的另一条(提示词预算不足导致关键内容被裁)成立且更严重,值得优先修。

审计报告是一份线索,不是一份判决书。 它的价值在于指出”哪里可疑”,验证和定级还是要自己来。

六、发布前确认「在可安装产物里」

有一个功能做完验收,才发现改动只在本地分支:已经发布的二进制不包含新页面,装了新版的人根本拿不到这个能力。

修法是把分支合并、补文档、发下一个版本号,而不是覆盖已发布的版本。教训是:“我测过了”要加一句”在哪个产物里测的”。

另一个同类:部署完代码,页面还是旧的——因为 php-fpm 的 OPcache 设了 validate_timestamps=0,必须重启才加载新文件。这类”代码更新了但运行的是旧的”在 PHP/OPcache、Java 容器、Node 常驻进程里都会出现,验收要看运行时加载的版本,不是文件时间戳。

总结

“修好了”要有证据,证据要满足三条:

  1. 能复现也能消失——A/B 对照,回退一次让日志说话;
  2. 条件跟生产对齐——数据条件、分支条件、运行时版本;
  3. 证据指向那个步骤本身——不是”整体成功”,而是”这一步确实执行了”。

再加两条心态:

  • 审计报告是线索不是判决书,先看两边数据是不是同一份;
  • 现象在你面前,原因不一定在你面前——先分清是本机环境还是真实产物。

最后一句:上线不是终点,是验收的开始。