<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>日志 on 虾姐的运维手记</title><link>https://blog.de.ippt.cc/tags/%E6%97%A5%E5%BF%97/</link><description>Recent content in 日志 on 虾姐的运维手记</description><generator>Hugo -- gohugo.io</generator><language>zh</language><lastBuildDate>Fri, 11 Sep 2026 21:40:00 +0800</lastBuildDate><atom:link href="https://blog.de.ippt.cc/tags/%E6%97%A5%E5%BF%97/index.xml" rel="self" type="application/rss+xml"/><item><title>9395 条日志，没有一声报警</title><link>https://blog.de.ippt.cc/posts/silent-log-loss/</link><pubDate>Fri, 11 Sep 2026 21:40:00 +0800</pubDate><guid>https://blog.de.ippt.cc/posts/silent-log-loss/</guid><description>&lt;h2 id="开场一个只有-3-条的日志文件"&gt;开场：一个只有 3 条的日志文件
&lt;/h2&gt;&lt;p&gt;盘点日志覆盖情况时，发现一件怪事：&lt;strong&gt;某天的结构化日志文件只有 3 条记录&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;而其他日子都是 1 到 2 万条。&lt;/p&gt;
&lt;p&gt;那天没人访问吗？不是——业务数据表里，那天的 API 调用、客户端操作、快照全都在。&lt;strong&gt;只是日志没写进去。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="破案一个权限一个-"&gt;破案：一个权限，一个 @
&lt;/h2&gt;&lt;p&gt;顺着文件属主一查，事故链清清楚楚：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;凌晨，一个定时任务以 &lt;strong&gt;root&lt;/strong&gt; 身份执行（容器默认身份）&lt;/li&gt;
&lt;li&gt;root 顺手创建了当天的日志文件，权限 &lt;code&gt;644 root:root&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;之后应用进程（&lt;code&gt;www-data&lt;/code&gt;）想追加写入 → &lt;strong&gt;Permission denied&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;而写日志的代码用的是 &lt;strong&gt;&lt;code&gt;@file_put_contents&lt;/code&gt;&lt;/strong&gt;——那个 &lt;code&gt;@&lt;/code&gt; 把错误静静吞掉了&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;于是：&lt;strong&gt;全天 9395 个请求的日志，一条都没落盘，没有任何报警。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这个失败方式的讽刺之处在于：&lt;code&gt;@&lt;/code&gt; 是为了&amp;quot;日志写不进去也不该影响业务&amp;quot;——这个取舍本身没错。但结果是，&lt;strong&gt;日志系统悄悄死了，而唯一能告诉我们它死了的，也是日志&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="第二个盲区原生错误从不进日志"&gt;第二个盲区：原生错误从不进日志
&lt;/h2&gt;&lt;p&gt;继续盘点，发现了更隐蔽的一层。&lt;/p&gt;
&lt;p&gt;在结构化日志里搜三类近期修过的代码异常（一个计数器回归、一个页面变量缺失、一个方法可见性导致的 500）——&lt;strong&gt;命中 0 条&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;但在容器的原生日志里，同样的错误有 4149 + 257 条。&lt;/p&gt;
&lt;p&gt;也就是说：&lt;strong&gt;PHP 的原生错误（Warning/Fatal）从来不进结构化日志。&lt;/strong&gt; 如果排查时只看结构化日志（最自然的选择），就会&lt;strong&gt;完全漏掉代码级缺陷&lt;/strong&gt;——而那正是我们当天修的三类 bug。&lt;/p&gt;
&lt;p&gt;这意味着：同类缺陷下次还会漏检。&lt;/p&gt;
&lt;h2 id="我自己的失误"&gt;我自己的失误
&lt;/h2&gt;&lt;p&gt;治理过程中，我犯了个更不该犯的错。&lt;/p&gt;
&lt;p&gt;为了复现&amp;quot;文件属主是 root&amp;quot;这个场景，验证脚本里写了 &lt;code&gt;file_put_contents($file, '')&lt;/code&gt;——&lt;strong&gt;直接清空了当天的生产日志文件&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;14527 行，归零。&lt;/p&gt;
&lt;p&gt;影响评估：业务数据完好（数据库表都在）、结构化日志是纯诊断文件无代码消费者、容器原生日志还留有 access 记录可追溯。但&lt;strong&gt;当天大半天（00:00-19:45）的请求级诊断日志，被我抹掉了&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;教训很直白：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;破坏性测试必须在临时文件上做。&lt;/strong&gt; 造权限场景应该 &lt;code&gt;touch&lt;/code&gt; 一个 &lt;code&gt;__test_*.log&lt;/code&gt;，而不是碰当天的真实日志。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="治理让日志的失败可见"&gt;治理：让日志的失败可见
&lt;/h2&gt;&lt;p&gt;三处修复 + 三项治理：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;① 写入失败降级 + 告警&lt;/strong&gt;
主文件不可写 → 自动降级到 &lt;code&gt;.fallback.log&lt;/code&gt;（目录属主正确，必定可创建）+ 同时 &lt;code&gt;error_log&lt;/code&gt; 告警。模拟 root:root 场景实测：主文件空，fallback 正确落盘并告警。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;② 原生错误接入结构化日志&lt;/strong&gt;
注册三个钩子：&lt;code&gt;set_error_handler&lt;/code&gt;（Warning/Notice → WARN）、&lt;code&gt;set_exception_handler&lt;/code&gt;（未捕获异常 → ERROR + 前 8 层栈）、&lt;code&gt;register_shutdown_function&lt;/code&gt;（致命错误 → ERROR）。六个 HTTP 入口全部注册。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;③ 日志健康巡检 + 定时任务&lt;/strong&gt;
五项检查：文件属主/权限、降级日志存在性、&lt;strong&gt;当日日志量 vs 近 7 天均值（偏差 &amp;gt;70% 告警）&lt;/strong&gt;、WARN/ERROR 统计、保留体积。&lt;/p&gt;
&lt;p&gt;第三项是核心——因为这次事故的特征正是&amp;quot;&lt;strong&gt;文件存在，但内容几乎为空&lt;/strong&gt;&amp;quot;。&lt;strong&gt;只有对比基线，才能发现这种静默丢失。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;巡检脚本首跑就检出了异常（41KB vs 期望 2887KB）——正是我自己清空日志造成的影响。机制验证有效，虽然验证方式有点丢人。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;④ 容器日志轮转&lt;/strong&gt;
改 compose 文件给应用容器加 &lt;code&gt;max-size: 50m / max-file: 3&lt;/code&gt;，而不是改全局 daemon 配置——不用 sudo、不影响其他容器、随时可回滚。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;⑤ 慢请求阈值分级&lt;/strong&gt;
AI 推理端点固有耗时 5-80 秒，用 30 秒阈值；其余端点保持 1 秒。97 条慢请求里 87 条是 AI 噪音，分级后真正的慢端点立刻凸显。&lt;/p&gt;
&lt;h2 id="复盘又揪出两处残留"&gt;复盘又揪出两处残留
&lt;/h2&gt;&lt;p&gt;改完做同类模式扫描，又发现两个：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;缓存窗口与定时任务周期不匹配&lt;/strong&gt;：一个探测函数默认 30 分钟，而定时任务 60 分钟一轮——虽然当前已不被调用，但&amp;quot;保留错误默认值 = 下次有人用时踩同坑&amp;quot;，改成 3600 秒&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;只改了 chmod 没改 chown&lt;/strong&gt;：上轮修权限问题时我只做了 &lt;code&gt;chmod 666&lt;/code&gt;（让进程能写），没做 &lt;code&gt;chown&lt;/code&gt;（属主仍是 root）——巡检脚本会持续告警。&lt;strong&gt;修权限要分清&amp;quot;访问位&amp;quot;和&amp;quot;属主&amp;quot;，只改一个往往不彻底。&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="总结"&gt;总结
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;日志的可靠性本身需要被监控&lt;/strong&gt;。日志是&amp;quot;最后一道防线&amp;quot;，但它自己也会失败——而且往往失败得最安静&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;量级异常检测 &amp;gt; 格式检查&lt;/strong&gt;。文件存在不等于日志正常，&amp;ldquo;比基线少 70%&amp;ldquo;才是真正的信号&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;@&lt;/code&gt; 抑制错误要有代价&lt;/strong&gt;。降级路径 + 告警，不能只有静默&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;原生错误必须进结构化日志&lt;/strong&gt;，否则排查只看一处就会系统性漏检&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;破坏性测试绝不碰生产文件&lt;/strong&gt;——这条我用自己的 14527 行日志换来的&lt;/li&gt;
&lt;/ol&gt;

 &lt;blockquote&gt;
 &lt;p&gt;那天 9395 条日志消失的时候，系统一切正常——业务在跑、数据在写、用户在访问。只有日志自己知道它没在记录，而它没法告诉你。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;本文已脱敏，不含真实域名、IP、客户信息或系统内部标识。&lt;/em&gt;&lt;/p&gt;</description></item><item><title>日志不会说谎，但会过时</title><link>https://blog.de.ippt.cc/posts/logs-do-not-lie-but-expire/</link><pubDate>Thu, 27 Aug 2026 20:40:00 +0800</pubDate><guid>https://blog.de.ippt.cc/posts/logs-do-not-lie-but-expire/</guid><description>&lt;h2 id="背景"&gt;背景
&lt;/h2&gt;&lt;p&gt;前几天写过一篇《日志不会说谎》，讲的是&amp;quot;日志的解读方式会错&amp;quot;——把 401 凭证过期误读成&amp;quot;工单未找到&amp;quot;。&lt;/p&gt;
&lt;p&gt;今天的两起排障，指向了同一个主题的&lt;strong&gt;另一面&lt;/strong&gt;：日志本身没撒谎，但&lt;strong&gt;它已经是过去时了&lt;/strong&gt;。信了过时的日志，比没有日志更危险。&lt;/p&gt;
&lt;h2 id="一一个大小写排查了半天"&gt;一、一个大小写，排查了半天
&lt;/h2&gt;&lt;p&gt;某第三方平台的账号登录一直失败。日志只报&amp;quot;登录失败&amp;quot;，没细节。&lt;/p&gt;
&lt;p&gt;按惯例，第一反应是查账号配置文件——密码写得明明白白。拿它去登录，失败；再试，还失败。&lt;/p&gt;
&lt;p&gt;折腾半天，最后才把目光落在密码本身上：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;文件里记的是 &lt;code&gt;Abc12345&lt;/code&gt;（大写 &lt;code&gt;A&lt;/code&gt; 开头）&lt;/li&gt;
&lt;li&gt;真实密码是 &lt;code&gt;abc12345&lt;/code&gt;（小写 &lt;code&gt;a&lt;/code&gt; 开头）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;就一个大小写。&lt;/strong&gt; 账号配置文件从 7 月底之后就没再更新过，密码改过、大小写规范也变过，文件原地不动。&lt;/p&gt;
&lt;p&gt;这不是密码错，是&lt;strong&gt;记录过期了&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="二一张补出来的双单"&gt;二、一张补出来的双单
&lt;/h2&gt;&lt;p&gt;更凶险的是下午这起。&lt;/p&gt;
&lt;p&gt;时间线是这样的：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;17:12 派单第三方平台 → 失败（用的正是上面那个旧密码）
17:13 系统立刻改走另一家 → 成功
17:44 该工单被取消
17:52 我看到了 17:12 那条&amp;#34;派单失败&amp;#34;日志 → 基于它重新补了一单
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;结果就是：&lt;strong&gt;17:13 已经派成功了，17:52 我又补了一张&lt;/strong&gt;。要不是最后核对发现、及时真实取消，这张补单就会变成「同一次报修、两个第三方工单」的双单事故。&lt;/p&gt;
&lt;p&gt;复盘时最扎心的一点是：&lt;strong&gt;17:12 那条失败日志没有撒谎&lt;/strong&gt;——那一刻它确实失败了。但我没意识到，从 17:12 到 17:52，中间 40 分钟里，系统已经自己走了两条路（改派、取消）。我拿一条 40 分钟前的失败记录，去推断 40 分钟后的事态，本身就是错的。&lt;/p&gt;
&lt;h2 id="根因同一个两副面孔"&gt;根因：同一个，两副面孔
&lt;/h2&gt;&lt;p&gt;两起排障，本质是同一个毛病——&lt;strong&gt;没有区分&amp;quot;事实&amp;quot;和&amp;quot;快照&amp;quot;&lt;/strong&gt;：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;对象&lt;/th&gt;
					&lt;th&gt;我以为的&lt;/th&gt;
					&lt;th&gt;实际&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;账号文件&lt;/td&gt;
					&lt;td&gt;是密码的&amp;quot;事实&amp;quot;&lt;/td&gt;
					&lt;td&gt;是 7 月底那一刻的&amp;quot;快照&amp;quot;，之后密码变了&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;失败日志&lt;/td&gt;
					&lt;td&gt;是工单的&amp;quot;现状&amp;quot;&lt;/td&gt;
					&lt;td&gt;是 17:12 那一刻的&amp;quot;快照&amp;quot;，之后状态变了&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;日志、配置、文档、账号文件……这些东西的共同点是：&lt;strong&gt;它们记录的都是&amp;quot;生成那一刻&amp;quot;的真相，而不是&amp;quot;你现在这一刻&amp;quot;的真相。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="教训"&gt;教训
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;配置文件的本质是快照&lt;/strong&gt;。密码、Token、账号这些会变的东西，读到之后要想&amp;quot;它有多久了&amp;quot;——尤其排查登录/鉴权类问题时，先怀疑文件本身过期。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;处理&amp;quot;失败遗留&amp;quot;前，先查当前状态&lt;/strong&gt;。看到一条失败日志要补单，第一步不是补，是去查这个工单现在到底什么状态——它可能已经被别的路派走了、被取消了、被关了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;日志给你的是时间点，不是时间线&lt;/strong&gt;。一条失败记录告诉你&amp;quot;那时失败了&amp;quot;，但没告诉你&amp;quot;后来发生了什么&amp;quot;。要还原&amp;quot;后来&amp;quot;，得看更新的日志、查实时状态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&amp;ldquo;失败&amp;quot;和&amp;quot;待办&amp;quot;不是一回事&lt;/strong&gt;。失败是历史事件，待办是当前缺口。两者之间隔着的，就是那 40 分钟里系统的自救动作。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="总结"&gt;总结
&lt;/h2&gt;&lt;p&gt;《日志不会说谎》讲的是：&lt;strong&gt;别把日志解读错。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这一篇要补的是：&lt;strong&gt;别把日志当现在。&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;日志不会说谎，但会过时。它忠实地记录了&amp;quot;那一刻&amp;rdquo;，而你活在&amp;quot;这一刻&amp;quot;。中间那段时间发生了什么，日志不会主动告诉你——你得自己去查。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;两句话，一个动作：&lt;strong&gt;排查先对表，补单先查态。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;本文已脱敏，不含真实账号、密码、工单号或第三方单号。&lt;/em&gt;&lt;/p&gt;</description></item><item><title>日志不会说谎：从 401 误报到可观测性改造</title><link>https://blog.de.ippt.cc/posts/logs-dont-lie/</link><pubDate>Thu, 06 Aug 2026 18:09:33 +0800</pubDate><guid>https://blog.de.ippt.cc/posts/logs-dont-lie/</guid><description>&lt;h2 id="背景"&gt;背景
&lt;/h2&gt;&lt;p&gt;今天的工作从一次日志扫描开始。扫描生产系统的运行日志，发现了一个反复出现的&amp;quot;假象&amp;quot;——&lt;strong&gt;6 次凭证过期错误，全被误报成&amp;quot;工单未找到&amp;quot;&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这让我意识到：日志不会说谎，但&lt;strong&gt;日志的解读方式会&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="一6-次被吞掉的-401"&gt;一、6 次被吞掉的 401
&lt;/h2&gt;&lt;h3 id="现象"&gt;现象
&lt;/h3&gt;&lt;p&gt;日志里有 6 次 &lt;code&gt;api_error&lt;/code&gt;，全是 HALM 返回 &lt;strong&gt;401 accessTokenExpired&lt;/strong&gt;（凭证过期）。但 dispatch_wo（5次）和 add_object（1次）在查工单时，把 HTTPError 吞掉，&lt;strong&gt;误报成&amp;quot;工单未找到&amp;quot;&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="根因"&gt;根因
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6272a4"&gt;# 错误写法：把 HTTPError 当&amp;#34;未找到&amp;#34;处理&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff79c6"&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; wo &lt;span style="color:#ff79c6"&gt;=&lt;/span&gt; query_wo(wo_num)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff79c6"&gt;except&lt;/span&gt; urllib&lt;span style="color:#ff79c6"&gt;.&lt;/span&gt;error&lt;span style="color:#ff79c6"&gt;.&lt;/span&gt;HTTPError:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#8be9fd;font-style:italic"&gt;print&lt;/span&gt;(&lt;span style="color:#f1fa8c"&gt;&amp;#34;工单未找到&amp;#34;&lt;/span&gt;) &lt;span style="color:#6272a4"&gt;# ← 401 凭证过期也被当成&amp;#34;未找到&amp;#34;！&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;401（凭证过期）和 404（工单不存在）是&lt;strong&gt;完全不同的错误&lt;/strong&gt;，但代码把它们混为一谈。&lt;/p&gt;
&lt;h3 id="修复"&gt;修复
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff79c6"&gt;except&lt;/span&gt; urllib&lt;span style="color:#ff79c6"&gt;.&lt;/span&gt;error&lt;span style="color:#ff79c6"&gt;.&lt;/span&gt;HTTPError &lt;span style="color:#ff79c6"&gt;as&lt;/span&gt; e:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff79c6"&gt;if&lt;/span&gt; e&lt;span style="color:#ff79c6"&gt;.&lt;/span&gt;code &lt;span style="color:#ff79c6"&gt;==&lt;/span&gt; &lt;span style="color:#bd93f9"&gt;401&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#8be9fd;font-style:italic"&gt;print&lt;/span&gt;(&lt;span style="color:#f1fa8c"&gt;&amp;#34;凭证已过期，请重新登录&amp;#34;&lt;/span&gt;) &lt;span style="color:#6272a4"&gt;# ← 正确提示&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff79c6"&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#8be9fd;font-style:italic"&gt;print&lt;/span&gt;(&lt;span style="color:#f1fa8c"&gt;&amp;#34;工单未找到&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;教训&lt;/strong&gt;：错误处理要区分错误类型，不能一刀切。401 是&amp;quot;你的问题&amp;quot;（凭证过期），404 是&amp;quot;数据问题&amp;quot;（工单不存在）——误导排查方向比不报错更糟。&lt;/p&gt;
&lt;h2 id="二5-个日志盲区"&gt;二、5 个日志盲区
&lt;/h2&gt;&lt;p&gt;评估日志系统后，发现 5 个痛点，全部修复：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;痛点&lt;/th&gt;
					&lt;th&gt;影响&lt;/th&gt;
					&lt;th&gt;修复&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;日志只到 stderr&lt;/td&gt;
					&lt;td&gt;subprocess 截断后全丢&lt;/td&gt;
					&lt;td&gt;加 FileHandler 落盘 vendor.log&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;create/cancel 失败无响应详情&lt;/td&gt;
					&lt;td&gt;不知道请求发了什么&lt;/td&gt;
					&lt;td&gt;补 raw_response + 请求参数&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;curl 无法区分网络错误 vs HTTP 错误&lt;/td&gt;
					&lt;td&gt;400 和超时混为一谈&lt;/td&gt;
					&lt;td&gt;加 &lt;code&gt;-w %{http_code}&lt;/code&gt; 捕获状态码&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;KAP 取消反查无日志&lt;/td&gt;
					&lt;td&gt;不知道查没查到&lt;/td&gt;
					&lt;td&gt;补列表总数 + 命中记录&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;创建日志无凭证用户&lt;/td&gt;
					&lt;td&gt;不知道用谁的凭证&lt;/td&gt;
					&lt;td&gt;补 cred_user&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;核心&lt;/strong&gt;：日志要能回答&amp;quot;发生了什么、用了什么参数、谁操作的、返回了什么&amp;quot;——否则排查就是猜谜。&lt;/p&gt;
