Skip to content

Commit 6951758

Browse files
authored
fix(logging): keep log records when the project root is on another mount (#1260)
os.path.relpath raises ValueError on Windows when the record's file and PROJECT_ROOT resolve to different mounts. That happens whenever the project is launched through a mapped network drive or a subst drive: the call frame keeps X:\MoneyPrinterTurbo\..., while realpath resolves PROJECT_ROOT back to C:\... Loguru catches the formatter error and discards the record, so the terminal and the WebUI log panel both go silent. Fall back to the absolute path there, and also when the file sits outside the project root, where "./" glued onto a ".." climb is no easier to read than the original path. Render the relative path with forward slashes as well, so Windows logs show "./app/services/task.py" like every other platform instead of the mixed "./app\services\task.py". test_webui_task.py already asserted the POSIX form but is not part of the Windows smoke job, so the drift went unnoticed; add it to that job to keep the regression covered on the platform where it appears.
1 parent 221e9ad commit 6951758

3 files changed

Lines changed: 85 additions & 2 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,4 @@ jobs:
109109
test/services/test_task_manager.py
110110
test/services/test_upload_post.py
111111
test/services/test_controller_video.py
112+
test/services/test_webui_task.py

app/utils/logging_utils.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,28 @@
2020
_terminal_handler_lock = threading.RLock()
2121

2222

23+
def _project_relative_path(file_path):
24+
"""
25+
把绝对路径缩短为 ``./`` 开头、始终使用正斜杠的项目相对路径。
26+
27+
Windows 上项目可能通过映射网络盘或 ``subst`` 盘启动。此时调用栈里的路径
28+
仍是 ``X:\\MoneyPrinterTurbo\\...``,而 ``PROJECT_ROOT`` 经 ``realpath``
29+
解析后落在 ``C:\\...``,``os.path.relpath`` 会直接抛出 ``ValueError``。
30+
格式化函数抛错会被 loguru 捕获并丢弃整条记录,终端和 WebUI 日志面板会
31+
同时变空,因此这里必须兜底返回原始路径。项目目录之外的文件同理:把
32+
``./`` 拼到 ``..`` 回溯路径上只会得到更难读的结果。
33+
"""
34+
try:
35+
relative_path = os.path.relpath(file_path, PROJECT_ROOT)
36+
except ValueError:
37+
return file_path
38+
if relative_path == os.pardir or relative_path.startswith(os.pardir + os.sep):
39+
return file_path
40+
# Windows 的 relpath 返回反斜杠分隔的路径,直接拼接会得到 ``./app\\utils``
41+
# 这种混合分隔符的输出,与其它平台的日志不一致。
42+
return f"./{relative_path.replace(os.sep, '/')}"
43+
44+
2345
def format_log_record(record):
2446
"""
2547
统一格式化终端与 WebUI 日志。
@@ -30,8 +52,7 @@ def format_log_record(record):
3052
"""
3153
file_path = record["file"].path
3254
if os.path.isabs(file_path):
33-
relative_path = os.path.relpath(file_path, PROJECT_ROOT)
34-
record["file"].path = f"./{relative_path}"
55+
record["file"].path = _project_relative_path(file_path)
3556

3657
# 日志消息有时会包含任务文件的绝对路径。统一缩短为项目相对路径,可以
3758
# 避免 WebUI 和终端因初始化入口不同而展示两套内容。

test/services/test_webui_task.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ def _attribute_name(node):
3333
return ".".join(reversed(names))
3434

3535

36+
def _log_record(file_path, message="generation finished"):
37+
"""构造 ``format_log_record`` 需要的最小 loguru 记录。"""
38+
return {
39+
"file": SimpleNamespace(name=os.path.basename(file_path), path=file_path),
40+
"message": message,
41+
}
42+
43+
3644
def test_generation_controls_submit_background_task_instead_of_blocking_page():
3745
"""
3846
WebUI 生成按钮不能重新直接调用同步流水线。
@@ -347,6 +355,59 @@ def logged_start(**_kwargs):
347355
)
348356

349357

358+
def test_log_paths_stay_posix_style_on_every_platform():
359+
"""
360+
调用位置必须始终显示为 ``./app/services/task.py``。
361+
362+
Windows 的 ``os.path.relpath`` 返回反斜杠分隔的路径,直接拼接会输出
363+
``./app\\services\\task.py``,同一份日志在不同系统上格式不一致,也无法
364+
和上面按正斜杠断言的后台日志回归测试对齐。
365+
"""
366+
record = _log_record(
367+
os.path.join(logging_utils.PROJECT_ROOT, "app", "services", "task.py")
368+
)
369+
370+
logging_utils.format_log_record(record)
371+
372+
assert record["file"].path == "./app/services/task.py"
373+
374+
375+
def test_log_paths_on_another_mount_do_not_discard_the_record():
376+
"""
377+
映射盘或 ``subst`` 盘启动时不能让整条日志消失。
378+
379+
这种部署下调用栈里的路径仍在 ``X:``,而 ``PROJECT_ROOT`` 已被 realpath
380+
解析回 ``C:``,``os.path.relpath`` 会抛出 ``ValueError``。loguru 捕获
381+
格式化异常后会丢弃记录,终端和 WebUI 日志面板会同时变空。
382+
"""
383+
absolute_path = os.path.join(
384+
logging_utils.PROJECT_ROOT, "app", "services", "task.py"
385+
)
386+
record = _log_record(absolute_path)
387+
388+
with patch.object(
389+
logging_utils.os.path,
390+
"relpath",
391+
side_effect=ValueError("path is on mount 'X:', start on mount 'C:'"),
392+
):
393+
log_format = logging_utils.format_log_record(record)
394+
395+
assert log_format == logging_utils.LOG_RECORD_FORMAT
396+
assert record["file"].path == absolute_path
397+
398+
399+
def test_log_paths_outside_the_project_keep_the_absolute_path():
400+
"""项目目录之外的文件保持绝对路径,避免输出 ``./../..`` 这类回溯路径。"""
401+
outside_path = os.path.join(
402+
os.path.dirname(logging_utils.PROJECT_ROOT), "site-packages", "worker.py"
403+
)
404+
record = _log_record(outside_path)
405+
406+
logging_utils.format_log_record(record)
407+
408+
assert record["file"].path == outside_path
409+
410+
350411
def test_generation_log_fragment_refreshes_within_half_a_second():
351412
"""日志轮询间隔不能退回到明显落后于终端输出的秒级刷新。"""
352413
assert webui_task.TASK_LOG_REFRESH_INTERVAL_SECONDS <= 0.5

0 commit comments

Comments
 (0)