Files
STM32F4-Base/App/log/sys_log.h
T
wandering1 5a806d8a26 fix(log,net): 处置主机单测照出的 10 条现状,撤销 1 条误报
用户裁定「全部修」。第 1 轮修复跨三名队友、写范围互斥、零越界;
11 条登记现状 → 10 条已处置 + 1 条撤销(非阻塞 recv 「看不见 EOF」是误报:
net_recv_locked:1885 的非阻塞分支已把 CLOSED||session_dead 判成 EOF(0),
动手前读码才发现,误报比漏报更贵)。

存储与日志(App/log/sys_log_mcu.c、soe_store.c、cfg_file_store_mcu.c、sys_log.h)
  A 卷忙整批丢日志:批量路径补 rc==-2 分支整批回投(不计故障/不打日志/
    保序);此前一批最多 8 条既不落盘也不回投,而文档写着「条目不丢」
  B rc==-1 文案与行为相反:改 write fail, requeue + 正常→故障节流
  C 删永不可达的截断支;容量契约写死 sys_log.h(不足一律 -1、不写调用方字节)
  D 自愈触发集违规(比原报告更严重):新增代码侧唯一入口 s_is_selfheal_code,
    触发集收敛为 {DISK_ERR,INT_ERR,NO_FILESYSTEM,DENIED},移出名字/参数类码——
    旧集合在 8.3 同名同因下每轮删一次自己的历史日志且永不自愈,正是存储架构
    §10.1 第 10 条严禁的归集;soe_store 侧同源取更保守子集(打开失败绝不 unlink)
  E 注释里的 FR_NO_SYSTEM 不存在,改 FR_NOT_READY / FR_NO_FILESYSTEM
  F syslog_post 满态留痕节流(A 修好后「队列常驻满」成必然场景,不修等于用
    新刷屏换掉静默丢);丢弃语义一字未改

NET(net_socket.c/.h、net_types.h、net_socket_sim.c、lftpd.c)
  IP 字节序统一到 POSIX 网络序:改 net_inet_addr(&s_addr[0]==第一段)+ 出向
    IP 数组按内存字节顺位拆装(进与出同时换族→芯片侧数组净效果不变,550h 钉住)
    + 模拟器逐字同步;ntoa 函数体未动,只改对它注释里取了废弃族值的例子——
    那正是两套约定长期共存不被发现的原因。lftpd 两处算术核对确认无需改
  UDP 绕过 netTask:两入口加执行任务上下文守卫,新码 NET_ERR_NOT_IN_NETTASK
    (-12,重试永远无意义);结论口径=危险面已结构性关掉、串行化改造仍未做。
    附带关掉第二条更隐蔽的路:非 netTask 自旋 recvfrom 期间控制块被 close 并
    把同一 sockfd 分给新会话→旧自旋者消费新会话数据报

控制台(console_log.c/.h):冷却改独立 s_con_cooling 标志(旧写法在 tick==0 那
  拍冷却等于没记);con_append 的判空位+推进 head+整行拷贝收进同一临界区(旧
  为区外 check-then-act,环 >95% 满时可静默覆盖且既不打 [ERR] 也不计 dropped);
  Instance==NULL 与僵尸通道分诊(未初始化绝不 reinit);三本账分离 + 新读口
  dbg_console_tx_timeout();[ERR] 行改为发成功才销账

测试:net_socket 452、console_log 127、sys_log_mcu 104、soe_store 27、
cfg_file_store_mcu 34;回归 net_listener 34 / net_events 34 / net_pending 43 /
net_select 22 / lftpd 35 / lftpd_inet 28 / lftpd_io 33 / iec104_serv 28 /
nand_port 149 / vol_lock 33 / soe 32 / cfg_file 151 全绿。原「按现状钉住」的
用例全部翻正向;console_log 用「逐条退回旧实现」做变异自证,失败集合单调增长
6→11→18→22(把一条本来空转的断言逼了出来)。
游标抬升:TC-NETSOCK-545→560、TC-SYSLOG-100→107、TC-CONLOG-135→167。

文档:控制台设计说明 §4.1 口径与 §13-O2 销项 + v1.5;运行与自检记录 §4.4/§4.5
四条;存储架构 §10.1 第 10 条推论(指向 s_is_selfheal_code);TRAP-NET-21 后续
条目;BSD 指南新增 §9.0 s_addr 唯一约定;覆盖率口径 §9「第 1 轮修复处置」表 +
后续项 5 条 + 台架复验 7 条 + 新坑 8 条 + 变更记录。

后续项(本轮刻意不做,理由见 §9):UDP 改走 netMsgQueue(需 per-task 结果槽,
net_msg_t 按值拷贝、禁止加回传字段,且要真机回归);sim_syslog.c 同源不可达
支(本轮编译验证不到,不交没编译过的改动);syslog_post 丢弃计数;
dbg_console_tx_timeout() 读端接线(STALL 行是被解析的证据行,加字段要连解析端
与台架判据一起改);104 来源地址日志(现网无输出点)。

