# App/log/ — 日志子系统 > 两类日志:**持久化运行日志**(`sys_log`,落 NAND)与**控制台日志**(`console_log`,走串口)。 > 两者都遵循**单写者**模型,避免多任务交错输出。 ## 文件一览 | 文件 | 作用 | |---|---| | `sys_log.h` | **日志接口(平台无关)**:定长 64B 记录写入 `/log/run.log`·`selftest.log` | | `sys_log_mcu.c` | `sys_log` 的 MCU 端后端实现(NAND + FatFs) | | `console_log.c/h` | **控制台单写者日志**:多任务日志经队列汇入唯一写者,避免串口输出交错 | | `soe_rec.c/h` | **SOE 记录纯编解码**(B9):64B 定长记录(CP56Time2a 时标 + 类型 + 元件号 + 单调序号 + 值 + sum8 撕裂校验),零 I/O 零 OS 依赖,主机单测覆盖 | | `soe_store.c/h` | **SOE 落盘胶水**(B9):`/log/soe.log`,protectTask 投递(零文件系统)→ defaultTask 排空落盘 | ## 关键机制 ### syslog(运行/自检记录) - **唯一写者 = defaultTask**;其它任务只投递,不直接写盘 - **单记录定长 64 B**,循环写;待机页可显示记录 - **内核启动前的日志**走**无锁静态缓冲** `s_preboot[8]` 暂存(因为内核启动前不能建队列), 内核启动后由 `syslog_drain` 惰性创建真队列并排空 - ⚠️ 写盘必须校验 `f_write` 返回的**字节数**(该 FatFs 移植在簇分配失败时返回 `FR_OK` + 短写, 不报错误码);追加写必须 `FA_WRITE|FA_OPEN_APPEND`(`FA_OPEN_APPEND` 单独用**不含写位**) ### console_log(控制台单写者) - 多任务的生产者 → **单写者消费者**,消除任务间日志行交错 - 设计详见 `docs/子系统设计/控制台单写者日志设计说明.md` ### soe(SOE 事件顺序记录,B9) - **记录 = 64 B 定长二进制**(`soe_rec.h` 布局),落 `/log/soe.log`; 与 run.log 分文件——run.log 是文本记录、有 UI/主机读取方,二进制 SOE 混入会破坏读取器;分文件保持"定长 64B + 条数=fsize/64 + 满删重建 + FTP 下载 + 主机解码"同一套先例 - **生产者 = protectTask**(元件动作/返回、软压板投退、采样闭锁/恢复六类 边沿):`soe_store_post()` 纯 RAM(取时标 + 组记录 + 队列/待发环), **零 NAND I/O**;⚠️ **post 内禁 `DBG_*`**——会把 protectTask 静态最坏链 垫高一帧(实证 872→1192 B FAIL),丢弃留痕由 drain(defaultTask)差值上报 - **唯一写者 = defaultTask**:挂 `syslog_drain()` 尾部(继承其卷就绪门), 50 ms 节拍 → SOE 落盘延迟 ≤50 ms;写盘纪律全套照 syslog(卷锁 try / `FA_WRITE|FA_OPEN_APPEND` / 校验字节数 / 满删重建 / 失败留痕去重), **绝不把名字/参数类错误码归入卷损坏触发集** - **时标口径**:sys_clock 秒域时基 → CP56 ms 域 = 秒×1000(亚秒恒 0, 不造假);墙钟未建 → IV 位如实置位;seq 仅上电生命周期内单调 (容量重建不重置,仅复位重置——主机解码按 boot 段分段断言) - 主机侧:`test/host/soe/decode_soe.py` 解码 + 断言(校验和/seq 单调/ CP56↔epoch UTC 交叉);⚠️ 交叉校验用 `calendar.timegm`,不是本地时区的 `dt.timestamp()`(+08 机差 28800 s,首跑实证坑) ## 相关文档 - `docs/子系统设计/控制台单写者日志设计说明.md` - `docs/子系统设计/运行与自检记录设计说明.md` - FatFs 陷阱:`AGENTS.md`「平台约束」