fix(agent): preserve multiline realtime logs

This commit is contained in:
jxxghp
2026-09-11 12:08:47 +08:00
parent 5d3663b42d
commit d15ca4be18
2 changed files with 65 additions and 4 deletions

View File

@@ -121,15 +121,27 @@ class _FileLogAdapter(SystemLogPort):
async with aiofiles.open(path, mode="r", encoding="utf-8", errors="replace") as file:
await file.seek(0, 2)
previous_size = (await asyncio.to_thread(path.stat)).st_size
pending = ""
while not runtime_stop_state.is_system_stopped:
if await disconnected():
break
current_size = (await asyncio.to_thread(path.stat)).st_size
if current_size < previous_size:
await file.seek(0)
previous_size = 0
pending = ""
if current_size > previous_size:
line = (await file.readline()).strip()
if line:
yield line
previous_size = current_size
pending += await file.read()
previous_size = (await asyncio.to_thread(path.stat)).st_size
fragments = pending.splitlines(keepends=True)
if fragments and not fragments[-1].endswith(("\n", "\r")):
pending = fragments.pop()
else:
pending = ""
for fragment in fragments:
line = fragment.strip()
if line:
yield line
else:
await asyncio.sleep(0.5)

View File

@@ -0,0 +1,49 @@
"""验证 Agent 多行日志在实时日志轮询中的传输完整性。"""
import asyncio
from pathlib import Path
from types import SimpleNamespace
import pytest
from app.startup.composition.system import _FileLogAdapter
@pytest.mark.asyncio
async def test_file_log_follow_drains_all_lines_from_one_append(tmp_path: Path) -> None:
"""一次写入包含多行时,实时跟随器应逐行转发全部新增内容。"""
log_path = tmp_path / "moviepilot.log"
log_path.touch()
adapter = _FileLogAdapter(SimpleNamespace(get=lambda key: tmp_path))
async def is_disconnected() -> bool:
"""保持测试连接,直到断言收集到全部新增日志。"""
return False
stream = await adapter.follow("moviepilot.log", 50, is_disconnected)
collected: list[str] = []
async def collect_lines() -> None:
"""收集同一次写入产生的三行日志。"""
async for line in stream:
collected.append(line)
if len(collected) == 3:
break
collector = asyncio.create_task(collect_lines())
await asyncio.sleep(0.05)
log_path.write_text(
"【INFO】 2026-09-11 11:39:36,300 callback - Agent消息: 首段\n"
"...\n"
"⚙️ => 读取技能说明:moviepilot-api\n",
encoding="utf-8",
)
await asyncio.wait_for(collector, timeout=2)
await stream.aclose()
assert collected == [
"【INFO】 2026-09-11 11:39:36,300 callback - Agent消息: 首段",
"...",
"⚙️ => 读取技能说明:moviepilot-api",
]