Skip to content

fix(LogMonitor): 日志文件消失时监控不再静默死亡 - #709

Merged
1w1w11w1 merged 1 commit into
devfrom
fix/logmonitor-coroutine-death
Sep 13, 2026
Merged

fix(LogMonitor): 日志文件消失时监控不再静默死亡#709
1w1w11w1 merged 1 commit into
devfrom
fix/logmonitor-coroutine-death

Conversation

@1w1w11w1

@1w1w11w1 1w1w11w1 commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

$monitor_file 的循环体里,mtime 检查落在读取分支的 try 之外。文件在 exists() 与随后的 stat() 之间被删除或重建时,FileNotFoundError 直接逃逸、监控协程带异常终止;调用方没有 done_callback,异常无人回收,日志监控静默失效,该趟任务再也读不到日志,只能卡到超时或误判异常。

exists() 与紧随其后的 stat() 是同一件事的两次查询,且读取分支本就对文件不存在给出 FileNotFoundError 并 continue。删除该冗余检查后,把 mtime 块并入已有的读取分支 try,循环体只保留一个异常出口。净减 7 行,不新增守卫。

替换 #670:该 PR 走的是「给 stat 补 try」的加守卫路线,本 PR 直接消除冗余检查并复用已有异常出口。

  • 修复日志文件被重建或删除时日志监控静默失效、该趟任务无法正常判定的问题

Closes #666

Summary by Sourcery

Bug Fixes:

  • 修复日志文件在监控过程中被删除或重建后导致监控协程静默终止、任务无法正常判定的问题。
Original summary in English

Summary by Sourcery

Bug Fixes:

  • 修复日志文件在监控过程中被删除或重建后导致监控协程静默终止、任务无法正常判定的问题。

monitor_file 的循环体里,mtime 检查落在读取分支的 try 之外,
exists() 与随后的 stat() 之间文件被删除或重建时,
FileNotFoundError 直接逃逸、监控协程带异常终止。
调用方没有 done_callback,异常无人回收,日志监控静默失效,
该趟任务再也读不到日志,只能卡到超时或误判异常。

exists() 与紧随其后的 stat() 是同一件事的两次查询,且读取分支
本就会对文件不存在给出 FileNotFoundError 并 continue,删除该冗余
检查后把 mtime 块并入已有的读取分支 try,循环体只保留一个异常出口。
净减 7 行,不新增守卫。
@sourcery-ai

sourcery-ai Bot commented Sep 12, 2026

Copy link
Copy Markdown
审阅者指南(小型 PR 中折叠显示)

审阅者指南

通过删除独立的文件存在性查询,并将 mtime 检查纳入已有读取 try,实现单一异常出口;文件在检查与 stat 之间被删除或重建时,监控会按既有重试流程继续运行而不会静默终止,同时补充变更日志和版本信息。

具备容错能力的日志文件监控时序图

sequenceDiagram
    participant Monitor as LogMonitor
    participant File as LogFile
    participant Callback as do_callback

    loop monitor_file
        Monitor->>File: stat()
        alt file exists and mtime is today
            Monitor->>File: read log contents
            Monitor->>Monitor: process log updates
        else FileNotFoundError during stat or read
            Monitor->>Callback: do_callback()
            Monitor->>Monitor: sleep(1)
            Monitor->>Monitor: continue monitoring
        else mtime is not today
            Monitor->>Callback: do_callback()
            Monitor->>Monitor: sleep(1)
        end
    end
Loading

文件级变更

变更 详细信息 文件
将文件修改时间检查并入统一的文件读取异常处理路径,避免竞态删除或重建导致监控协程退出。
  • 移除 exists() 与 stat() 之间的冗余存在性检查及其单独分支。
  • 在已有 try 块中执行 mtime 检查和后续读取,使 FileNotFoundError 复用原有 continue、回调与重试逻辑。
  • 保留非当天文件的告警、回调和延迟处理,以及文件轮转/替换后的读取状态逻辑。
app/utils/LogMonitor.py
记录日志监控故障修复并更新版本元数据。
  • 新增日志文件运行中被删除或重建时监控失效问题的变更记录。
  • 更新资源版本信息。
CHANGELOG.md
res/version.json

