import time import json import sys import traceback # Store the start time globally _bot_start_time = time.time() def get_bot_start_time(): """Retrieve the bot's start time globally.""" return _bot_start_time def load_config_file(): CONFIG_PATH = "config.json" try: with open(CONFIG_PATH, "r") as f: config_data = json.load(f) return config_data except FileNotFoundError: print("Error: config.json not found.") sys.exit(1) except json.JSONDecodeError as e: print(f"Error parsing config.json: {e}") sys.exit(1) # Load configuration file config_data = load_config_file() ############################### # Simple Logging System ############################### def log(message, level="INFO", exec_info=False): """ A simple logging function with adjustable log levels. Logs messages in a structured format. Available levels:\n DEBUG = Information useful for debugging\n INFO = Informational messages\n WARNING = Something happened that may lead to issues\n ERROR = A non-critical error has happened\n CRITICAL = A critical, but non-fatal, error\n FATAL = Fatal error. Program exits after logging this\n\n See 'config.json' for disabling/enabling logging levels """ # Initiate logfile lfp = config_data["logging"]["logfile_path"] # Log File Path clfp = f"cur_{lfp}" # Current Log File Path if not config_data["logging"]["terminal"]["log_to_terminal"] and not config_data["logging"]["file"]["log_to_file"]: print(f"!!! WARNING !!! CONSOLE AND LOGFILE OUTPUT DISABLED !!!\n!!! NO LOGS WILL BE PROVIDED !!!") from modules import utility log_levels = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", "FATAL"} if level not in log_levels: level = "INFO" # Default to INFO if an invalid level is provided if level in config_data["logging"]["log_levels"] or level == "FATAL": elapsed = time.time() - get_bot_start_time() uptime_str, _ = utility.format_uptime(elapsed) timestamp = time.strftime('%Y-%m-%d %H:%M:%S') log_message = f"[{timestamp} - {uptime_str}] [{level}] {message}" # Include traceback for certain error levels if exec_info or level in ["CRITICAL", "FATAL"]: log_message += f"\n{traceback.format_exc()}" # Print to terminal if enabled # 'FATAL' errors override settings # Checks config file to see enabled/disabled logging levels if config_data["logging"]["terminal"]["log_to_terminal"] or level == "FATAL": config_level_format = f"log_{level.lower()}" if config_data["logging"]["terminal"][config_level_format] or level == "FATAL": print(log_message) # Write to file if enabled # 'FATAL' errors override settings # Checks config file to see enabled/disabled logging levels if config_data["logging"]["file"]["log_to_file"] or level == "FATAL": config_level_format = f"log_{level.lower()}" if config_data["logging"]["file"][config_level_format] or level == "FATAL": try: lfp = config_data["logging"]["logfile_path"] clfp = f"cur_{lfp}" with open(lfp, "a", encoding="utf-8") as logfile: # Write to permanent logfile logfile.write(f"{log_message}\n") logfile.flush() # Ensure it gets written immediately with open(clfp, "a", encoding="utf-8") as c_logfile: # Write to this-run logfile c_logfile.write(f"{log_message}\n") c_logfile.flush() # Ensure it gets written immediately except Exception as e: print(f"[WARNING] Failed to write to logfile: {e}") # Handle fatal errors with shutdown if level == "FATAL": print(f"!!! FATAL ERROR LOGGED, SHUTTING DOWN !!!") sys.exit(1) def reset_curlogfile(): # Initiate logfile lfp = config_data["logging"]["logfile_path"] # Log File Path clfp = f"cur_{lfp}" # Current Log File Path try: open(clfp, "w") #log(f"Current-run logfile cleared", "DEBUG") except Exception as e: #log(f"Failed to clear current-run logfile: {e}") pass def init_db_conn(): try: import modules.db db_conn = modules.db.init_db_connection(config_data) if not db_conn: # If we get None, it means FATAL. We might sys.exit(1) or handle it differently. log("Terminating bot due to no DB connection.", "FATAL") sys.exit(1) return db_conn except Exception as e: log(f"Unable to initialize database!: {e}", "FATAL") return None