import os import sys import logging import datetime import pytest from dotenv import load_dotenv, find_dotenv # Load environment variables load_dotenv(find_dotenv()) # Define a global format string for log alignment LOG_FORMAT = "%(asctime)s %(levelname)-8s: %(filename)-20s:%(funcName)-20s:%(lineno)-4d >>> %(message)s" # Define custom logging levels SUCCESS_LOG_LEVEL = 21 APP_LOG_LEVEL = 15 PROD_LOG_LEVEL = 14 DATABASE_LOG_LEVEL = 13 CHAT_LOG_LEVEL = 12 QUERY_LOG_LEVEL = 11 TESTING_LOG_LEVEL = 9 VARIABLES_LOG_LEVEL = 2 PEDANTIC_LOG_LEVEL = 1 # Register custom log levels CUSTOM_LEVELS = { SUCCESS_LOG_LEVEL: "SUCCESS", APP_LOG_LEVEL: "APP", PROD_LOG_LEVEL: "PROD", DATABASE_LOG_LEVEL: "DATABASE", CHAT_LOG_LEVEL: "CHAT", QUERY_LOG_LEVEL: "QUERY", TESTING_LOG_LEVEL: "TESTING", VARIABLES_LOG_LEVEL: "VARIABLES", PEDANTIC_LOG_LEVEL: "PEDANTIC" } for level, name in CUSTOM_LEVELS.items(): logging.addLevelName(level, name) # ANSI escape sequences for colors class LogColors: """ANSI escape sequences for various log levels to colorize log output.""" DARK_RED, BRIGHT_RED = '\033[31m', '\033[91m' ORANGE, GOLD, YELLOW = '\033[33m', '\033[93m', '\033[93m' GREEN, DARKGREEN, LIGHTGREEN = '\033[92m', '\033[32m', '\033[92m' LIGHTBLUE, BLUE, INDIGO, VIOLET = '\033[94m', '\033[94m', '\033[34m', '\033[35m' RESET, WHITE, BLACK = '\033[0m', '\033[37m', '\033[30m' # Custom Formatter class ColoredFormatter(logging.Formatter): """Custom formatter to add color and emojis to log messages for console output.""" COLORS = { logging.CRITICAL: f"{LogColors.DARK_RED}๐Ÿšจ ", logging.ERROR: f"{LogColors.BRIGHT_RED}โŒ ", logging.WARNING: f"{LogColors.ORANGE}โš ๏ธ ", logging.INFO: f"{LogColors.GREEN}โ„น๏ธ ", logging.DEBUG: f"{LogColors.BLUE}๐Ÿ› ", SUCCESS_LOG_LEVEL: f"{LogColors.LIGHTGREEN}โœ… ", APP_LOG_LEVEL: f"{LogColors.INDIGO}๐Ÿš€ ", PROD_LOG_LEVEL: f"{LogColors.VIOLET}๐Ÿญ ", DATABASE_LOG_LEVEL: f"{LogColors.DARKGREEN}๐Ÿ—„๏ธ ", CHAT_LOG_LEVEL: f"{LogColors.LIGHTBLUE}๐Ÿ’ฌ ", QUERY_LOG_LEVEL: f"{LogColors.GOLD}๐Ÿ” ", TESTING_LOG_LEVEL: f"{LogColors.YELLOW}๐Ÿงช ", VARIABLES_LOG_LEVEL: f"{LogColors.WHITE}๐Ÿ”ข ", PEDANTIC_LOG_LEVEL: f"{LogColors.BLACK}๐Ÿ”ฌ ", } def format(self, record): log_fmt = self.COLORS.get(record.levelno, self.COLORS[logging.INFO]) + self._fmt formatter = logging.Formatter(log_fmt) message = formatter.format(record) + LogColors.RESET try: return message except UnicodeEncodeError: return message.encode('ascii', 'replace').decode('ascii') def formatException(self, ei): try: return super().formatException(ei) except UnicodeEncodeError: return super().formatException(ei).encode('ascii', 'replace').decode('ascii') class FileFormatter(logging.Formatter): def format(self, record): return logging.Formatter(LOG_FORMAT).format(record) class PytestFormatter: @pytest.hookimpl(hookwrapper=True) def pytest_runtest_makereport(self, item, call): outcome = yield report = outcome.get_result() if report.when == "call": if report.passed: logging.success(f"โœ… Test passed: {item.name}") elif report.failed: logging.error(f"โŒ Test failed: {item.name}") elif report.skipped: logging.warning(f"โญ๏ธ Test skipped: {item.name}") # Custom logger methods def _add_custom_log_methods(): def create_log_method(level): def log_method(self, message, *args, **kws): if self.isEnabledFor(level): self._log(level, message, args, **kws) return log_method custom_methods = { "chat": CHAT_LOG_LEVEL, "variables": VARIABLES_LOG_LEVEL, "pedantic": PEDANTIC_LOG_LEVEL, "prod": PROD_LOG_LEVEL, "query": QUERY_LOG_LEVEL, "database": DATABASE_LOG_LEVEL, "testing": TESTING_LOG_LEVEL, "app": APP_LOG_LEVEL, "success": SUCCESS_LOG_LEVEL } for method_name, level in custom_methods.items(): setattr(logging.Logger, method_name, create_log_method(level)) _add_custom_log_methods() # Set an environment directory def set_log_path(env_var): env_path = os.getenv(env_var.upper()) if env_path and not os.path.exists(env_path): os.makedirs(env_path) return env_path # Get a logger with specified settings def get_logger(name=None, log_level=None, log_path=None, log_file=None, delimiter='_', runtime=False, log_format=None): logger = logging.getLogger(name or __name__) log_levels = { 'CRITICAL': logging.CRITICAL, 'ERROR': logging.ERROR, 'WARNING': logging.WARNING, 'SUCCESS': SUCCESS_LOG_LEVEL, 'APP': APP_LOG_LEVEL, 'PROD': PROD_LOG_LEVEL, 'DATABASE': DATABASE_LOG_LEVEL, 'INFO': logging.INFO, 'DEBUG': logging.DEBUG, 'TESTING': TESTING_LOG_LEVEL, 'CHAT': CHAT_LOG_LEVEL, 'QUERY': QUERY_LOG_LEVEL, 'VARIABLES': VARIABLES_LOG_LEVEL, 'PEDANTIC': PEDANTIC_LOG_LEVEL, } desired_level = log_levels.get(log_level, logging.INFO) if log_level else logger.level or logging.INFO logger.setLevel(desired_level) logger.propagate = False console_formatter = ColoredFormatter(LOG_FORMAT if log_format in (None, "default") else log_format) file_formatter = FileFormatter(LOG_FORMAT if log_format in (None, "default") else log_format) if not logger.handlers: if runtime: ch = logging.StreamHandler(sys.stdout) ch.setLevel(logging.DEBUG) ch.setFormatter(console_formatter) logger.addHandler(ch) if log_path and log_file: os.makedirs(log_path, exist_ok=True) log_file_path = os.path.join(log_path, f"{log_file}{'_' if runtime else delimiter}{datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S') if not runtime else ''}.log") fh = logging.FileHandler(log_file_path) fh.setLevel(desired_level) fh.setFormatter(file_formatter) logger.addHandler(fh) else: for handler in logger.handlers: handler.setLevel(desired_level) handler.setFormatter(file_formatter if isinstance(handler, logging.FileHandler) else console_formatter) return logger # Retrieve all loggers def get_loggers(): """Return a list of all logger instances.""" return [logging.getLogger(name) for name in logging.Logger.manager.loggerDict] # Initialize logger using provided settings def initialise_logger(log_name='backend', log_level=None, log_dir=None, log_format='default', runtime=True): log_level = log_level or os.getenv("LOG_LEVEL", "DEBUG") log_dir = log_dir or os.getenv("LOG_PATH", "/logs") return get_logger(name=log_name, log_level=log_level, log_path=log_dir, log_file=log_name, runtime=runtime, log_format=log_format)