import logging import logging.config import os import yaml import zmq from zmq.log.handlers import PUBHandler from control_backend.core.config import settings def add_logging_level(level_name: str, level_num: int, method_name: str | None = None) -> None: """ Adds a logging level to the `logging` module and the currently configured logging class. """ if not method_name: method_name = level_name.lower() if hasattr(logging, level_name): raise AttributeError(f"{level_name} already defined in logging module") if hasattr(logging, method_name): raise AttributeError(f"{method_name} already defined in logging module") if hasattr(logging.getLoggerClass(), method_name): raise AttributeError(f"{method_name} already defined in logger class") def log_for_level(self, message, *args, **kwargs): if self.isEnabledFor(level_num): self._log(level_num, message, args, **kwargs) def log_to_root(message, *args, **kwargs): logging.log(level_num, message, *args, **kwargs) logging.addLevelName(level_num, level_name) setattr(logging, level_name, level_num) setattr(logging.getLoggerClass(), method_name, log_for_level) setattr(logging, method_name, log_to_root) def setup_logging(path: str = settings.logging_settings.logging_config_file) -> None: """ Setup logging configuration of the CB. Tries to load the logging configuration from a file, in which we specify custom loggers, formatters, handlers, etc. :param path: :return: """ if os.path.exists(path): with open(path) as f: try: config = yaml.safe_load(f.read()) except (AttributeError, yaml.YAMLError) as e: logging.warning(f"Could not load logging configuration: {e}") config = {} custom_levels = config.get("custom_levels", {}) or {} for level_name, level_num in custom_levels.items(): add_logging_level(level_name, level_num) if config.get("handlers") is not None and config.get("handlers").get("ui"): pub_socket = zmq.Context.instance().socket(zmq.PUB) pub_socket.connect(settings.zmq_settings.internal_pub_address) config["handlers"]["ui"]["interface_or_socket"] = pub_socket logging.config.dictConfig(config) # Patch ZMQ PUBHandler to know about custom levels if custom_levels: for logger_name in config.get("loggers", {}): logger = logging.getLogger(logger_name) for handler in logger.handlers: if isinstance(handler, PUBHandler): # Use the INFO formatter as the default template default_fmt = handler.formatters[logging.INFO] for level_num in custom_levels.values(): handler.setFormatter(default_fmt, level=level_num) else: logging.warning("Logging config file not found. Using default logging configuration.")