From f1f39c71f2bc24437349debc9414f580399706fd Mon Sep 17 00:00:00 2001 From: Rightup Date: Thu, 16 Jul 2026 16:06:18 +0100 Subject: [PATCH] refactor(logging): enhance logging configuration and add TRACE level support --- config.yaml.example | 2 +- repeater/config_manager.py | 46 +++++++++++++++++++++++++++++++++++++- repeater/logging_utils.py | 27 ++++++++++++++++++++++ repeater/main.py | 7 +++--- 4 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 repeater/logging_utils.py diff --git a/config.yaml.example b/config.yaml.example index f404dd7..a58ca91 100644 --- a/config.yaml.example +++ b/config.yaml.example @@ -628,7 +628,7 @@ glass: cert_store_dir: "/etc/openhop_repeater/glass" logging: - # Log level: DEBUG, INFO, WARNING, ERROR + # Log level: TRACE, DEBUG, INFO, WARNING, ERROR level: INFO # Log format diff --git a/repeater/config_manager.py b/repeater/config_manager.py index 53f981c..013d80d 100644 --- a/repeater/config_manager.py +++ b/repeater/config_manager.py @@ -4,6 +4,8 @@ from typing import Any, Dict, List, Optional import yaml +from repeater.logging_utils import normalize_log_level + logger = logging.getLogger("ConfigManager") @@ -192,6 +194,36 @@ class ConfigManager: logger.warning("Failed live HTTP config apply: %s", message) return success + def _apply_live_logging_config(self) -> bool: + if not self.daemon: + logger.warning("Daemon not available for logging live update") + return False + + logging_cfg = self.config.get("logging", {}) if isinstance(self.config, dict) else {} + level = normalize_log_level(logging_cfg.get("level", "INFO")) + + root_logger = logging.getLogger() + root_logger.setLevel(level) + + repeater_logger = logging.getLogger("RepeaterDaemon") + repeater_logger.setLevel(level) + + sx1262_logger = logging.getLogger("SX1262_wrapper") + sx1262_logger.setLevel(level) + + mqtt_logger = logging.getLogger("MQTTHandler") + mqtt_logger.setLevel(level) + + buffer = getattr(self.daemon, "_log_buffer", None) + if buffer is not None: + buffer.setLevel(level) + + logger.info( + "Applied live logging config: level=%s", + logging.getLevelName(level) if isinstance(level, int) else level, + ) + return True + def save_to_file(self) -> bool: """ Save current config to YAML file. @@ -240,7 +272,16 @@ class ConfigManager: # Default sections to update if not specified if sections is None: - sections = ["repeater", "delays", "radio", "acl", "identities", "glass", "http"] + sections = [ + "repeater", + "delays", + "radio", + "acl", + "identities", + "glass", + "http", + "logging", + ] # Update each section for section in sections: @@ -306,6 +347,9 @@ class ConfigManager: if "http" in sections: live_update_ok = self._apply_live_http_config() and live_update_ok + if "logging" in sections: + live_update_ok = self._apply_live_logging_config() and live_update_ok + return live_update_ok except Exception as e: diff --git a/repeater/logging_utils.py b/repeater/logging_utils.py new file mode 100644 index 0000000..a95e87d --- /dev/null +++ b/repeater/logging_utils.py @@ -0,0 +1,27 @@ +import logging +from typing import Any + +TRACE_LEVEL = 5 +logging.addLevelName(TRACE_LEVEL, "TRACE") + + +def normalize_log_level(level: Any) -> int: + if isinstance(level, int): + return level + if not isinstance(level, str): + return logging.INFO + + level_name = level.strip().upper() + if not level_name: + return logging.INFO + if level_name == "TRACE": + return TRACE_LEVEL + + resolved = getattr(logging, level_name, None) + if isinstance(resolved, int): + return resolved + + try: + return int(level_name) + except ValueError: + return logging.INFO diff --git a/repeater/main.py b/repeater/main.py index e48e894..958df86 100644 --- a/repeater/main.py +++ b/repeater/main.py @@ -36,6 +36,7 @@ from repeater.packet_router import PacketRouter from repeater.sensors import SensorManager from repeater.utils_packet import create_scoped_advert_packet from repeater.web.http_server import HTTPStatsServer, _log_buffer +from repeater.logging_utils import normalize_log_level from openhop_core.companion.radio_capabilities import resolve_max_tx_power_dbm from openhop_core.protocol.constants import PAYLOAD_TYPE_RAW_CUSTOM @@ -115,14 +116,14 @@ class RepeaterDaemon: self.radio_status = "unknown" self.radio_error = None - log_level = config.get("logging", {}).get("level", "INFO") + log_level = normalize_log_level(config.get("logging", {}).get("level", "INFO")) logging.basicConfig( - level=getattr(logging, log_level), + level=log_level, format=config.get("logging", {}).get("format"), ) root_logger = logging.getLogger() - _log_buffer.setLevel(getattr(logging, log_level)) + _log_buffer.setLevel(log_level) root_logger.addHandler(_log_buffer) def _configured_identity_specs(self, identity_type: str) -> list[IdentitySpec]: