Files
efi-kernel/文档/05-日志与排障.md
lou 53e7d1e5f2 底座文档 11 份 + 修掉"同锁调用互相排队、双双卡死"
文档/ (2026-09-16; 老板定调: 这是 agent 底座, 所以必须扎实, 现在越扎实以后开发越简单)
  00-索引           文档地图 + 30 秒概念速查 + 三条命令跑起来 + 事实源优先级
  01-快速上手       体检 -> 建库 -> 起内核 -> 起驱动 -> 收工, 全带实测输出; 第一次最易踩的四个坑
  02-写一个驱动     五分钟最小驱动 / 形态选择 / 能碰哪些表 / 汇报与调用两份模板 / 交付检查表
  03-命令手册       两层每条命令 + 日志选项 + 退出码约定 + --json 样例 + 日常十条
  04-契约与调用     一次调用的完整生命周期 / 六条仲裁 / 锁与按需拉起 / 排障表
  05-日志与排障     三条道怎么读 + "症状->判据->处置"总表 + 断电收尸语义
  06-架构与不变量   分层 / 14 条硬不变量 / 主流程表 / 双真相 / 为什么故意不做 / 已知薄弱点
  07-模块与接口     逐模块职责与公开接口 + "想改 X -> 动哪几处"连带清单
  08-数据模型       8 张表逐字段 (谁写谁读) + events.kind 字典 + 状态机 + 快照 + 排查 SQL
  09-扩展指南       六个配方 (加子命令/加字段/加表/加日志来源/加自测/改判定) + 同步清单
  10-验收与质量门   四道门 + 五份自测明细 + pyright 严格档 + 26 条已知坑总表 + 发布 checklist
  规矩: 不重复设计文档 / 每条命令实测过再写 (含 jq 表达式) / 代码>设计>文档 的事实源优先级 /
        改代码必须同步文档 (清单在 09 末尾) / 暂时没做到的事写成"已知边界"不含糊过去

修复: 同锁串行化原来是死的 (实测抓到的真缺陷)
  旧行为: db.领调用 只领 pending (waiting 没人再碰) + db.同锁在跑 把 waiting 也算"占着锁"
          -> 同一把锁上两条请求互相排队, 双双停在 waiting 谁也不跑 (实测 id 16/17);
             而 收权超时 只收 running -> 排队连超时都没有 = 死锁
  修法:   ① db.领调用 的 SQL 改 state IN ('pending','waiting') -- 每轮把排队的领回来重判, 锁一空就推进
          ② db.同锁在跑 只认 state='running' (排队的还没拿到锁, 不挡人)
          ③ 内核.转发调用 waiting 分支补 deadline (排队也立期限); 内核.收权超时 遍历 running + waiting
          ④ 抽出 内核.期限文本() 统一算 deadline
  实测:   两条同锁调用串行跑完 (19.started_at == 18.finished_at); 排队者超时被收权 (events 有记录)
  回归:   自测db.py 调用组 +4 条断言 (waiting 不算占着锁 / waiting 会被重新领 / ...);
          去掉一条依赖生产库全局计数的脆弱断言

其它: 内核 与 引导器 的 用法() 末尾加文档指引
验收: uvx pyright 0 errors / 0 warnings; 五份自测全过 (进程/内核 58/配置/db/日志 86);
      试跑引导器.py PASS 11 / FAIL 0 / 残留无; 残留进程 0
2026-09-16 21:13:31 +08:00

9.0 KiB
Raw Permalink Blame History

05 · 日志与排障

出事先看四条日志 --内核(内核怎么想的)、内核.out.log(命令输出与崩溃原文)、 日志 <驱动>(驱动说了什么)、事件 -n 50(谁在什么时候干了什么)。这份文档先讲三条道怎么读, 再给一张"症状 → 判据 → 处置"的总表。

1. 三条道(一个文件只装一种内容)

文件 里面是什么 从哪看
内核/logs/内核.log 只有结构化日志行(内核自己写) 内核/内核.py 日志 --内核 / UEFI.boot.py 内核 日志
内核/logs/内核.out.log 内核进程的 stdout/stderr 原始流:命令输出(表格/JSON+ 未捕获的崩溃原文 内核 日志 --输出
内核/logs/引导器.log 引导器的动作:开始 / 收工 / 移交 / 内核启停 / 所有 WARN UEFI.boot.py 日志
驱动/<名>/logs/<名>.log 驱动的 stdout/stderr 原样(内核不解析,每轮启动前插一条分隔头) 内核/内核.py 日志 <名>

行格式(内核/引导器那份):

2026-09-16T21:08:35+08:00 INFO  [内核] 常驻调度启动 pid=26456
└── 时刻(带时区)          └级别 └来源  └内容(不截断)

为什么分三条:以前命令输出跟日志行挤在一个文件里(引导器把内核 stdout 一起重定向进去了), 想按级别筛一条都做不到。现在日志行归内核自己写、stdio 归 .out.log,两边都干净。 档案(修前的脏文件)在 归档/20260916-日志系统重做前/

2. 级别、门槛、轮转

配置键(环境.efi.json 默认 行为
门槛 log_level INFO 低于它的不写DEBUG 不落盘);空串 = 全写
单份上限 log_max_mb 5 超过就轮转成 .10 = 不轮转
保留份数 log_keep 3 留几份历史(.1/.2/.3);0 = 不轮转
默认行数 log_lines 200 日志 命令不写 -n 时给几行
  • 轮转只在拉起进程之前做(运行中改名会让进程继续写老 inode = 日志丢)。内核拉驱动前、 引导器拉内核前各轮转一次;引导器每次跑也轮转自己的。
  • 读的时候把历史份一起算-n 200 拿到的是"跨轮转的最近 200 行"。
  • 驱动日志是别人的原始输出--级别 只能靠字样(含 ERROR/Traceback/失败 → ERRORWARN/警告/超时/retry → WARN,其余 INFO)。这一点必须记住:猜的,不是规范
./.venv/bin/python 内核/内核.py 日志 --内核 --级别 WARN -n 50     # 内核只说警告以上
./.venv/bin/python 内核/内核.py 日志 样板常驻 -g 心跳 -n 20        # 只看含"心跳"的行
./.venv/bin/python 内核/内核.py 日志 --全部 -n 20                # 所有驱动各一段
./.venv/bin/python 内核/内核.py 日志 --json | jq -r '.["消息"]'   # 机器读(JSON Lines)
python3 UEFI.boot.py 日志 --级别 WARN -n 30                     # 引导器自己的动作

3. 现场状态从哪看(判活一律回 /proc

./.venv/bin/python 内核/内核.py 状态                # 全部驱动: 状态/PID/运行时长/配置一致性
./.venv/bin/python 内核/内核.py 状态 样板常驻 --json # 单驱动详情: 命令行/内存/CPU/重启次数/契约
python3 UEFI.boot.py 内核 状态                    # 内核自己: pid/启动时刻/今日次数/未收尾台账

库里记的 pid 只是账:真正判活用 /proc/<pid>/cmdline 校验(pid 会被系统复用 —— 宁可不杀, 不可误杀)。状态 里若写"现判 X 而库里记的是 Y",以现场为准

4. 症状 → 判据 → 处置(总表)

环境 / 引导器

症状 判据 处置
体检不过: venv 不健康 环境 --json 的 3 项详情(pyvenv.cfg 里的版本/路径与实际不符) python3 UEFI.boot.py 环境 重建
包缺失 第 4 项列出缺哪个(必需项缺才阻塞) python3 UEFI.boot.py 包 安装
PG 那项 WARN 第 6 项 pg_ok=false(引导器自己的命令不因此失败 查 PG 起没起、socket 路径对不对(环境.efi.jsondb.host
内核命令报"连不上 PG" 内核不降级(它的内存就是 PG 同上;PG 恢复后 内核 重启

内核进程

症状 判据 处置
启动 后 0.6s 就退出 [守护] 启动后 0.6s 就退出 —— 内核入口是空文件 / 缺包 内核 日志 --级别 ERROR;跑 环境 重建 + 包 安装
ERROR 已经有一个内核在跑了 (抢不到调度锁) PG 会话级咨询锁(0x65666901)被占 内核 状态 看是谁;要重来用 内核 重启
内核反复重连 PG 日志里 重连 PG 失败 (第 N/10 次) 查 PG;连续 10 次它会退出,让引导器看门狗发现
停止时报"还有 N 个没停掉" /proc 复查不为空 逐个看 状态试跑引导器.py 的残留检查也能抓

驱动

症状 判据 处置
无效 (invalid),说明写 协议版本不支持 配置.efi.jsonefi 不是内核支持的版本 改成 1
无效runtime 只能是 python 或 exec 字段拼错或值不对 改配置
无效入口路径越界 / 入口文件不存在 entry 用了 ..、绝对路径、或文件真不在 改成驱动根内的相对路径
无效驱动名重复 两个文件夹声明了同一个 name name(先到的先生效)
无效契约无人提供 它的 needs 没有任何驱动的 provides 对上 补提供方,或去掉这个 needs
无效依赖成环 (契约链: A -> B -> A) 契约依赖成环 拆环(扫描期就能看到)
失败 (failed)last_error 里有"日志末尾" 拉起后秒退;尾部只算本次启动的输出 日志 <名> -n 50 看它临死说了什么;常驻驱动建议手动 python3 <入口> 跑一遍
崩了 (crashed) 旧状态说在跑但 /proc/<pid> 没了(断电/被杀) restart / autostart;想让它自己回来就配 restart=on-failure
挂起 (zombie) 进程已死、父进程没回收 内核会自己回收;状态 如实报 崩了
列表里显示 待重启 boot_hash != config_hash(配置改过,进程还用旧配置跑) 内核/内核.py 重启 <名>
改了配置/代码没生效 同上(配置有指纹) 重启;改代码同理

调用 / 锁

症状 判据 处置
请求一直 pending 常驻内核不在跑 python3 UEFI.boot.py 内核 启动 --守护
denied calls.error 写明哪一条(越权/成环/没人提供/起不来) 04-契约与调用.md 第 7 节
waiting 同一 lock_keyrunning 正常排队;等对方 done 会自动推进;超时由内核收权(默认 60s)
timeout 过了 deadline 查提供方为什么慢;waiting 也可能超时(排队没等到头)
事件里 依赖失效: 上游不在跑 … 上游状态是 崩了/失败只是没启动不算 起上游,或查上游为什么崩

日志本身

症状 判据 处置
内核 日志 是空的 你看的是结构化日志,内核刚起还没说什么 内核 日志 --输出(命令输出/崩溃原文在那份里)
日志文件几十 MB 轮转没生效(log_max_mb 被设成 0?) 环境.efi.json;下一轮启动会自动轮转
驱动日志里几轮输出连成一片 那是上次启动的输出(分隔头之前) ==== 启动 … ==== 分隔头定位这一轮
事件表被刷满 某驱动汇报太密 让它的上报间隔变大(样板是 30s 一条)

5. 断电之后(本机 22:30 断电,这是必做项)

来电后按顺序:

python3 UEFI.boot.py --check                  # 1 环境 (venv/包/PG 都可能被动过)
python3 UEFI.boot.py 内核 启动 --守护           # 2 内核一启动就先"收尸"
./.venv/bin/python 内核/内核.py 状态            # 3 看谁被认领 / 谁判"崩了"
./.venv/bin/python 内核/内核.py 事件 -n 20      # 4 时间线: 最后一条 heartbeat 就是死的那一刻

收尸语义(设计 02 第 5 节):

库里的状态 /proc 实际 内核判成 动作
running 有 pid 在且 cmdline 匹配 running 认领(不重起),驱动不受内核生死影响
running 有 pid 没了 crashed exit 事件;autostart=true 才拉起
running 有 pid 在但 cmdline 不匹配 exited 清 pid不杀pid 被复用)
stopped / 无记录 stopped
其它状态 在且匹配 running "认领回来"(有人绕过内核对它做了什么)

内核自己死了不连累驱动(驱动是独立会话,start_new_session)—— "内核可以随时死"是设计而不是缺陷。