From 61972397b4c486bdcd617e0c73b419fa6170e845 Mon Sep 17 00:00:00 2001 From: Haozhe Wu Date: Mon, 21 Sep 2026 20:33:54 +0800 Subject: [PATCH] test(channels): pin stdlib-style logging across src (#1531) After #1533, the napcat follow-up and #1529, no logger call in src passes a '{}' message with arguments and none calls loguru's .opt(). An AST scan now fails on either, with a probe proving the scan sees both forms. --- agent/tests/test_channel_stdlib_logging.py | 35 ++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/agent/tests/test_channel_stdlib_logging.py b/agent/tests/test_channel_stdlib_logging.py index 6cae83d5..f460f8c9 100644 --- a/agent/tests/test_channel_stdlib_logging.py +++ b/agent/tests/test_channel_stdlib_logging.py @@ -8,8 +8,11 @@ before anything is logged. from __future__ import annotations +import ast import asyncio import logging +import re +from pathlib import Path from types import SimpleNamespace import pytest @@ -34,3 +37,35 @@ def test_signal_safe_handle_logs_and_swallows(caplog) -> None: assert "Error in receive: boom" in record.getMessage() assert "100%" in record.getMessage() assert record.exc_info is not None + + +_SRC = Path(__file__).resolve().parents[1] / "src" +_BRACE_PLACEHOLDER = re.compile(r"\{(?:![rs])?(?::[^{}]*)?\}") +_LOG_METHODS = {"debug", "info", "warning", "warn", "error", "exception", "critical"} + + +def _loguru_style_calls(path: Path) -> list[str]: + found = [] + for node in ast.walk(ast.parse(path.read_text(encoding="utf-8"))): + if not (isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute)): + continue + if node.func.attr == "opt": + found.append(f"{path.name}:{node.lineno} .opt()") + elif node.func.attr in _LOG_METHODS and len(node.args) > 1: + first = node.args[0] + if isinstance(first, ast.Constant) and isinstance(first.value, str) and _BRACE_PLACEHOLDER.search(first.value): + found.append(f"{path.name}:{node.lineno} {first.value!r}") + return found + + +def test_no_loguru_style_logging_anywhere_in_src() -> None: + """The whole tree logs through stdlib (#1533, its napcat follow-up, #1529).""" + offenders = [hit for path in sorted(_SRC.rglob("*.py")) for hit in _loguru_style_calls(path)] + assert not offenders, offenders + + +def test_the_scan_sees_a_loguru_style_call(tmp_path: Path) -> None: + """Guard the guard: an empty scan would pass vacuously.""" + probe = tmp_path / "probe.py" + probe.write_text('logger.info("sent {} to {}", a, b)\nlogger.opt(exception=True).error("x")\n', encoding="utf-8") + assert len(_loguru_style_calls(probe)) == 2