You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
SoulSync/utils/logging_config.py

120 lines
4.1 KiB

import logging
import logging.handlers
import sys
import re
from pathlib import Path
from datetime import datetime
from typing import Optional
class SafeFormatter(logging.Formatter):
"""Formatter that handles Unicode characters safely on Windows"""
@staticmethod
def strip_emojis(text):
"""Remove emoji characters from text for Windows compatibility"""
# Remove emoji characters but keep other Unicode
emoji_pattern = re.compile("["
u"\U0001F600-\U0001F64F" # emoticons
u"\U0001F300-\U0001F5FF" # symbols & pictographs
u"\U0001F680-\U0001F6FF" # transport & map symbols
u"\U0001F1E0-\U0001F1FF" # flags (iOS)
u"\U00002702-\U000027B0"
u"\U000024C2-\U0001F251"
"]+", flags=re.UNICODE)
return emoji_pattern.sub('', text)
def format(self, record):
# Try to format with emojis first, fall back to stripped version
try:
return super().format(record)
except UnicodeEncodeError:
# Strip emojis and try again for Windows compatibility
record.getMessage = lambda: self.strip_emojis(record.msg % record.args if record.args else record.msg)
return super().format(record)
class ColoredFormatter(SafeFormatter):
COLORS = {
'DEBUG': '\033[94m',
'INFO': '\033[92m',
'WARNING': '\033[93m',
'ERROR': '\033[91m',
'CRITICAL': '\033[95m',
'RESET': '\033[0m'
}
def format(self, record):
log_color = self.COLORS.get(record.levelname, self.COLORS['RESET'])
reset_color = self.COLORS['RESET']
record.levelname = f"{log_color}{record.levelname}{reset_color}"
return super().format(record)
def setup_logging(level: str = "INFO", log_file: Optional[str] = None) -> logging.Logger:
log_level = getattr(logging, level.upper(), logging.INFO)
logger = logging.getLogger("newmusic")
logger.setLevel(log_level)
if logger.handlers:
logger.handlers.clear()
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(log_level)
# Force UTF-8 encoding for Windows compatibility with Unicode characters
if hasattr(console_handler.stream, 'reconfigure'):
console_handler.stream.reconfigure(encoding='utf-8', errors='replace')
console_formatter = ColoredFormatter(
fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
console_handler.setFormatter(console_formatter)
logger.addHandler(console_handler)
if log_file:
log_path = Path(log_file)
log_path.parent.mkdir(parents=True, exist_ok=True)
file_handler = logging.handlers.RotatingFileHandler(
log_path, encoding='utf-8', maxBytes=10*1024*1024, backupCount=3
)
file_handler.setLevel(log_level)
file_formatter = SafeFormatter(
fmt='%(asctime)s - %(name)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
file_handler.setFormatter(file_formatter)
logger.addHandler(file_handler)
logger.info(f"Logging initialized with level: {level}")
return logger
def get_logger(name: str) -> logging.Logger:
return logging.getLogger(f"newmusic.{name}")
def set_log_level(level: str) -> bool:
"""Dynamically change the log level for all loggers without restart"""
try:
log_level = getattr(logging, level.upper(), logging.INFO)
# Get the root "newmusic" logger
root_logger = logging.getLogger("newmusic")
root_logger.setLevel(log_level)
# Update all handlers
for handler in root_logger.handlers:
handler.setLevel(log_level)
root_logger.info(f"Log level changed to: {level.upper()}")
return True
except Exception as e:
print(f"Error setting log level: {e}")
return False
def get_current_log_level() -> str:
"""Get the current log level"""
root_logger = logging.getLogger("newmusic")
return logging.getLevelName(root_logger.level)
main_logger = get_logger("main")