diff --git a/src/serena/util/logging.py b/src/serena/util/logging.py index 75af996b..dae83c0a 100644 --- a/src/serena/util/logging.py +++ b/src/serena/util/logging.py @@ -1,11 +1,14 @@ import queue import threading from collections.abc import Callable +from typing import Optional from sensai.util import logging from serena.constants import SERENA_LOG_FORMAT +lg = logging + class MemoryLogHandler(logging.Handler): def __init__(self, level: int = logging.NOTSET) -> None: @@ -65,3 +68,42 @@ class LogBuffer: def get_log_messages(self) -> list[str]: with self._lock: return self._log_messages.copy() + + +class SuspendedLoggersContext: + """A context manager that provides an isolated logging environment. + + Temporarily removes all root log handlers upon entry, providing a clean slate + for defining new log handlers within the context. Upon exit, restores the original + logging configuration. This is useful when you need to temporarily configure + an isolated logging setup with well-defined log handlers. + + The context manager: + - Removes all existing (root) log handlers on entry + - Allows defining new temporary handlers within the context + - Restores the original configuration (handlers and root log level) on exit + + Example: + >>> with SuspendedLoggersContext(): + ... # No handlers are active here (configure your own and set desired log level) + ... pass + >>> # Original log handlers are restored here + + """ + + def __init__(self) -> None: + self.saved_root_handlers: list = [] + self.saved_root_level: Optional[int] = None + + def __enter__(self) -> "SuspendedLoggersContext": + root_logger = lg.getLogger() + self.saved_root_handlers = root_logger.handlers.copy() + self.saved_root_level = root_logger.level + root_logger.handlers.clear() + return self + + def __exit__(self, exc_type, exc_val, exc_tb) -> None: # type: ignore + root_logger = lg.getLogger() + root_logger.handlers = self.saved_root_handlers + if self.saved_root_level is not None: + root_logger.setLevel(self.saved_root_level) diff --git a/test/solidlsp/csharp/test_csharp_basic.py b/test/solidlsp/csharp/test_csharp_basic.py index 66772ef4..5bd7beb1 100644 --- a/test/solidlsp/csharp/test_csharp_basic.py +++ b/test/solidlsp/csharp/test_csharp_basic.py @@ -7,6 +7,7 @@ from unittest.mock import Mock, patch import pytest from sensai.util import logging +from serena.util.logging import SuspendedLoggersContext from solidlsp import SolidLanguageServer from solidlsp.language_servers.csharp_language_server import ( CSharpLanguageServer, @@ -256,7 +257,7 @@ class TestCSharpSolutionProjectOpening: mock_settings.ls_resources_dir = "/tmp/test_ls_resources" mock_settings.project_data_relative_path = "project_data" - with logging.SuspendedLoggersContext(): + with SuspendedLoggersContext(): logging.getLogger().setLevel(logging.DEBUG) with logging.MemoryLoggerContext() as mem_log: CSharpLanguageServer(mock_config, str(temp_path), mock_settings) @@ -285,7 +286,7 @@ class TestCSharpSolutionProjectOpening: mock_settings.project_data_relative_path = "project_data" # Create CSharpLanguageServer instance - with logging.SuspendedLoggersContext(): + with SuspendedLoggersContext(): logging.getLogger().setLevel(logging.DEBUG) with logging.MemoryLoggerContext() as mem_log: CSharpLanguageServer(mock_config, str(temp_path), mock_settings)