# -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # File Name: logging_manager.py # Original Author: Clark Lin # Email: clark_lin@outlook.com # # Change History # Version Date By Description # 0.01 2024-03-18 Clark Lin Initial version # # Main features summary: # - Feature 1 # - Feature 2 # - Feature 3 # # Copyright Information: # Copyright © 2024 Oasis # Licensed TBD # ------------------------------------------------------------------------------ import json import logging from logging.handlers import TimedRotatingFileHandler import os import datetime class LoggingManager: _instance = None @staticmethod def get_instance(config_rel_path='../config/logging.json'): """Get the singleton instance of LoggingManager.""" if LoggingManager._instance is None: LoggingManager._instance = LoggingManager(config_rel_path) return LoggingManager._instance def __init__(self, config_rel_path='../config/logging.json'): """ Initialize the LoggingManager with the relative path to the configuration file. By default, the configuration file is assumed to be located at ../config/logging.json. Args: config_rel_path (str, optional): Relative path to the JSON configuration file. Defaults to '../config/logging.json'. """ if self._instance is not None: raise RuntimeError("LoggingManager is a singleton class. Use get_instance() instead of directly instantiating.") script_dir = os.path.dirname(os.path.abspath(__file__)) config_absolute_path = os.path.join(script_dir, config_rel_path) self.config = self.load_config(config_absolute_path) self.logger = self.setup_logger() def load_config(self, config_path): """ Load logging configuration from the provided JSON file. Args: config_path (str): Absolute path to the JSON configuration file. Returns: dict: The loaded logging configuration. """ with open(config_path, 'r') as f: config_data = json.load(f) return config_data.get('logging', {}) def setup_logger(self): """ Setup the logger with the configuration settings. Returns: logging.Logger: A configured logger instance. """ log_level = self.config.get('log_level', logging.INFO) log_dir = self.config.get('log_dir', './logs') os.makedirs(log_dir, exist_ok=True) # Use today's date to create a new file name each day today_date = datetime.datetime.today().strftime('%Y%m%d') filename = f"{today_date}.log" # Construct the full path for the log file log_file_path = os.path.join(log_dir, filename) logger = logging.getLogger(__name__) logger.setLevel(log_level) # Create a file handler specifically for today's log file file_handler = logging.FileHandler(log_file_path) file_handler.setLevel(log_level) # Create and set a log formatter formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') file_handler.setFormatter(formatter) logger.addHandler(file_handler) return logger def log(self, log_level, module, message, *args, **kwargs): """ Log a message at the specified log level. Args: log_level (int): One of the logging levels (e.g., logging.INFO). message (str): The log message to be recorded. *args, **kwargs: Additional arguments passed to the underlying logging function. Raises: ValueError: If the given log_level is invalid. """ if log_level not in [ logging.NOTSET, logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL, ]: raise ValueError(f'Invalid log level: {log_level}') # Init message message = module + ' ' + message # Append additional content arguments = list() for arg in args: arguments.append(arg) add_info = ','.join(arguments) if len(add_info) != 0: message += '(' + add_info + ')' else: pass # self.logger.log(log_level, message, *args, **kwargs) self.logger.log(log_level, message) # Example usage if __name__ == "__main__": lm = LoggingManager() lm.log(logging.INFO, 'This is an info message.') lm.log(logging.ERROR, 'An error has occurred.') # For dynamic log level change, you could add a method like this: # def set_log_level(self, new_level): # self.logger.setLevel(new_level) # And then call it: # lm.set_log_level(logging.DEBUG)