可能相关的问题


提示和命令

与 Sourcery 交互

  • 触发新的审阅: 在拉取请求中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审阅评论。
  • 从审阅评论生成 GitHub issue: 回复审阅评论,请 Sourcery 根据该评论创建 issue。你也可以回复审阅评论并输入 @sourcery-ai issue,根据该评论创建 issue。
  • 生成拉取请求标题: 在拉取请求标题的任意位置输入 @sourcery-ai,即可随时生成标题。你也可以在拉取请求中评论 @sourcery-ai title,随时重新生成标题。
  • 生成拉取请求摘要: 在拉取请求正文的任意位置输入 @sourcery-ai summary,即可在指定位置随时生成 PR 摘要。你也可以在拉取请求中评论 @sourcery-ai summary,随时重新生成摘要。
  • 生成审阅者指南: 在拉取请求中评论 @sourcery-ai guide,即可随时重新生成审阅者指南。
  • 解决所有 Sourcery 评论: 在拉取请求中评论 @sourcery-ai resolve,即可解决所有 Sourcery 评论。如果你已经处理完所有评论且不想再看到它们,此功能非常实用。
  • 忽略所有 Sourcery 审阅: 在拉取请求中评论 @sourcery-ai dismiss,即可忽略所有现有的 Sourcery 审阅。如果你想从新的审阅开始,这项功能尤其有用——别忘了评论 @sourcery-ai review 来触发新的审阅!

自定义你的使用体验

访问你的控制面板

  • 启用或禁用审阅功能,例如 Sourcery 生成的拉取请求摘要、审阅者指南等。
  • 更改审阅语言。
  • 添加、删除或编辑自定义审阅说明。
  • 调整其他审阅设置。

获取帮助

Original review guide in English
Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

通过删除独立的文件存在性查询,并将 mtime 检查纳入已有读取 try,实现单一异常出口;文件在检查与 stat 之间被删除或重建时,监控会按既有重试流程继续运行而不会静默终止,同时补充变更日志和版本信息。

Sequence diagram for resilient log file monitoring

sequenceDiagram
    participant Monitor as LogMonitor
    participant File as LogFile
    participant Callback as do_callback

    loop monitor_file
        Monitor->>File: stat()
        alt file exists and mtime is today
            Monitor->>File: read log contents
            Monitor->>Monitor: process log updates
        else FileNotFoundError during stat or read
            Monitor->>Callback: do_callback()
            Monitor->>Monitor: sleep(1)
            Monitor->>Monitor: continue monitoring
        else mtime is not today
            Monitor->>Callback: do_callback()
            Monitor->>Monitor: sleep(1)
        end
    end
Loading

File-Level Changes

Change Details Files
将文件修改时间检查并入统一的文件读取异常处理路径,避免竞态删除或重建导致监控协程退出。
  • 移除 exists() 与 stat() 之间的冗余存在性检查及其单独分支。
  • 在已有 try 块中执行 mtime 检查和后续读取,使 FileNotFoundError 复用原有 continue、回调与重试逻辑。
  • 保留非当天文件的告警、回调和延迟处理,以及文件轮转/替换后的读取状态逻辑。
app/utils/LogMonitor.py
记录日志监控故障修复并更新版本元数据。
  • 新增日志文件运行中被删除或重建时监控失效问题的变更记录。
  • 更新资源版本信息。
CHANGELOG.md
res/version.json

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

嗨——我发现了 1 个问题

面向 AI Agent 的提示
请处理此次代码审查中的评论:

## 具体评论

### 评论 1
<location path="app/utils/LogMonitor.py" line_range="173-183" />
<code_context>
-                continue
-
-            if not if_mtime_checked:
-                file_mtime_date = date.fromtimestamp(current_path.stat().st_mtime)
-                if file_mtime_date == date.today():
-                    log_stat = current_path.stat()
-                    if_mtime_checked = True
-                else:
-                    if warned_mtime_date != file_mtime_date:
-                        logger.warning(f"日志文件今天未被修改: {file_mtime_date}")
-                        warned_mtime_date = file_mtime_date
-                    await self.do_callback()
-                    await asyncio.sleep(1)
-                    continue
-
             # 尝试读取文件
</code_context>
<issue_to_address>
**issue (broader_impact):** 当初始 `stat()` 执行前日志路径不存在时,新的外层异常处理路径只会记录日志并休眠五秒;它不再像已移除的 `exists()` 分支那样调用 `do_callback()``do_callback()` 会更新 `last_callback_time``last_callback_at`,因此在文件重新创建期间,任务会失去文件缺失状态下的心跳,并可能被视为停滞或发生超时。

**触发条件:** 受监控的文件被删除,或尚未创建时。

**建议修复:**`FileNotFoundError` 处理程序中保留文件缺失时的回调语义,或者在休眠前以其他方式更新监控器的心跳。
</issue_to_address>

Sourcery 评估

等待批准。 请先处理 1 个发现的问题。

阻塞性发现:app/utils/LogMonitor.py:183


Sourcery 对开源项目免费——如果您喜欢我们的审查结果,请考虑分享它们 ✨
Original comment in English

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="app/utils/LogMonitor.py" line_range="173-183" />
<code_context>
-                continue
-
-            if not if_mtime_checked:
-                file_mtime_date = date.fromtimestamp(current_path.stat().st_mtime)
-                if file_mtime_date == date.today():
-                    log_stat = current_path.stat()
-                    if_mtime_checked = True
-                else:
-                    if warned_mtime_date != file_mtime_date:
-                        logger.warning(f"日志文件今天未被修改: {file_mtime_date}")
-                        warned_mtime_date = file_mtime_date
-                    await self.do_callback()
-                    await asyncio.sleep(1)
-                    continue
-
             # 尝试读取文件
</code_context>
<issue_to_address>
**issue (broader_impact):** When the log path is absent before the initial `stat()`, the new outer exception path only logs and sleeps for five seconds; it no longer calls `do_callback()` as the removed `exists()` branch did. `do_callback()` updates `last_callback_time` and `last_callback_at`, so the task loses its missing-file heartbeat and can be treated as stalled or time out while the file is being recreated.

**Triggers:** When the monitored file is deleted or has not yet been created.

**Suggested fix:** Preserve the missing-file callback semantics in the `FileNotFoundError` handler, or otherwise update the monitor heartbeat before sleeping.
</issue_to_address>

Sourcery assessment

Approval pending. 1 finding to address first.

Blocking findings: app/utils/LogMonitor.py:183


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread app/utils/LogMonitor.py
Comment on lines +173 to +183
file_mtime_date = date.fromtimestamp(current_path.stat().st_mtime)
if file_mtime_date == date.today():
log_stat = current_path.stat()
if_mtime_checked = True
else:
if warned_mtime_date != file_mtime_date:
logger.warning(f"日志文件今天未被修改: {file_mtime_date}")
warned_mtime_date = file_mtime_date
await self.do_callback()
await asyncio.sleep(1)
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (broader_impact): 当初始 stat() 执行前日志路径不存在时,新的外层异常处理路径只会记录日志并休眠五秒;它不再像已移除的 exists() 分支那样调用 do_callback()do_callback() 会更新 last_callback_timelast_callback_at,因此在文件重新创建期间,任务会失去文件缺失状态下的心跳,并可能被视为停滞或发生超时。

触发条件: 受监控的文件被删除,或尚未创建时。

建议修复:FileNotFoundError 处理程序中保留文件缺失时的回调语义,或者在休眠前以其他方式更新监控器的心跳。

Original comment in English

issue (broader_impact): When the log path is absent before the initial stat(), the new outer exception path only logs and sleeps for five seconds; it no longer calls do_callback() as the removed exists() branch did. do_callback() updates last_callback_time and last_callback_at, so the task loses its missing-file heartbeat and can be treated as stalled or time out while the file is being recreated.

Triggers: When the monitored file is deleted or has not yet been created.

Suggested fix: Preserve the missing-file callback semantics in the FileNotFoundError handler, or otherwise update the monitor heartbeat before sleeping.

@1w1w11w1
1w1w11w1 merged commit dd2eaa9 into dev Sep 13, 2026
4 of 5 checks passed
@1w1w11w1
1w1w11w1 deleted the fix/logmonitor-coroutine-death branch September 13, 2026 03:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant