Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions src/openhound/core/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,13 @@ def _is_valid_path(base_path: Path, spec_path: Path) -> Path:
)
return resolved_spec_path

@staticmethod
def _close_handlers(logger: logging.Logger) -> None:
"""Remove and close all handlers attached directly to a logger."""
for handler in logger.handlers[:]:
logger.removeHandler(handler)
handler.close()

@staticmethod
def default_platform_path() -> Path:
"""Get the default log path based on platform
Expand Down Expand Up @@ -260,7 +267,7 @@ def set_handler(self, name: str) -> None:
log_file_path = self.base_path / f"ext_{name}.log"
valid_path = self._is_valid_path(self.base_path, log_file_path)
self.dlt_logger.setLevel(self.root_logger.level)
self.dlt_logger.handlers.clear()
self._close_handlers(self.dlt_logger)
self.handlers[self.runtime_mode](self.dlt_logger, valid_path)
self.dlt_logger.propagate = False

Expand Down Expand Up @@ -293,14 +300,14 @@ def setup(self) -> None:
self.backup_count = dlt_backup_count if dlt_backup_count else self.backup_count
self.interval = dlt_interval if dlt_interval else self.interval

# Clear the default DLT log handlers and set our own handlers based on the runtime mode
self.dlt_logger.handlers.clear()
self.handlers[self.runtime_mode](self.dlt_logger, self.log_file_path)
self.dlt_logger.propagate = False
# The root logger owns the default OpenHound file handler. DLT logs propagate
# there until set_handler routes them to an extension-specific log file.
self._close_handlers(self.dlt_logger)
self.dlt_logger.setLevel(getattr(logging, self.level))
self.dlt_logger.propagate = True

# Set the root logger level and handlers based on the runtime mode
self.root_logger.setLevel(getattr(logging, self.level))
self.root_logger.handlers.clear()
self._close_handlers(self.root_logger)
self.handlers[self.runtime_mode](self.root_logger, self.log_file_path)

def container_handlers(self, logger: logging.Logger, file_path: Path) -> None:
Expand Down
38 changes: 25 additions & 13 deletions tests/test_log_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,50 @@
from openhound.core.logging import RotatingFileHandler, logger_override


def _rotating_handlers(logger: logging.Logger) -> list[RotatingFileHandler]:
return [
handler
for handler in logger.handlers
if isinstance(handler, RotatingFileHandler)
]


def test_root_handler_setup():
"""Test that the root logger has a handler configured and that it is a RotatingFileHandler"""
root_logger = logging.getLogger()
assert len(root_logger.handlers) > 0, (
"The root logger should have at least one handler configured"
)

base_handler = root_logger.handlers[0]
assert isinstance(base_handler, RotatingFileHandler), (
rotating_handlers = _rotating_handlers(root_logger)
assert len(rotating_handlers) == 1, (
"The root logger should use RotatingFileHandler"
)
base_handler = rotating_handlers[0]
assert base_handler.baseFilename.endswith("openhound.log"), (
"The root logger should log to 'openhound.log'"
)


def test_dlt_handler_setup():
"""Test that the default DLT handler is overwritten by our RotatingFileHandler"""
def test_dlt_handler_setup(tmp_path):
"""Test that default DLT logs propagate to the root OpenHound handler."""
logger_override.base_path = tmp_path
logger_override.setup()

dlt_logger = logging.getLogger("dlt")
assert len(dlt_logger.handlers) > 0, (
"The default DLT logger should have at least one handler configured"
assert len(dlt_logger.handlers) == 0, (
"The default DLT logger should not own a separate file handler"
)
assert dlt_logger.propagate is False, (
"The DLT logger should have propagation disabled to prevent duplicate logs in the root logger"
assert dlt_logger.propagate is True, (
"The DLT logger should propagate to the root OpenHound handler"
)

base_handler = dlt_logger.handlers[0]
assert isinstance(base_handler, RotatingFileHandler), (
"The default DLT logger should use RotatingFileHandler"
root_handlers = _rotating_handlers(logging.getLogger())
assert len(root_handlers) == 1, (
"Only the root logger should own the default OpenHound file handler"
)
assert base_handler.baseFilename.endswith("openhound.log"), (
"The default DLT logger should log to 'openhound.log'"
assert root_handlers[0].baseFilename.endswith("openhound.log"), (
"Default DLT logs should flow to 'openhound.log'"
)


Expand Down
Loading