Skip to content
Merged
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ and this project adheres to
- build: Make `authors` optional in setup converter (omit `author` and
`author_email` when not specified).

### Fixed

- log: Clear handlers in `setup_logger()` by default to avoid duplicate log
lines, with optional clear=False to keep existing handlers (#88).

## [1.8.0] - 2026-05-22

### Added
Expand Down
11 changes: 10 additions & 1 deletion src/log/rfl/log/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,13 @@ def setup_logger(
debug_flags: Optional[List[str]] = None,
formatter: logging.Formatter = auto_formatter(),
component: Optional[str] = None,
clear: bool = True,
) -> None:
"""Setup root logger debug level, debug flags and formatter."""
"""Setup root logger debug level, debug flags and formatter.

When clear is True (default), existing root logger handlers are removed
before adding a new one, similarly to logging.basicConfig(force=True).
"""
if debug:
logging_level = logging.DEBUG
else:
Expand All @@ -39,6 +44,10 @@ def setup_logger(
debug_flags = []

root_logger = logging.getLogger()
if clear:
for existing_handler in root_logger.handlers[:]:
root_logger.removeHandler(existing_handler)
existing_handler.close()
root_logger.setLevel(logging_level)
handler = logging.StreamHandler()
handler.setLevel(logging_level)
Expand Down
15 changes: 15 additions & 0 deletions src/log/rfl/tests/test_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,21 @@ def test_setup_filter_debug_all(self):
handler, {"name": "fail.pkg", "levelno": logging.DEBUG, "msg": "test"}
)

def test_setup_clears_handlers(self):
setup_logger()
setup_logger()
logger = logging.getLogger()
self.assertEqual(len(logger.handlers), 1)

def test_setup_clear_false_stacks_handlers(self):
root_logger = logging.getLogger()
for handler in root_logger.handlers[:]:
root_logger.removeHandler(handler)
handler.close()
setup_logger(clear=False)
setup_logger(clear=False)
self.assertEqual(len(root_logger.handlers), 2)


class TestEnforceLogger(TestLogFilterer):
def setUp(self):
Expand Down
Loading