|
|
|
|
@ -1,10 +1,37 @@
|
|
|
|
|
import logging
|
|
|
|
|
import sys
|
|
|
|
|
import re
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
from typing import Optional
|
|
|
|
|
|
|
|
|
|
class ColoredFormatter(logging.Formatter):
|
|
|
|
|
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',
|
|
|
|
|
@ -32,6 +59,9 @@ def setup_logging(level: str = "INFO", log_file: Optional[str] = None) -> loggin
|
|
|
|
|
|
|
|
|
|
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',
|
|
|
|
|
@ -44,10 +74,10 @@ def setup_logging(level: str = "INFO", log_file: Optional[str] = None) -> loggin
|
|
|
|
|
log_path = Path(log_file)
|
|
|
|
|
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
|
|
|
|
file_handler = logging.FileHandler(log_path)
|
|
|
|
|
file_handler = logging.FileHandler(log_path, encoding='utf-8')
|
|
|
|
|
file_handler.setLevel(log_level)
|
|
|
|
|
|
|
|
|
|
file_formatter = logging.Formatter(
|
|
|
|
|
file_formatter = SafeFormatter(
|
|
|
|
|
fmt='%(asctime)s - %(name)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s',
|
|
|
|
|
datefmt='%Y-%m-%d %H:%M:%S'
|
|
|
|
|
)
|
|
|
|
|
|