### # Copyright 2016-2025 Hewlett Packard Enterprise, Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ### # -*- coding: utf-8 -*- """Debug utilities for iLOrest""" import json import os import logging try: from logging_config_path import get_logging_config_path except ModuleNotFoundError: from ilorest.logging_config_path import get_logging_config_path def is_debug_enabled(config_path=None): """ Check if debug logging is enabled by reading the logging configuration file. Args: config_path (str, optional): Path to the logging config file. Defaults to the standard config location. Returns: bool: True if debug logging is enabled, False otherwise. """ if config_path is None: config_path = get_logging_config_path() try: # Check if config file exists if not os.path.isfile(config_path): # If config file doesn't exist, assume debug is disabled return False # Load the configuration file with open(config_path, "r", encoding="utf-8") as f: config = json.load(f) # Check the root logger level first loggers = config.get("loggers", {}) root_logger = loggers.get("", {}) root_level = root_logger.get("level", "INFO").upper() # If root logger is set to DEBUG, debug is enabled if root_level == "DEBUG": return True # Check if any specific loggers have DEBUG level for logger_name, logger_config in loggers.items(): if logger_name == "": # Skip root logger, already checked continue level = logger_config.get("level", "INFO").upper() if level == "DEBUG": return True # Check handlers for DEBUG level handlers = config.get("handlers", {}) for handler_name, handler_config in handlers.items(): level = handler_config.get("level", "INFO").upper() if level == "DEBUG": return True # If no DEBUG level found anywhere, debug is disabled return False except (FileNotFoundError, json.JSONDecodeError, KeyError, ValueError): # If there's any error reading the config, assume debug is disabled return False except Exception: # For any other unexpected error, assume debug is disabled return False def get_current_log_level(logger_name="", config_path=None): """ Get the current log level for a specific logger from the configuration file. Args: logger_name (str): Name of the logger. Empty string for root logger. config_path (str, optional): Path to the logging config file. Returns: str: The log level (e.g., 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'). Returns 'INFO' as default if not found. """ if config_path is None: config_path = get_logging_config_path() try: # Check if config file exists if not os.path.isfile(config_path): return "INFO" # Load the configuration file with open(config_path, "r", encoding="utf-8") as f: config = json.load(f) # Get the specific logger configuration loggers = config.get("loggers", {}) logger_config = loggers.get(logger_name, {}) # Return the level, defaulting to INFO return logger_config.get("level", "INFO").upper() except Exception: # For any error, return INFO as default return "INFO" def set_debug_level_in_config(enable_debug=True, config_path=None): """ Update the logging configuration file to enable or disable debug logging. Args: enable_debug (bool): True to enable debug, False to disable. config_path (str, optional): Path to the logging config file. Returns: bool: True if successful, False otherwise. """ if config_path is None: config_path = get_logging_config_path() try: # Check if config file exists if not os.path.isfile(config_path): return False # Load the configuration file with open(config_path, "r", encoding="utf-8") as f: config = json.load(f) # Set the desired level new_level = "DEBUG" if enable_debug else "INFO" # Update root logger level if "loggers" not in config: config["loggers"] = {} if "" not in config["loggers"]: config["loggers"][""] = {} config["loggers"][""]["level"] = new_level # Update all other loggers to the same level for logger_name in config["loggers"]: config["loggers"][logger_name]["level"] = new_level # Update handlers to the same level if "handlers" in config: for handler_name in config["handlers"]: config["handlers"][handler_name]["level"] = new_level # Write the updated configuration back to file with open(config_path, "w", encoding="utf-8") as f: json.dump(config, f, indent=2) return True except PermissionError as e: logging.warning("Administrator privileges are required to save the configuration") return False except Exception as e: logging.debug(f"Unexpected error in set_debug_level_in_config: {e}") return False def set_file_handlers_level(root_logger=None, level=logging.INFO): """Set level for any FileHandler-like handlers on the provided logger. Args: root_logger (logging.Logger): Logger instance to operate on. If None, uses logging.getLogger(). level (int): Level to set for file handlers. Returns: int: Number of handlers updated. """ updated = 0 try: if root_logger is None: root_logger = logging.getLogger() for h in list(root_logger.handlers): try: if isinstance(h, logging.FileHandler) or getattr(h, "baseFilename", None): h.setLevel(level) updated += 1 except Exception: pass return updated except Exception: return 0 def set_named_handler_level_if_exists(root_logger=None, name="logconfig_stdout", level=logging.INFO): """Set level on a named handler only if it exists; do not create handlers. Args: root_logger (logging.Logger): Logger to operate on (defaults to root logger). name (str): Handler name to look for. level (int): Level to set. Returns: bool: True if handler level was updated, False otherwise. """ try: if root_logger is None: root_logger = logging.getLogger() for h in list(root_logger.handlers): if getattr(h, "name", None) == name: try: h.setLevel(level) return True except Exception: return False return False except Exception: return False def update_config_handlers_levels(config_path=None, level_str="INFO", handler_names=None): """Update handler levels in the JSON logging configuration file. Args: config_path (str): Path to the logging_config.json file. level_str (str): Level string to set (e.g., 'DEBUG', 'INFO'). handler_names (iterable|None): If provided, only update these handler names; otherwise update all handlers. Returns: bool: True if update succeeded, False otherwise. """ if config_path is None: config_path = get_logging_config_path() try: if not os.path.isfile(config_path): return False with open(config_path, "r", encoding="utf-8") as f: config = json.load(f) if "handlers" not in config: config["handlers"] = {} for hname in list(config["handlers"].keys()): if handler_names is None or hname in handler_names: try: config["handlers"][hname]["level"] = level_str except Exception: pass with open(config_path, "w", encoding="utf-8") as f: json.dump(config, f, indent=2) return True except PermissionError as e: logging.warning("Administrator privileges are required to save the configuration") return False except Exception as e: logging.debug(f"Unexpected error in update_config_handlers_levels: {e}") return False def update_config_loggers_levels(config_path=None, level_str="INFO"): """Update all logger levels in the JSON logging configuration file. Args: config_path (str): Path to the logging_config.json file. level_str (str): Level string to set for loggers. Returns: bool: True if update succeeded, False otherwise. """ if config_path is None: config_path = get_logging_config_path() try: if not os.path.isfile(config_path): return False with open(config_path, "r", encoding="utf-8") as f: config = json.load(f) if "loggers" not in config: config["loggers"] = {} # Set root logger if "" not in config["loggers"]: config["loggers"][""] = {} config["loggers"][""]["level"] = level_str # Set all other loggers to the same level for lname in list(config["loggers"].keys()): try: config["loggers"][lname]["level"] = level_str except Exception: pass with open(config_path, "w", encoding="utf-8") as f: json.dump(config, f, indent=2) return True except PermissionError as e: logging.warning("Administrator privileges are required to save the configuration") return False except Exception as e: logging.debug(f"Unexpected error in update_config_loggers_levels: {e}") return False def set_runtime_logger_levels(root_logger=None, runtime_level=logging.INFO, set_module_logger=None): """Set runtime logger and handler levels consistently. Args: root_logger (logging.Logger): Logger instance to operate on. Defaults to root logger. runtime_level (int): Logging level (e.g., logging.DEBUG). set_module_logger (logging.Logger|None): Optional module-level logger to set (e.g., rdmc helper LOGGER). Returns: bool: True if operations completed (best-effort), False on failure. """ try: if root_logger is None: root_logger = logging.getLogger() root_logger.setLevel(runtime_level) # Update named stdout handler if present set_named_handler_level_if_exists(root_logger, "logconfig_stdout", runtime_level) # Update file handlers level set_file_handlers_level(root_logger, runtime_level) if set_module_logger is not None: try: set_module_logger.setLevel(runtime_level) except Exception: pass return True except Exception: return False def update_config_file_rotation_settings(config_path=None, max_bytes=None, backup_count=None): """Update file rotation settings for RotatingFileHandler in the JSON logging configuration file. Args: config_path (str): Path to the logging_config.json file. max_bytes (int|None): Maximum size of log file in bytes before rotation. backup_count (int|None): Number of backup files to keep. Returns: bool: True if update succeeded, False otherwise. """ if config_path is None: config_path = get_logging_config_path() try: if not os.path.isfile(config_path): return False with open(config_path, "r", encoding="utf-8") as f: config = json.load(f) if "handlers" not in config: config["handlers"] = {} # Update rotation settings for RotatingFileHandler instances updated = False for handler_name, handler_config in config["handlers"].items(): if not isinstance(handler_config, dict): continue handler_class = handler_config.get("class", "") if "RotatingFileHandler" in handler_class: if max_bytes is not None: handler_config["maxBytes"] = max_bytes updated = True if backup_count is not None: handler_config["backupCount"] = backup_count updated = True if updated: with open(config_path, "w", encoding="utf-8") as f: json.dump(config, f, indent=2) return updated except PermissionError as e: logging.warning("Administrator privileges are required to save the configuration") return False except Exception as e: logging.debug(f"Unexpected error in update_config_file_rotation_settings: {e}") return False def get_config_file_rotation_settings(config_path=None): """Get current file rotation settings from the JSON logging configuration file. Args: config_path (str): Path to the logging_config.json file. Returns: dict: Dictionary containing rotation settings for each RotatingFileHandler. Format: {handler_name: {'maxBytes': value, 'backupCount': value}} """ if config_path is None: config_path = get_logging_config_path() rotation_settings = {} try: if not os.path.isfile(config_path): return rotation_settings with open(config_path, "r", encoding="utf-8") as f: config = json.load(f) handlers = config.get("handlers", {}) for handler_name, handler_config in handlers.items(): if not isinstance(handler_config, dict): continue handler_class = handler_config.get("class", "") if "RotatingFileHandler" in handler_class: rotation_settings[handler_name] = { "maxBytes": handler_config.get("maxBytes"), "backupCount": handler_config.get("backupCount"), } return rotation_settings except Exception: return rotation_settings def validate_rotation_parameters(max_bytes=None, backup_count=None): """Validate file rotation parameters. Args: max_bytes (int|None): Maximum size of log file in bytes before rotation. backup_count (int|None): Number of backup files to keep. Returns: tuple: (is_valid, error_message) where is_valid is bool and error_message is str or None. """ try: if max_bytes is not None: if not isinstance(max_bytes, int): return False, "log_file_size must be an integer" if max_bytes <= 0: return False, "log_file_size must be a positive integer" if backup_count is not None: if not isinstance(backup_count, int): return False, "backup_count must be an integer" if backup_count < 0: return False, "backup_count must be a non-negative integer" return True, None except Exception: return False, "Invalid rotation parameters"