Initial commit
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user