feat: 日志轮转(保留 14 天)
main.log 无轮转、已涨到 34MB,且每晚任务都会继续追加。 - logrotate/xwlb 模板 + scripts/install_logrotate.sh(幂等,装到 /etc/logrotate.d/xwlb) - 每天轮转、保留 14 份、立即压缩 → main.log-YYYYMMDD.gz - 必须用 copytruncate:脚本以 >>main.log 长开句柄,改名式轮转会 让当晚日志写进归档、新文件空着 - journald 同步设 MaxRetentionSec=14day + SystemMaxUse=500M - 安装脚本自带 logrotate -d 校验(这条立刻抓到"指令行尾注释"被当成参数值的错误) - tests/test_logrotate.py 19 项,锁住保留天数、行尾注释、占位符替换 实测:34MB → 2.9MB,归档 32453 行与轮转前完全一致
This commit is contained in:
@@ -2,7 +2,9 @@
|
|||||||
xwlb_video/
|
xwlb_video/
|
||||||
audio_processing/
|
audio_processing/
|
||||||
main.log
|
main.log
|
||||||
|
main.log-*
|
||||||
*.log
|
*.log
|
||||||
|
*.log-*
|
||||||
.run_daily.lock
|
.run_daily.lock
|
||||||
state/
|
state/
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
- **可重跑的分步入口**:只重做转写、只重做切分、强制重切,都有独立命令。
|
- **可重跑的分步入口**:只重做转写、只重做切分、强制重切,都有独立命令。
|
||||||
- **配置分层**:口令/Key 在 `.env`,其余(含**三个模型名**与**各供应商 base url**)在 `config.yml`;换模型、换供应商都不用动代码。
|
- **配置分层**:口令/Key 在 `.env`,其余(含**三个模型名**与**各供应商 base url**)在 `config.yml`;换模型、换供应商都不用动代码。
|
||||||
- **失败说得清楚**:缺 Key、连不上库(含隧道提示)、模型返回非 JSON 等都有明确日志与修复指引。
|
- **失败说得清楚**:缺 Key、连不上库(含隧道提示)、模型返回非 JSON 等都有明确日志与修复指引。
|
||||||
- **97 项离线自检**:配置、清理、事实守卫、LLM 返回解析、隧道自愈五个测试套件,不依赖网络与真实 API。
|
- **116 项离线自检**:配置、清理、事实守卫、LLM 返回解析、隧道自愈、日志轮转六个测试套件,不依赖网络与真实 API。
|
||||||
|
|
||||||
### 1.3 当前数据规模(2026-09-25 实测)
|
### 1.3 当前数据规模(2026-09-25 实测)
|
||||||
|
|
||||||
@@ -373,7 +373,7 @@ endpoints:
|
|||||||
|
|
||||||
### 3.6 自检与测试
|
### 3.6 自检与测试
|
||||||
|
|
||||||
五个测试套件共 **97 项断言**,全部离线运行(不消耗 API 额度、不触碰真实文件):
|
六个测试套件共 **116 项断言**,全部离线运行(不消耗 API 额度、不触碰真实文件):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
.venv/bin/python tests/test_config.py # 37 项:配置加载、优先级、接入点/路由、敏感项隔离
|
.venv/bin/python tests/test_config.py # 37 项:配置加载、优先级、接入点/路由、敏感项隔离
|
||||||
@@ -381,6 +381,7 @@ endpoints:
|
|||||||
.venv/bin/python tests/test_fidelity_guard.py # 14 项:数值事实守卫(含真实篡改样例)
|
.venv/bin/python tests/test_fidelity_guard.py # 14 项:数值事实守卫(含真实篡改样例)
|
||||||
.venv/bin/python tests/test_news_parse.py # 16 项:LLM 返回解析(含真实坏返回)
|
.venv/bin/python tests/test_news_parse.py # 16 项:LLM 返回解析(含真实坏返回)
|
||||||
.venv/bin/python tests/test_tunnel.py # 20 项:隧道自愈各分支(临时端口,不碰真实隧道)
|
.venv/bin/python tests/test_tunnel.py # 20 项:隧道自愈各分支(临时端口,不碰真实隧道)
|
||||||
|
.venv/bin/python tests/test_logrotate.py # 19 项:轮转指令、保留天数、行尾注释、占位符替换
|
||||||
```
|
```
|
||||||
|
|
||||||
其中隧道测试**不会碰真实的 13306 与 autossh.sh**:它用临时 config.yml + 临时端口的假脚本,
|
其中隧道测试**不会碰真实的 13306 与 autossh.sh**:它用临时 config.yml + 临时端口的假脚本,
|
||||||
@@ -470,7 +471,39 @@ mysql:
|
|||||||
|
|
||||||
(直连远端数据库时,`host` 不是本机地址 → 判定为"不适用隧道",不会去跑 autossh.sh。)
|
(直连远端数据库时,`host` 不是本机地址 → 判定为"不适用隧道",不会去跑 autossh.sh。)
|
||||||
|
|
||||||
### 3.9 故障排查
|
### 3.9 日志轮转
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash scripts/install_logrotate.sh # 安装/更新(幂等,可反复执行)
|
||||||
|
```
|
||||||
|
|
||||||
|
两处一起收口,都是**保留 14 天**:
|
||||||
|
|
||||||
|
| 日志 | 位置 | 策略 |
|
||||||
|
|---|---|---|
|
||||||
|
| 任务日志 `main.log` | 项目根(`>>main.log` 追加) | logrotate:**每天轮转、保留 14 份、立即压缩** → `main.log-20260925.gz` |
|
||||||
|
| systemd journal | `/var/log/journal` | journald:`MaxRetentionSec=14day` + `SystemMaxUse=500M` |
|
||||||
|
|
||||||
|
实测效果:34MB 的 `main.log` 轮转后成 2.9MB 压缩归档,**归档行数与轮转前完全一致**(32453 行),磁盘立即回收。
|
||||||
|
|
||||||
|
配置里有两个容易踩的点:
|
||||||
|
|
||||||
|
- **必须用 `copytruncate`**:脚本以 `>>main.log` 方式持续写入,一次任务跑几十分钟、句柄一直开着。
|
||||||
|
默认的"改名+新建"会让运行中的进程继续写旧文件,当晚日志被切进归档、新文件却空着。
|
||||||
|
- **指令后面不能写注释**:logrotate 会把注释当成参数值(实测报
|
||||||
|
`bad rotation count '14 # 保留 14 份'`),注释只能单独成行。
|
||||||
|
`tests/test_logrotate.py` 专门锁住了这条。
|
||||||
|
|
||||||
|
轮转由系统自带的 `logrotate.timer`(每天 00:46)驱动,无需额外定时任务。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo logrotate -d /etc/logrotate.d/xwlb # 干跑校验配置
|
||||||
|
sudo logrotate -f /etc/logrotate.d/xwlb # 立即轮转一次
|
||||||
|
zcat main.log-20260925.gz | less # 看归档
|
||||||
|
ls -lh main.log* # 看当前归档与占用
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.10 故障排查
|
||||||
|
|
||||||
| 现象 | 原因 / 处理 |
|
| 现象 | 原因 / 处理 |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -487,6 +520,8 @@ mysql:
|
|||||||
| 换供应商后报模型不存在 | `models.correct_model` / `models.split_model` 还是旧供应商的模型名,需同步修改 |
|
| 换供应商后报模型不存在 | `models.correct_model` / `models.split_model` 还是旧供应商的模型名,需同步修改 |
|
||||||
| `getVideo5` 报某天 404 | 该天页面尚未上线(当天节目过期或未发布),日志会明确跳过 |
|
| `getVideo5` 报某天 404 | 该天页面尚未上线(当天节目过期或未发布),日志会明确跳过 |
|
||||||
| 磁盘被中间产物占满 | `python cleanup.py --dry-run` 查看,再 `python cleanup.py` |
|
| 磁盘被中间产物占满 | `python cleanup.py --dry-run` 查看,再 `python cleanup.py` |
|
||||||
|
| `main.log` 越来越大 | 已配 logrotate 保留 14 天(见 3.9);未装则 `bash scripts/install_logrotate.sh` |
|
||||||
|
| 日志轮转报 `bad rotation count` | 指令行写了行尾注释;logrotate 不支持,注释要单独成行 |
|
||||||
| 某天识别到一半卡死(无新日志、无 CPU) | ASR 的 `Recognition.call` 没有超时参数,长连接可能悬挂;定时任务有 40 分钟上限并在 21:30/22:00 重试。手动跑请自行 `timeout` |
|
| 某天识别到一半卡死(无新日志、无 CPU) | ASR 的 `Recognition.call` 没有超时参数,长连接可能悬挂;定时任务有 40 分钟上限并在 21:30/22:00 重试。手动跑请自行 `timeout` |
|
||||||
| 定时任务没跑 | `systemctl list-timers xwlb-daily.timer`;`systemctl status xwlb-daily.service`;日志 `journalctl -u xwlb-daily -n 100` |
|
| 定时任务没跑 | `systemctl list-timers xwlb-daily.timer`;`systemctl status xwlb-daily.service`;日志 `journalctl -u xwlb-daily -n 100` |
|
||||||
| 定时任务报数据库连接失败 | 隧道服务:`systemctl status xwlb-tunnel`;手动兜底 `bash autossh.sh` |
|
| 定时任务报数据库连接失败 | 隧道服务:`systemctl status xwlb-tunnel`;手动兜底 `bash autossh.sh` |
|
||||||
|
|||||||
@@ -294,6 +294,8 @@ xwlb/
|
|||||||
| **定时任务(生产入口)** | `bash scripts/run_daily.sh [YYYYMMDD]` | 全链路 + 失败重试(21:30 / 22:00);由 `systemd/xwlb-daily.timer` 每天 21:00 触发 |
|
| **定时任务(生产入口)** | `bash scripts/run_daily.sh [YYYYMMDD]` | 全链路 + 失败重试(21:30 / 22:00);由 `systemd/xwlb-daily.timer` 每天 21:00 触发 |
|
||||||
| 这天跑到哪一步 | `python scripts/day_status.py <日期>` | 退出码 0=已有精编 / 1=识别完成只缺切分 / 2=需重跑全链路 |
|
| 这天跑到哪一步 | `python scripts/day_status.py <日期>` | 退出码 0=已有精编 / 1=识别完成只缺切分 / 2=需重跑全链路 |
|
||||||
| 安装定时任务 | `bash scripts/install_systemd.sh` | 安装单元、启用定时器与隧道服务 |
|
| 安装定时任务 | `bash scripts/install_systemd.sh` | 安装单元、启用定时器与隧道服务 |
|
||||||
|
| 安装日志轮转 | `bash scripts/install_logrotate.sh` | 装 `/etc/logrotate.d/xwlb`(每天/保留14天/压缩)与 journald 上限(14 天) |
|
||||||
|
| 校验轮转配置 | `sudo logrotate -d /etc/logrotate.d/xwlb` | 干跑,不实际轮转 |
|
||||||
| 校对效果评估 | `python tools/compare_raw_improve.py --md <文件>` | 统计 news_raw vs news_improve 差异分类与 token 开销 |
|
| 校对效果评估 | `python tools/compare_raw_improve.py --md <文件>` | 统计 news_raw vs news_improve 差异分类与 token 开销 |
|
||||||
| 合并方案评估 | `python tools/experiment_merge_correct_split.py <日期>` | 实测"校对+切分合并成一次调用"的覆盖率与事实漂移 |
|
| 合并方案评估 | `python tools/experiment_merge_correct_split.py <日期>` | 实测"校对+切分合并成一次调用"的覆盖率与事实漂移 |
|
||||||
| 配置自检 | `python tests/test_config.py` | 校验 config.yml 加载、优先级与敏感项隔离 |
|
| 配置自检 | `python tests/test_config.py` | 校验 config.yml 加载、优先级与敏感项隔离 |
|
||||||
@@ -341,6 +343,13 @@ xwlb/
|
|||||||
|
|
||||||
退出码约定:`0` 成功 / `1` 重试用尽仍失败 / `2` 已有实例在运行。
|
退出码约定:`0` 成功 / `1` 重试用尽仍失败 / `2` 已有实例在运行。
|
||||||
|
|
||||||
|
**日志(保留 14 天)**:任务日志 `main.log` 由 logrotate 每天轮转为
|
||||||
|
`main.log-YYYYMMDD.gz` 保留 14 份(`logrotate/xwlb` 模板 →
|
||||||
|
`scripts/install_logrotate.sh` 安装);systemd journal 通过
|
||||||
|
`/etc/systemd/journald.conf.d/xwlb.conf` 设 `MaxRetentionSec=14day` + `SystemMaxUse=500M`。
|
||||||
|
轮转必须用 `copytruncate`——脚本以 `>>main.log` 长开句柄,
|
||||||
|
改名式轮转会让当晚日志写进归档、新文件空着。
|
||||||
|
|
||||||
**为什么必须有 `state/.asr_complete_<日期>` 标记**:识别中途卡死(B19)会留下部分分片,
|
**为什么必须有 `state/.asr_complete_<日期>` 标记**:识别中途卡死(B19)会留下部分分片,
|
||||||
若只按"库里有没有分片"判断,重试会走"只重跑切分",把**半天内容当成完整一天**入库且不报错。
|
若只按"库里有没有分片"判断,重试会走"只重跑切分",把**半天内容当成完整一天**入库且不报错。
|
||||||
标记由 `audioRead.process_long_audio` 在**全部识别成功**时写入,失败时主动删除。
|
标记由 `audioRead.process_long_audio` 在**全部识别成功**时写入,失败时主动删除。
|
||||||
|
|||||||
+5
-2
@@ -19,7 +19,7 @@
|
|||||||
| B3 | P0 | `newsProcess.py:89,119-121` | DeepSeek 返回非纯 JSON(```json 围栏 / 截断)即整批失败 | 22 天 `xwlb_daily_ext` 当天 0 条 | ✅ 已修复(回归测试覆盖) |
|
| B3 | P0 | `newsProcess.py:89,119-121` | DeepSeek 返回非纯 JSON(```json 围栏 / 截断)即整批失败 | 22 天 `xwlb_daily_ext` 当天 0 条 | ✅ 已修复(回归测试覆盖) |
|
||||||
| B4 | P0 | `getVideo5.py:69,92,156` | `get_xwlb_video_link()` 返回值错误(只回最后一个 / 未定义 / `[]`) | 取错链接、`UnboundLocalError`、无效 URL 仍进下载 | ✅ 已修复并实测(联网) |
|
| B4 | P0 | `getVideo5.py:69,92,156` | `get_xwlb_video_link()` 返回值错误(只回最后一个 / 未定义 / `[]`) | 取错链接、`UnboundLocalError`、无效 URL 仍进下载 | ✅ 已修复并实测(联网) |
|
||||||
| B5 | P0 | `getVideo5.py:104-146`、`audioRead.py:389-399` | 下载与分片落库无幂等 | 重跑 → 重复下载 + `xwlb_daily` 重复行 → 新闻重复、token 翻倍 | ✅ 已修复并实测 |
|
| B5 | P0 | `getVideo5.py:104-146`、`audioRead.py:389-399` | 下载与分片落库无幂等 | 重跑 → 重复下载 + `xwlb_daily` 重复行 → 新闻重复、token 翻倍 | ✅ 已修复并实测 |
|
||||||
| B6 | P1 | `audioRead.py:260`、`getVideo5.py:132-140` | 关键外部调用无 timeout/重试;ffmpeg 输出未接管 | 单点失败无补救;`main.log` 34MB 且无轮转 | 🟡 部分(校对已加超时/重试;ffmpeg 输出已接管;yt-dlp 进度与日志轮转未处理) |
|
| B6 | P1 | `audioRead.py:260`、`getVideo5.py:132-140` | 关键外部调用无 timeout/重试;ffmpeg 输出未接管 | 单点失败无补救;`main.log` 34MB 且无轮转 | 🟡 部分(校对已加超时/重试;ffmpeg 输出已接管;**日志轮转已完成**;yt-dlp 进度未处理) |
|
||||||
| B7 | P1 | `deepseek.py:257-268` | 降级分支 `return result`(变量错/可能未绑定) | DeepSeek 失败时抛 `NameError`,降级形同虚设 | ✅ 已修复(`return fallback_result`) |
|
| B7 | P1 | `deepseek.py:257-268` | 降级分支 `return result`(变量错/可能未绑定) | DeepSeek 失败时抛 `NameError`,降级形同虚设 | ✅ 已修复(`return fallback_result`) |
|
||||||
| B8 | P1 | `audioRead.py:422-439` | `__main__` 示例用 3 参数调 4 参数签名 | `python audioRead.py` 必错 | ✅ 已修复(改为 `python audioRead.py [YYYYMMDD]`) |
|
| B8 | P1 | `audioRead.py:422-439` | `__main__` 示例用 3 参数调 4 参数签名 | `python audioRead.py` 必错 | ✅ 已修复(改为 `python audioRead.py [YYYYMMDD]`) |
|
||||||
| B9 | P1 | `audioRead.py:387`、`getVideo5.py:104-146` | 临时文件不清理(chunk/input.wav/mp4) | 单日约 180MB 常驻,磁盘持续增长 | 🟡 部分(分片与按日 WAV 已在 `finally` 清理;mp4/mp3 保留策略待定) |
|
| B9 | P1 | `audioRead.py:387`、`getVideo5.py:104-146` | 临时文件不清理(chunk/input.wav/mp4) | 单日约 180MB 常驻,磁盘持续增长 | 🟡 部分(分片与按日 WAV 已在 `finally` 清理;mp4/mp3 保留策略待定) |
|
||||||
@@ -345,7 +345,10 @@ systemd `TimeoutStartSec=10800`。**根治需要给 ASR 加子进程看门狗**
|
|||||||
## 后续待办(本轮未做)
|
## 后续待办(本轮未做)
|
||||||
|
|
||||||
1. **数据治理**:`xwlb_daily` 现存 101 行空白记录(可识别、可重跑修复);725 天原文里 203 天没有 `xwlb_daily_ext`——回填需重新调用 DeepSeek,属于有成本的操作,需你确认范围后再执行。
|
1. **数据治理**:`xwlb_daily` 现存 101 行空白记录(可识别、可重跑修复);725 天原文里 203 天没有 `xwlb_daily_ext`——回填需重新调用 DeepSeek,属于有成本的操作,需你确认范围后再执行。
|
||||||
2. **B6 收尾**:yt-dlp 进度 hook 仍直接 `print`;`main.log` 无轮转(建议 `RotatingFileHandler` 或由外部 logrotate 管理)。
|
2. **B6 收尾**:yt-dlp 进度 hook 仍直接 `print`;
|
||||||
|
~~`main.log` 无轮转~~ —— **已完成(2026-09-25)**:logrotate 每天轮转、保留 14 天、立即压缩
|
||||||
|
(`logrotate/xwlb` + `scripts/install_logrotate.sh`),journal 同步设 `MaxRetentionSec=14day`;
|
||||||
|
实测 34MB → 2.9MB 且归档行数与轮转前一致(32453 行)。
|
||||||
3. **B9 收尾**:`xwlb_video/*.mp4` 与 `*.mp3` 是否保留需定策略(mp3 可重跑,mp4 价值较低)。
|
3. **B9 收尾**:`xwlb_video/*.mp4` 与 `*.mp3` 是否保留需定策略(mp3 可重跑,mp4 价值较低)。
|
||||||
4. ~~**B14 收尾**:`git init` 纳入版本管理~~ —— **已完成(2026-09-25)**:
|
4. ~~**B14 收尾**:`git init` 纳入版本管理~~ —— **已完成(2026-09-25)**:
|
||||||
已建仓并推送到 `git@192.168.1.10:simon/xwlb.git`(分支 `main`),
|
已建仓并推送到 `git@192.168.1.10:simon/xwlb.git`(分支 `main`),
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# 《新闻联播》抓取入库 —— 项目日志轮转
|
||||||
|
#
|
||||||
|
# 安装:bash scripts/install_logrotate.sh
|
||||||
|
# (会把 @PROJECT_DIR@ / @OWNER@ 替换成实际路径与用户后装到 /etc/logrotate.d/xwlb)
|
||||||
|
#
|
||||||
|
# 为什么用 copytruncate 而不是默认的"改名+新建":
|
||||||
|
# 脚本以 >>main.log 的方式持续写入,一次任务可能跑几十分钟,文件句柄一直开着。
|
||||||
|
# 改名式轮转会让**运行中的进程继续写旧文件**(新 main.log 要到下次任务才有内容),
|
||||||
|
# 当晚的日志会被切进归档里。copytruncate 是"先复制再原地截断",句柄始终有效。
|
||||||
|
# 代价是复制与截断之间若有写入,那几行会丢(微秒级窗口),日志可接受。
|
||||||
|
#
|
||||||
|
# 未用 delaycompress:归档立即压缩(34MB → 约 2MB),磁盘马上回收;
|
||||||
|
# 需要看归档内容用 zcat main.log-YYYYMMDD.gz。
|
||||||
|
#
|
||||||
|
# 注意:logrotate 不支持行尾注释(会把它当成参数值),注释只能单独成行。
|
||||||
|
|
||||||
|
@PROJECT_DIR@/main.log {
|
||||||
|
# 每天轮转
|
||||||
|
daily
|
||||||
|
# 保留 14 份,即 14 天
|
||||||
|
rotate 14
|
||||||
|
# 文件不存在不报错
|
||||||
|
missingok
|
||||||
|
# 空文件不轮转
|
||||||
|
notifempty
|
||||||
|
# 压缩归档
|
||||||
|
compress
|
||||||
|
# 见上文:不打断正在写入的进程
|
||||||
|
copytruncate
|
||||||
|
# 归档名带日期 → main.log-20260925.gz
|
||||||
|
dateext
|
||||||
|
dateformat -%Y%m%d
|
||||||
|
# logrotate 以 root 运行,切到日志属主操作(避免权限告警)
|
||||||
|
su @OWNER@ @GROUP@
|
||||||
|
}
|
||||||
Executable
+51
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# 安装/更新日志轮转(需要 sudo;幂等,可反复执行)
|
||||||
|
#
|
||||||
|
# bash scripts/install_logrotate.sh
|
||||||
|
#
|
||||||
|
# 做三件事:
|
||||||
|
# 1) 把 logrotate/xwlb 装到 /etc/logrotate.d/xwlb(替换路径与属主占位符)
|
||||||
|
# 2) 给 systemd journal 加上限(保留 14 天,避免 journal 无限增长)
|
||||||
|
# 3) 用 logrotate -d 校验配置,并打印当前归档
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
OWNER="$(id -un)"
|
||||||
|
GROUP="$(id -gn)"
|
||||||
|
SUDO=""
|
||||||
|
if [ "$(id -u)" -ne 0 ]; then SUDO="sudo"; fi
|
||||||
|
|
||||||
|
echo "== 1/3 安装 logrotate 配置 =="
|
||||||
|
TMP="$(mktemp)"
|
||||||
|
trap 'rm -f "$TMP"' EXIT
|
||||||
|
sed -e "s#@PROJECT_DIR@#$DIR#g" -e "s#@OWNER@#$OWNER#g" -e "s#@GROUP@#$GROUP#g" \
|
||||||
|
"$DIR/logrotate/xwlb" > "$TMP"
|
||||||
|
$SUDO install -m 0644 -o root -g root "$TMP" /etc/logrotate.d/xwlb
|
||||||
|
echo " /etc/logrotate.d/xwlb(路径 $DIR,属主 $OWNER)"
|
||||||
|
|
||||||
|
echo "== 2/3 journal 上限 =="
|
||||||
|
$SUDO mkdir -p /etc/systemd/journald.conf.d
|
||||||
|
$SUDO tee /etc/systemd/journald.conf.d/xwlb.conf >/dev/null <<EOF
|
||||||
|
# 《新闻联播》任务日志:保留 14 天 + 总量上限,避免 journal 无限增长
|
||||||
|
# 安装来源:$DIR/scripts/install_logrotate.sh
|
||||||
|
[Journal]
|
||||||
|
MaxRetentionSec=14day
|
||||||
|
SystemMaxUse=500M
|
||||||
|
EOF
|
||||||
|
echo " /etc/systemd/journald.conf.d/xwlb.conf(保留 14 天 / 上限 500M)"
|
||||||
|
if systemctl is-active --quiet systemd-journald; then
|
||||||
|
$SUDO systemctl restart systemd-journald
|
||||||
|
echo " systemd-journald 已重启使配置生效"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "== 3/3 校验配置 =="
|
||||||
|
$SUDO logrotate -d /etc/logrotate.d/xwlb 2>&1 | grep -E "considering|rotating|log needs|error|reading config" | head -8 || true
|
||||||
|
echo
|
||||||
|
echo "== 当前归档 =="
|
||||||
|
ls -lh "$DIR"/main.log* 2>/dev/null | awk '{printf " %-42s %s\n", $NF, $5}' || echo " (暂无)"
|
||||||
|
echo
|
||||||
|
echo "轮转由 logrotate.timer 每天触发:"
|
||||||
|
systemctl list-timers logrotate.timer --no-pager 2>/dev/null | head -2 || true
|
||||||
|
echo
|
||||||
|
echo "立即轮转一次(可选): sudo logrotate -f /etc/logrotate.d/xwlb"
|
||||||
|
echo "查看归档内容: zcat $DIR/main.log-YYYYMMDD.gz | less"
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
"""日志轮转配置回归测试(不依赖 pytest,不需要 root)
|
||||||
|
|
||||||
|
python tests/test_logrotate.py
|
||||||
|
|
||||||
|
覆盖:
|
||||||
|
- 保留 14 天等关键指令齐全;
|
||||||
|
- **指令后面不能有行尾注释**(logrotate 会把注释当成参数值,
|
||||||
|
实测报错 `bad rotation count '14 # 保留 14 份'`,这条就是那次踩坑的回归);
|
||||||
|
- 占位符齐全,且安装脚本确实会替换(否则装过去的路径是字面量 @PROJECT_DIR@);
|
||||||
|
- 若本机已安装,装好的文件里不应残留占位符。
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
CONF = ROOT / 'logrotate' / 'xwlb'
|
||||||
|
INSTALLER = ROOT / 'scripts' / 'install_logrotate.sh'
|
||||||
|
INSTALLED = Path('/etc/logrotate.d/xwlb')
|
||||||
|
|
||||||
|
|
||||||
|
def directives(text):
|
||||||
|
"""取配置块里的指令行(去注释、去空行、去大括号)"""
|
||||||
|
out = []
|
||||||
|
for raw in text.splitlines():
|
||||||
|
line = raw.strip()
|
||||||
|
if not line or line.startswith('#') or line in ('{', '}'):
|
||||||
|
continue
|
||||||
|
out.append(line)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
failed = 0
|
||||||
|
|
||||||
|
def check(desc, got, want):
|
||||||
|
nonlocal failed
|
||||||
|
if got == want:
|
||||||
|
print(f" ✓ {desc}: {got!r}")
|
||||||
|
else:
|
||||||
|
failed += 1
|
||||||
|
print(f" ✗ {desc}: 期望 {want!r},实际 {got!r}")
|
||||||
|
|
||||||
|
print("配置文件:")
|
||||||
|
check('模板存在', CONF.exists(), True)
|
||||||
|
text = CONF.read_text(encoding='utf-8')
|
||||||
|
lines = directives(text)
|
||||||
|
|
||||||
|
def has_directive(name):
|
||||||
|
return any(re.match(rf'^{name}(\s|$)', ln) for ln in lines)
|
||||||
|
|
||||||
|
for d in ('daily', 'rotate', 'missingok', 'notifempty', 'compress',
|
||||||
|
'copytruncate', 'dateext', 'dateformat', 'su'):
|
||||||
|
check(f'含指令 {d}', has_directive(d), True)
|
||||||
|
|
||||||
|
print("\n保留天数:")
|
||||||
|
rotate = [ln for ln in lines if ln.startswith('rotate')][0]
|
||||||
|
check('rotate 值为 14(保留 14 天)', int(rotate.split()[1]), 14)
|
||||||
|
|
||||||
|
print("\n行尾注释(logrotate 会把注释吃成参数值):")
|
||||||
|
bad = [ln for ln in lines if '#' in ln]
|
||||||
|
check('指令行无行尾注释', bad, [])
|
||||||
|
|
||||||
|
print("\n占位符与安装替换:")
|
||||||
|
check('含路径占位符 @PROJECT_DIR@', '@PROJECT_DIR@' in text, True)
|
||||||
|
check('含属主占位符 @OWNER@', '@OWNER@' in text, True)
|
||||||
|
installer = INSTALLER.read_text(encoding='utf-8')
|
||||||
|
check('安装脚本会替换路径占位符', 's#@PROJECT_DIR@#' in installer, True)
|
||||||
|
check('安装脚本会替换属主占位符', 's#@OWNER@#' in installer, True)
|
||||||
|
check('安装脚本会做语法校验(logrotate -d)', 'logrotate -d' in installer, True)
|
||||||
|
|
||||||
|
print("\n已安装文件(若本机装过):")
|
||||||
|
if INSTALLED.exists():
|
||||||
|
installed = INSTALLED.read_text(encoding='utf-8')
|
||||||
|
check('无残留占位符', [p for p in ('@PROJECT_DIR@', '@OWNER@', '@GROUP@')
|
||||||
|
if p in installed], [])
|
||||||
|
check('已指向真实项目路径', str(ROOT) in installed, True)
|
||||||
|
else:
|
||||||
|
print(" · 本机未安装,跳过(未运行 scripts/install_logrotate.sh)")
|
||||||
|
|
||||||
|
print(f"\n结果: {'全部通过' if failed == 0 else f'{failed} 项失败'}")
|
||||||
|
return 1 if failed else 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main())
|
||||||
Reference in New Issue
Block a user