From fd3ef240a7a0a44075a459097dd8d9bebd0c3279 Mon Sep 17 00:00:00 2001 From: Frost Ming Date: Thu, 12 Nov 2026 09:55:00 +0800 Subject: [PATCH] refactor: use module-scoped loggers instead of the root logger Replace bare logging.* calls in the library with a per-module `_logger = logging.getLogger(__name__)` so records are emitted under the `acp.*` namespace and can be configured or silenced by applications. Co-Authored-By: Claude Opus 5.5 --- src/acp/connection.py | 20 +++++++++++--------- src/acp/stdio.py | 4 +++- src/acp/task/supervisor.py | 8 +++++--- src/acp/ws/server.py | 4 +++- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/acp/connection.py b/src/acp/connection.py index c054c9fd..2ef25a20 100644 --- a/src/acp/connection.py +++ b/src/acp/connection.py @@ -18,6 +18,8 @@ from .task import MessageSender, TaskSupervisor from .telemetry import span_context +_logger = logging.getLogger(__name__) + JsonValue = Any MethodHandler = Callable[[str, JsonValue | None, bool], Awaitable[JsonValue | None]] @@ -107,7 +109,7 @@ async def main_loop(self) -> None: try: await self._receive_loop() except Exception as exc: - logging.exception("Connection main loop failed", exc_info=exc) + _logger.exception("Connection main loop failed", exc_info=exc) self._on_receive_error(None, exc) # type: ignore[arg-type] raise @@ -190,7 +192,7 @@ def _notify_observers(self, direction: StreamDirection, message: dict[str, Any]) try: result = observer(event) except Exception: - logging.exception("Stream observer failed", exc_info=True) + _logger.exception("Stream observer failed", exc_info=True) continue if inspect.isawaitable(result): self._tasks.create( @@ -200,7 +202,7 @@ def _notify_observers(self, direction: StreamDirection, message: dict[str, Any]) ) def _on_observer_error(self, task: asyncio.Task[Any], exc: BaseException) -> None: - logging.exception("Stream observer coroutine failed", exc_info=exc) + _logger.exception("Stream observer coroutine failed", exc_info=exc) def _track_incoming(self, request_id: Any, task: asyncio.Task[Any]) -> None: # A ``$/cancel_request`` can only name a valid request id, and Python would alias an invalid @@ -237,7 +239,7 @@ def _send_cancel_request(self, request_id: int) -> None: ) def _on_cancel_request_error(self, task: asyncio.Task[Any], exc: BaseException) -> None: - logging.debug("Failed to send %s", _CANCEL_REQUEST_METHOD, exc_info=exc) + _logger.debug("Failed to send %s", _CANCEL_REQUEST_METHOD, exc_info=exc) async def _run_request( self, message: dict[str, Any], handler: asyncio.Future[dict[str, Any]] | None = None @@ -257,7 +259,7 @@ async def _run_request( except ConnectionError: if self._closed or self._disconnected: # The connection went away while the handler was running; nobody can receive it. - logging.debug("Dropping response for request %r: connection closed", message["id"]) + _logger.debug("Dropping response for request %r: connection closed", message["id"]) return raise self._notify_observers(StreamDirection.OUTGOING, payload) @@ -284,7 +286,7 @@ async def _execute_request(self, message: dict[str, Any]) -> dict[str, Any]: except ValidationError as exc: payload["error"] = RequestError.invalid_params({"errors": exc.errors()}).to_error_obj() except Exception as exc: - logging.exception( + _logger.exception( "Unhandled error while handling request method=%s", method, exc_info=exc, @@ -302,7 +304,7 @@ async def _run_notification(self, message: dict[str, Any]) -> None: try: await self._handler(method, message.get("params"), True) except Exception as exc: - logging.exception( + _logger.exception( "Unhandled error while handling notification method=%s", method, exc_info=exc, @@ -335,11 +337,11 @@ def _handle_response(self, message: dict[str, Any]) -> None: self._pending.pop(request_id, None) def _on_receive_error(self, task: asyncio.Task[Any], exc: BaseException) -> None: - logging.exception("Receive loop failed", exc_info=exc) + _logger.exception("Receive loop failed", exc_info=exc) self._disconnect() def _on_task_error(self, task: asyncio.Task[Any], exc: BaseException) -> None: - logging.exception("Background task failed", exc_info=exc) + _logger.exception("Background task failed", exc_info=exc) def _disconnect(self) -> None: if self._disconnected: diff --git a/src/acp/stdio.py b/src/acp/stdio.py index 99bd1570..7d3dac5c 100644 --- a/src/acp/stdio.py +++ b/src/acp/stdio.py @@ -18,6 +18,8 @@ from .interfaces import Agent, Client from .transports import spawn_stdio_transport +_logger = logging.getLogger(__name__) + __all__ = [ "spawn_agent_process", "spawn_client_process", @@ -76,7 +78,7 @@ def write(self, data: bytes) -> None: # type: ignore[override] sys.stdout.buffer.write(data) sys.stdout.buffer.flush() except Exception: - logging.exception("Error writing to stdout") + _logger.exception("Error writing to stdout") def can_write_eof(self) -> bool: # type: ignore[override] return False diff --git a/src/acp/task/supervisor.py b/src/acp/task/supervisor.py index 48765ad8..7dc61481 100644 --- a/src/acp/task/supervisor.py +++ b/src/acp/task/supervisor.py @@ -5,6 +5,8 @@ from collections.abc import Awaitable, Callable from typing import Any +_logger = logging.getLogger(__name__) + __all__ = ["TaskSupervisor"] ErrorHandler = Callable[[asyncio.Task[Any], BaseException], None] @@ -55,16 +57,16 @@ def _on_done(self, task: asyncio.Task[Any], on_error: ErrorHandler | None) -> No on_error(task, exc) handled = True except Exception: - logging.exception("Error in %s task-specific error handler", self._source) + _logger.exception("Error in %s task-specific error handler", self._source) if not handled: for handler in self._error_handlers: try: handler(task, exc) handled = True except Exception: - logging.exception("Error in %s supervisor error handler", self._source) + _logger.exception("Error in %s supervisor error handler", self._source) if not handled: - logging.exception("Unhandled error in %s task", self._source) + _logger.exception("Unhandled error in %s task", self._source) async def shutdown(self) -> None: self._closed = True diff --git a/src/acp/ws/server.py b/src/acp/ws/server.py index 0ae5dcb2..a11ce203 100644 --- a/src/acp/ws/server.py +++ b/src/acp/ws/server.py @@ -18,6 +18,8 @@ from ..experimental.negotiation import AgentProtocolRouter from ..http.server import AgentFactory +_logger = logging.getLogger(__name__) + __all__ = ["handle_websocket"] @@ -41,7 +43,7 @@ async def receive(self) -> dict[str, Any] | None: if event.get("text") is None: if event.get("bytes") is not None and not self._warned_binary: self._warned_binary = True - logging.warning("Ignoring binary WebSocket frame; ACP messages must be sent as text frames") + _logger.warning("Ignoring binary WebSocket frame; ACP messages must be sent as text frames") continue try: message = decode_message(event["text"])