From 8c33b65786bbb7cd712783f6793e4fb32b1463be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Palancher?= Date: Thu, 2 Jul 2026 12:10:35 +0200 Subject: [PATCH 1/3] feat(log): support log levels higher than INFO fix #89 --- CHANGELOG.md | 1 + src/log/rfl/log/__init__.py | 20 +++++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a6b2d9..7ce625d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to - build: - Support setuptools.packages.find.where directive in setup converter. - Support setuptools.data-files directives in setup converter. +- log: Support log levels higher than INFO in `setup_logger()` (#89). ### Changed diff --git a/src/log/rfl/log/__init__.py b/src/log/rfl/log/__init__.py index 9e04c96..887c15a 100644 --- a/src/log/rfl/log/__init__.py +++ b/src/log/rfl/log/__init__.py @@ -4,7 +4,7 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from typing import Optional, List +from typing import Optional, List, Union import logging from .formatters import TTYFormatter, DaemonFormatter, auto_formatter @@ -22,18 +22,32 @@ def setup_logger( debug: bool = False, + level: Optional[Union[int, str]] = None, log_flags: Optional[List[str]] = None, 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 level, debug flags and formatter. + + Log level is resolved in this order: explicit level argument, then + debug=True (DEBUG), otherwise INFO. The debug flag also controls + formatter verbosity, independent of the resolved log level. When clear is True (default), existing root logger handlers are removed before adding a new one, similarly to logging.basicConfig(force=True). """ - if debug: + if level is not None: + if isinstance(level, str): + logging_level = getattr(logging, level.upper(), None) + if not isinstance(logging_level, int): + raise ValueError(f"Unknown log level: {level}") + elif isinstance(level, int): + logging_level = level + else: + raise TypeError(f"Unsupported log level type: {type(level).__name__}") + elif debug: logging_level = logging.DEBUG else: logging_level = logging.INFO From 05e375bab54c89f93c2ae82ac09c83b17bbee954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Palancher?= Date: Thu, 2 Jul 2026 12:11:17 +0200 Subject: [PATCH 2/3] tests(log): cover setup_logger(level=) --- src/log/rfl/tests/test_setup.py | 36 +++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/log/rfl/tests/test_setup.py b/src/log/rfl/tests/test_setup.py index 42496e6..aa59faa 100644 --- a/src/log/rfl/tests/test_setup.py +++ b/src/log/rfl/tests/test_setup.py @@ -48,6 +48,42 @@ def test_setup_debug(self): handler = logger.handlers[-1] self.assertEqual(handler.level, logging.DEBUG) + def test_setup_warning(self): + setup_logger(level=logging.WARNING) + logger = logging.getLogger() + self.assertEqual(logger.level, logging.WARNING) + handler = logger.handlers[-1] + self.assertEqual(handler.level, logging.WARNING) + + def test_setup_error(self): + setup_logger(level=logging.ERROR) + logger = logging.getLogger() + self.assertEqual(logger.level, logging.ERROR) + handler = logger.handlers[-1] + self.assertEqual(handler.level, logging.ERROR) + + def test_setup_level_string(self): + setup_logger(level="WARNING") + logger = logging.getLogger() + self.assertEqual(logger.level, logging.WARNING) + handler = logger.handlers[-1] + self.assertEqual(handler.level, logging.WARNING) + + def test_setup_level_overrides_debug(self): + setup_logger(level=logging.WARNING, debug=True) + logger = logging.getLogger() + self.assertEqual(logger.level, logging.WARNING) + handler = logger.handlers[-1] + self.assertEqual(handler.level, logging.WARNING) + + def test_setup_invalid_level(self): + with self.assertRaisesRegex(ValueError, r"Unknown log level: NOTALEVEL"): + setup_logger(level="NOTALEVEL") + + def test_setup_invalid_level_type(self): + with self.assertRaisesRegex(TypeError, r"Unsupported log level type: float"): + setup_logger(level=1.5) + def test_setup_filter(self): setup_logger(log_flags=["rfl"]) # get root logger From d5f7bcaaebcf42a692688262662bb01fda6ba9ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Palancher?= Date: Thu, 2 Jul 2026 12:11:39 +0200 Subject: [PATCH 3/3] docs(log): add documentation for setup_logger() --- src/log/README.md | 50 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/log/README.md b/src/log/README.md index fb99f81..c255a3b 100644 --- a/src/log/README.md +++ b/src/log/README.md @@ -1,3 +1,53 @@ # RFL: log package Logging utility. + +## setup_logger() + +Configure the root logger with a stream handler, formatter, and per-component +filtering. + +### Basic usage + +```python +import logging +from rfl.log import setup_logger + +setup_logger() # INFO level (default) +setup_logger(debug=True) # DEBUG level +setup_logger(level=logging.WARNING) # WARNING and above +setup_logger(level="ERROR") # string level names also accepted +``` + +### Log level + +Level is resolved in this order: + +1. `level` argument (int constant or string name: `DEBUG`, `INFO`, `WARNING`, + `ERROR`, `CRITICAL`) +2. `debug=True` → `DEBUG` (when `level` is omitted) +3. default → `INFO` + +The `debug` flag also controls formatter verbosity (detailed prefixes), independent +of the resolved log level. + +### Component filtering + +By default, only log records from components listed in `log_flags` are shown for +INFO and above. DEBUG records require `debug_flags`. + +```python +setup_logger(log_flags=["rfl"]) # INFO+ from rfl only +setup_logger(debug=True, debug_flags=["network"]) # DEBUG from network only +setup_logger(log_flags=["ALL"], debug_flags=["ALL"]) # show everything +``` + +Component names are derived from the first segment of the logger name +(e.g. `rfl.pkg.module` → `rfl`). + +### Other options + +- `component`: prefix log lines with a fixed component label in the formatter +- `formatter`: formatter class factory (default: TTY or daemon auto-selection) +- `clear`: when `True` (default), remove existing root handlers before setup; + set `clear=False` to stack handlers