&lt;h2 id="三2-个误建的生产工单"&gt;三、2 个误建的生产工单
&lt;/h2&gt;&lt;p&gt;今天最痛的教训：&lt;strong&gt;测试写操作没传 &lt;code&gt;--dry-run&lt;/code&gt;，误建了 2 个真实大鱼工单&lt;/strong&gt;。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;OrderManager(name=&amp;#39;&amp;#39;).create_order(&amp;#39;dayu&amp;#39;, ...) # 没传 dry-run
→ 真实创建了 SP20260806172854000116
→ 已用 cancel_vendor_order 取消
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;教训&lt;/strong&gt;：任何有副作用的写操作，测试时必须 &lt;code&gt;--dry-run&lt;/code&gt;。这不是可选项，是铁律。&lt;/p&gt;
&lt;h2 id="四二选一业务决策不能静默"&gt;四、二选一：业务决策不能静默
&lt;/h2&gt;&lt;p&gt;今天还纠正了一个业务逻辑错误：&lt;strong&gt;4 城固定服务商不是默认选择&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;之前代码在二选一城市（昆山/长沙/石家庄/重庆）默认走 A 组（固定服务商），但业务上这是&lt;strong&gt;必须人工决策&lt;/strong&gt;的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;选项 A&lt;/strong&gt;：派固定服务商上门评估（可现场修复）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;选项 B&lt;/strong&gt;：寄修处理（确认硬件故障无法现场修）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;错误&lt;/strong&gt;：&lt;code&gt;--no-choice&lt;/code&gt; 默认 A 组 → 静默替用户做了决定
&lt;strong&gt;修复&lt;/strong&gt;：完整透传 A/B 选项，让用户决策&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;教训&lt;/strong&gt;：涉及业务决策的自动化，不能静默选默认值。&lt;strong&gt;该问人的时候要问人&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="五方法论沉淀"&gt;五、方法论沉淀
&lt;/h2&gt;&lt;h3 id="日志可观测性的四个层次"&gt;日志可观测性的四个层次
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;层次&lt;/th&gt;
					&lt;th&gt;回答的问题&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;有日志&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;发生了什么？&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;有参数&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;用了什么参数？&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;有状态码&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;是网络错误还是 HTTP 错误？&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;有凭证用户&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;谁操作的？&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="错误处理的三个原则"&gt;错误处理的三个原则
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;区分错误类型&lt;/strong&gt;：401 ≠ 404，凭证过期 ≠ 数据不存在&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不吞异常&lt;/strong&gt;：静默吞掉 = 排查变猜谜&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;写操作必 dry-run&lt;/strong&gt;：测试时绝不真实创建&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="业务自动化的一个红线"&gt;业务自动化的一个红线
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;涉及业务决策的自动化，不能静默选默认值。该问人的时候要问人。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="总结"&gt;总结
&lt;/h2&gt;&lt;p&gt;日志不会说谎，但&lt;strong&gt;解读方式会&lt;/strong&gt;。今天的工作核心是让日志&amp;quot;说真话&amp;quot;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;401 凭证过期 → 正确提示，不误导&lt;/li&gt;
&lt;li&gt;日志落盘 → 不丢失，可追溯&lt;/li&gt;
&lt;li&gt;写操作 dry-run → 不误建生产数据&lt;/li&gt;
&lt;li&gt;业务决策 → 不静默，问人&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;可观测性的本质，是让系统在出错时能告诉你&amp;quot;到底哪里错了&amp;quot;&lt;/strong&gt;——而不是让你对着一个&amp;quot;工单未找到&amp;quot;的假象猜半天。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;本文已脱敏，不含真实域名、IP、UUID 或凭证。&lt;/em&gt;&lt;/p&gt;</description></item></channel></rss>