feat(logging): 日志持久化落盘(LOG_FILE 滚动文件) #14

Merged
shangfangjian merged 1 commits from log-persistence into main 2026-09-22 13:34:46 +08:00
Collaborator

背景

此前应用日志只有两个去向:stdout(docker json-file,不可查询、受宿主轮转策略支配)和 Admin「系统日志」页的内存环形缓冲(2000 条,进程重启清零)。排查「昨晚采集为什么失败」这类问题时无据可查。

方案

  • LOG_FILE 配置(默认空):填文件路径后,setup_logging 在 stdout/内存 handler 之外额外挂 RotatingFileHandler(单文件 10MB × 5 份,utf-8,带时间/级别/logger 名格式)
  • 幂等:按 abspath 判重,相对/绝对路径指向同一文件视为同一个,重复调用不重复挂 handler
  • best-effort:目录自动创建;文件基础设施打开失败只记 warning 绝不拖垮启动(与 _safe_write_ingest_failure 同级约束)
  • compose:api 默认 LOG_FILE=/app/logs/app.log 并挂 applogs 命名卷,容器重建日志不丢

测试

  • 新增 tests/test_log_persistence.py(7 例):落盘内容/幂等/空值禁用/自动建目录/失败降级
  • 全量 326 passed, 8 skipped
  • 顺手加固存量 flaky:test_p0_standings_cutoff 的断言撞上匿名约束 name=None 间歇性 TypeError(集合顺序不定),先判真值再子串匹配

文档

.env.example 注释示例、docs/06 env 表、docs/07 与 README 的 core 树描述同步更新。

## 背景 此前应用日志只有两个去向:stdout(docker json-file,不可查询、受宿主轮转策略支配)和 Admin「系统日志」页的内存环形缓冲(2000 条,进程重启清零)。排查「昨晚采集为什么失败」这类问题时无据可查。 ## 方案 - **LOG_FILE 配置**(默认空):填文件路径后,`setup_logging` 在 stdout/内存 handler 之外额外挂 `RotatingFileHandler`(单文件 10MB × 5 份,utf-8,带时间/级别/logger 名格式) - **幂等**:按 abspath 判重,相对/绝对路径指向同一文件视为同一个,重复调用不重复挂 handler - **best-effort**:目录自动创建;文件基础设施打开失败只记 warning 绝不拖垮启动(与 `_safe_write_ingest_failure` 同级约束) - **compose**:api 默认 `LOG_FILE=/app/logs/app.log` 并挂 `applogs` 命名卷,容器重建日志不丢 ## 测试 - 新增 `tests/test_log_persistence.py`(7 例):落盘内容/幂等/空值禁用/自动建目录/失败降级 - 全量 **326 passed, 8 skipped** - 顺手加固存量 flaky:`test_p0_standings_cutoff` 的断言撞上匿名约束 name=None 间歇性 TypeError(集合顺序不定),先判真值再子串匹配 ## 文档 .env.example 注释示例、docs/06 env 表、docs/07 与 README 的 core 树描述同步更新。
WorkBuddy added 1 commit 2026-09-22 12:18:44 +08:00
- LOG_FILE 配置: 空(默认)保持 stdout + Admin 内存日志页(重启清零);
  填路径后额外写 RotatingFileHandler(单文件 10MB × 5 份,utf-8)
- setup_logging 幂等挂载(按 abspath 判重,相对/绝对同文件算一个);
  目录自动创建;文件基础设施失败只 warning 绝不拖垮启动
- docker-compose: api 挂 applogs 卷,默认 LOG_FILE=/app/logs/app.log,
  容器重建日志不丢;.env.example/.gitignore/docs 同步
- tests/test_log_persistence.py: 落盘/幂等/空值禁用/自动建目录/失败降级
- test_p0_standings_cutoff: 匿名约束 name=None 使 any() 子串匹配 TypeError
  (集合顺序不定 → flaky),先判真值再匹配(存量缺陷顺手加固)
shangfangjian merged commit 432bfee8ad into main 2026-09-22 13:34:46 +08:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: shangfangjian/Profeto#14