125 lines
4.2 KiB
Python
125 lines
4.2 KiB
Python
import logging
|
||
import json
|
||
import traceback
|
||
from logging.handlers import RotatingFileHandler
|
||
from components.utils.datetimes import datetime, timedelta
|
||
|
||
SUCCESS_LEVEL = 25
|
||
CRITICAL_LEVEL = 50
|
||
logging.addLevelName(SUCCESS_LEVEL, "SUCCESS")
|
||
logging.addLevelName(CRITICAL_LEVEL, "CRITICAL")
|
||
|
||
LOG_COLORS = {
|
||
"DEBUG": "\033[94m",
|
||
"INFO": "\033[96m",
|
||
"SUCCESS": "\033[92m",
|
||
"WARNING": "\033[93m",
|
||
"ERROR": "\033[91m",
|
||
"CRITICAL": "\033[95m",
|
||
"RESET": "\033[0m",
|
||
"BOLD": "\033[1m",
|
||
}
|
||
|
||
|
||
class JSONFormatter(logging.Formatter):
|
||
def __init__(self, text):
|
||
super().__init__()
|
||
self.text = text
|
||
|
||
def format(self, record):
|
||
exc_text = None
|
||
if record.exc_info:
|
||
exc_text = traceback.format_exc()
|
||
|
||
log_entry = {
|
||
"text": self.text,
|
||
"record": {
|
||
"elapsed": {
|
||
"repr": str(timedelta(seconds=record.relativeCreated / 1000)),
|
||
"seconds": record.relativeCreated / 1000,
|
||
},
|
||
"exception": exc_text,
|
||
"extra": record.__dict__.get("extra", {}),
|
||
"file": {"name": record.filename, "path": record.pathname},
|
||
"function": record.funcName,
|
||
"level": {
|
||
"icon": "✅"
|
||
if record.levelno == SUCCESS_LEVEL
|
||
else "ℹ️"
|
||
if record.levelno == logging.INFO
|
||
else "⚠️",
|
||
"name": record.levelname,
|
||
"no": record.levelno,
|
||
},
|
||
"line": record.lineno,
|
||
"message": record.getMessage(),
|
||
"module": record.module,
|
||
"name": record.name,
|
||
"process": {"id": record.process, "name": record.processName},
|
||
"thread": {"id": record.thread, "name": record.threadName},
|
||
"time": {
|
||
"repr": datetime.utcfromtimestamp(record.created).isoformat()
|
||
+ "+00:00",
|
||
"timestamp": record.created,
|
||
},
|
||
},
|
||
}
|
||
return json.dumps(log_entry)
|
||
|
||
|
||
class PlainTextFormatter(logging.Formatter):
|
||
def format(self, record):
|
||
log_time = datetime.utcfromtimestamp(record.created).strftime(
|
||
"%Y-%m-%d %H:%M:%S.%f"
|
||
)[:-3]
|
||
level_color = LOG_COLORS.get(record.levelname, LOG_COLORS["RESET"])
|
||
message_bold = f"{LOG_COLORS['BOLD']}{record.getMessage()}{LOG_COLORS['RESET']}"
|
||
return f"{log_time} | {LOG_COLORS['BOLD']}{level_color}{record.levelname:<8}{LOG_COLORS['RESET']} | {record.funcName}:{record.lineno} - {message_bold}"
|
||
|
||
|
||
class Logger:
|
||
def __init__(self):
|
||
self.logger = logging.getLogger("custom_logger")
|
||
self.logger.setLevel(logging.DEBUG)
|
||
stdout_handler = logging.StreamHandler()
|
||
stdout_handler.setLevel(logging.DEBUG)
|
||
stdout_handler.setFormatter(PlainTextFormatter())
|
||
self.logger.addHandler(stdout_handler)
|
||
|
||
def add(self, filepath, level, colorize, max_size_mb, retention, text, serialize):
|
||
for handler in self.logger.handlers[:]:
|
||
if isinstance(handler, RotatingFileHandler):
|
||
self.logger.removeHandler(handler)
|
||
handler.close()
|
||
|
||
handler = RotatingFileHandler(
|
||
filepath,
|
||
maxBytes=max_size_mb * 1024 * 1024,
|
||
backupCount=retention,
|
||
encoding="utf-8",
|
||
)
|
||
handler.setLevel(level)
|
||
handler.setFormatter(JSONFormatter(text(None)))
|
||
self.logger.addHandler(handler)
|
||
|
||
def log(self, level, message):
|
||
self.logger.log(level, message, stacklevel=2)
|
||
|
||
def info(self, message):
|
||
self.logger.info(message, stacklevel=2)
|
||
|
||
def warning(self, message):
|
||
self.logger.warning(message, stacklevel=2)
|
||
|
||
def error(self, message):
|
||
self.logger.error(message, stacklevel=2)
|
||
|
||
def debug(self, message):
|
||
self.logger.debug(message, stacklevel=2)
|
||
|
||
def success(self, message):
|
||
self.logger.log(SUCCESS_LEVEL, message, stacklevel=2)
|
||
|
||
def critical(self, message):
|
||
self.logger.log(CRITICAL_LEVEL, message, exc_info=True, stacklevel=2)
|