gate: @build 0 errors 0 warnings(Code=213720 RO=18860 RW=1204 ZI=131500)。
pre-commit run --all-files -> 39 hooks, 36 Passed; failed = mem-numbers +
mem-budget(受管快照与本树首次构建产物的漂移,非本批增量)+ id-check(已修,
现 0 台账外新账,可判定 84.63% > 基线 82.6%)。实测水位 RW_IRAM1 57.4%、
RW_CCM 87.9%,均低于 92% 红线;RAM/Flash 本批增量为数个 static 变量。
test-coverage / cppcheck / coding-standard / bsp-err / layer-deps / doc-links
全 Passed;全量覆盖率 33/33 套件、line 89.3%、func 92.3%、逐文件下降 0。
按仓内分支约定 --no-verify 提交,台账 --write 回填留待主工作树合并后。
2026-10-24 19:43:56 +08:00

165 lines
8.3 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称:System Log — 运行/自检记录引擎(双端共享核心)
* 模块功能:定长 64B 文本日志记录的公共 API、格式常量与行拆分;
* MCU 后端 = FatFS /log 目录日志文件(sys_log_mcu.c),模拟器后端 = 宿主文件(sim_syslog.c)
* 适用平台:STM32F407ZGTx + PC 模拟器(共享头,无 OS/LVGL 依赖)
* 作者:王建锋
* 创建日期:2026-09-14
* 修改记录:
* v1.0 2026-09-14 王建锋 创建初始版本(设计见 docs/子系统设计/运行与自检记录设计说明.md §4)
* v1.1 2026-10-24 王建锋 仅契约注释更正,与 App/log/sys_log_mcu.c 的 C/F 两项
* 整改同步(无签名、无行为改动):
* syslog_get_record 写死容量要求(ts_len > SYSLOG_TS_LEN、
* desc_len > SYSLOG_DESC_MAX)并明确"容量不足一律 -1、
* 不截断、不写调用方缓冲"——旧注释像是"给少了会被截断",
* 实际是整条读不到;syslog_post 写明必须假设丢弃存在、
* 且丢弃留痕已按通道节流(节流后无丢弃计数)
*/
#ifndef SYS_LOG_H
#define SYS_LOG_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
/*
* 日志 ID:决定落盘文件与容量上限
*/
typedef enum {
SYSLOG_RUN = 0u, /* 运行记录 /log/run.log:上电/重启事件 */
SYSLOG_SELFTEST, /* 自检记录 /log/selftest.log:开机自检故障(仅故障才写) */
SYSLOG_COUNT /* 哨兵值,非有效 ID */
} syslog_id_t;
/*
* 记录格式(定长 64B,一行一条;设计文档 §4.2):
* [0..18] "YYYY-MM-DD HH:MM:SS"(ISO 8601 空格变体,19 字符;墙钟无效 = "1970-01-01 08:00:00")
* [19] ' '
* [20..29] epoch 秒 ASCII "%010u"(机器解析/上电重启分类用;墙钟无效 = "0000000000")
* [30] ' '
* [31..58] 描述 UTF-8(≤28B = 9 汉字),不足补 '\0'
* [59..63] '\0' 填充
*/
#define SYSLOG_REC_SIZE 64u /* 单条记录字节数(定长) */
#define SYSLOG_TS_LEN 19u /* 时间戳字段长度 "YYYY-MM-DD HH:MM:SS" */
#define SYSLOG_EPOCH_OFFS 20u /* epoch ASCII 字段偏移 */
#define SYSLOG_EPOCH_LEN 10u /* epoch ASCII 字段长度(2026 年 Unix 秒 = 10 位) */
#define SYSLOG_DESC_OFFS 31u /* 描述字段偏移 */
#define SYSLOG_DESC_MAX 28u /* 描述最大字节数(受框架行缓冲 CP_ROW_BUF_LEN=32 约束) */
/*
* 容量上限(条数;满时删除重建,设计文档 §4.3)
*/
#define SYSLOG_RUN_MAX 1024u /* /log/run.log:64KB */
#define SYSLOG_SELFTEST_MAX 512u /* /log/selftest.log:32KB */
/*
* 上电/重启分类窗口(秒):run.log 末条 epoch 距 now < 该值 → "reboot",否则 "power on"
*/
#define SYSLOG_REBOOT_WINDOW_S 600u
/*
* 函数功能:投递一条日志(任意上下文;非阻塞,队列满则丢弃)
* 入口参数:id - 日志 ID syslog_id_t
* desc - 描述串(UTF-8,≤SYSLOG_DESC_MAX 字节;超长截断) const char *
* 返回值:无
* 限定条件:MCU 端 FreeRTOS 启动前调用走静态待发数组(由 defaultTask 挂载后落盘)
* 函数说明:调用方**必须假设投递可能被丢弃**——MCU 后端(sys_log_mcu.c)队列 8 槽、
* 启动前待发数组 8 槽,卷被 ftpTask 长期占用时 defaultTask 会把整批回投
* 队列(队列因此可长时间处于满态),此时本函数直接丢弃该条,并只在
* "转入故障"的那一次留痕(丢弃留痕按正常→故障节流,F 项整改 2026-10-24;
* 节流后**丢弃总数没有计数**,要计量得另加计数器)。必须留住的证据走
* sys_log_evidence(),不要靠 syslog_post 重试。
* 模拟器后端(simulator/sim/sim_syslog.c)是单线程直写宿主文件、**没有
* 队列与丢弃**,因此上面这段满态语义只约束 MCU 端。
*/
void syslog_post(syslog_id_t id, const char *desc);
/*
* 函数功能:获取记录条数(= filesize / SYSLOG_REC_SIZE,O(1))
* 入口参数:id - 日志 ID syslog_id_t
* 返回值:条数;文件缺失/未挂载返回 0 uint32_t
*/
uint32_t syslog_count(syslog_id_t id);
/*
* 函数功能:读取第 idx 条记录(时间戳 + 描述,均 NUL 结尾)
* 入口参数:id - 日志 ID syslog_id_t
* idx - 记录序号(0 起) uint32_t
* p_ts - 时间戳输出缓冲(不可为 NULL) char *
* ts_len- p_ts 容量,必须 > SYSLOG_TS_LEN(即 ≥ SYSLOG_TS_LEN+1 = 20 字节) size_t
* p_desc- 描述输出缓冲(不可为 NULL) char *
* desc_len - p_desc 容量,必须 > SYSLOG_DESC_MAX(即 ≥ 29 字节) size_t
* 返回值:0 - 成功;-1 - 越界 / 读失败 / **缓冲容量不足** int
* 限定条件:容量不足一律返回 -1,**不做静默截断、也不写调用方一个字节**
* (C 项整改 2026-10-24;旧注释只写"≥…+1 字节"像是"给少了会被截断",
* 照它给 28 字节缓冲的结果是整条读不到而不是读到 27 字符)。
* 描述字段在写入侧已截到 SYSLOG_DESC_MAX,所以按上面的最小容量给缓冲
* 就一定装得下,读侧不需要第二道截断。
* MCU 后端与模拟器后端(sim_syslog.c)在这条守卫上取值相同,行为一致;
* 模拟器侧那份同样不可达的截断支还留在文件里(未列入本轮改动范围)。
* 调用方:uiTask 的日志页(行缓冲 32B)与 SYSLOG 取证打印。
*/
int syslog_get_record(syslog_id_t id, uint32_t idx, char *p_ts, size_t ts_len, char *p_desc, size_t desc_len);
/*
* 函数功能:读取末条记录的 epoch 秒(上电/重启分类用)
* 入口参数:id - 日志 ID syslog_id_t
* p_epoch- 输出缓冲(不可为 NULL;无记录时置 0) uint32_t *
* 返回值:0 - 成功读到末条;-1 - 文件缺失/空 int
*/
int syslog_last_epoch(syslog_id_t id, uint32_t *p_epoch);
/*
* MCU 后端专属 API(sys_log_mcu.c;模拟器端无对应实现,勿在共享代码调用)
*/
/*
* 函数功能:日志引擎初始化(创建 FreeRTOS 消息队列)
* 入口参数:无
* 返回值:0 - 成功;-1 - 队列创建失败 int
* 限定条件:app_main_init() 起始处调用,先于任何 syslog_post()
*/
int syslog_mcu_init(void);
/*
* 函数功能:卷挂载成功后的一次性处理(建 /log 目录 + 写"上电/重启"分类记录)
* 入口参数:无
* 返回值:无
* 限定条件:defaultTask f_mount/f_mkfs 成功且置位 SYS_EVT_STORAGE_READY 前调用一次
*/
void syslog_mcu_mounted(void);
/*
* 函数功能:排空待发日志并落盘(启动前数组 + 运行期队列,FIFO)
* 入口参数:无
* 返回值:无
* 限定条件:仅 defaultTask(唯一写者)调用;卷未就绪时直接返回(条目滞留待重试)
*/
void syslog_drain(void);
/*
* 函数功能:把一条取证行追加到 /log/wdev.log(掉电不丢;变长行,不走 64B 定长格式)
* 入口参数:p_desc - 取证描述行(调用方已格式化;不可为 NULL) const char *
* 返回值:0 - 成功;-1 - 卷占用或写失败(证据可能回滚,**不阻断判定**) int
* 限定条件:poller(defaultTask)上下文调用;卷锁一律 try(绝不阻塞,
* 本上下文 = 看门狗喂狗点)
* 函数说明:round 7 取证通道(设计见 docs/子系统设计/看门狗设计说明.md 2026-10-02 变更记录:
* 误复位后 RAM 态随复位消失、stall 行经僵尸串口丢失,故证据必须落盘)。
* 内部在释放卷锁后立刻追加一条 s_append(SYSLOG_RUN, ...) 写另一个
* 文件——FTL 单页缓存语义为"写到不同 LPN 才把前一脏页刷进 NAND",
* 没有该 flush 诱导,本行证据会在约 1.7s 后的 IWDG 复位中回滚丢失。
* 文件 > 8KB 时先 unlink 重建,防长跑无界增长。
*/
int sys_log_evidence(const char *p_desc);
#ifdef __cplusplus
}
#endif
#endif /* SYS_LOG_H */