Files
DS_L10N/lib/logger.py
2025-10-29 15:35:54 +09:00

210 lines
5.8 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Colored Console Logger for DS_L10N
컬러 콘솔 로깅 시스템
"""
import logging
import sys
from datetime import datetime
from pathlib import Path
from typing import Optional
# Windows에서 ANSI 컬러 지원 활성화
if sys.platform == 'win32':
import os
os.system('') # ANSI escape sequences 활성화
# UTF-8 콘솔 출력 설정
try:
import ctypes
kernel32 = ctypes.windll.kernel32
kernel32.SetConsoleCP(65001) # UTF-8 input
kernel32.SetConsoleOutputCP(65001) # UTF-8 output
except:
pass
# ANSI 컬러 코드
class Colors:
RESET = '\033[0m'
BOLD = '\033[1m'
# 기본 색상
BLACK = '\033[30m'
RED = '\033[31m'
GREEN = '\033[32m'
YELLOW = '\033[33m'
BLUE = '\033[34m'
MAGENTA = '\033[35m'
CYAN = '\033[36m'
WHITE = '\033[37m'
# 밝은 색상
BRIGHT_RED = '\033[91m'
BRIGHT_GREEN = '\033[92m'
BRIGHT_YELLOW = '\033[93m'
BRIGHT_BLUE = '\033[94m'
BRIGHT_MAGENTA = '\033[95m'
BRIGHT_CYAN = '\033[96m'
BRIGHT_WHITE = '\033[97m'
# 이모지 아이콘
class Icons:
SUCCESS = ''
ERROR = ''
WARNING = '⚠️'
INFO = ''
ROCKET = '🚀'
GEAR = '⚙️'
FILE = '📄'
FOLDER = '📁'
CHART = '📊'
SEARCH = '🔍'
CLEAN = '🧹'
BACKUP = '💾'
CLOCK = '⏱️'
class ColoredFormatter(logging.Formatter):
"""컬러 로그 포매터"""
LEVEL_COLORS = {
'DEBUG': Colors.CYAN,
'INFO': Colors.BLUE,
'WARNING': Colors.YELLOW,
'ERROR': Colors.RED,
'CRITICAL': Colors.BRIGHT_RED + Colors.BOLD,
'SUCCESS': Colors.GREEN,
}
LEVEL_ICONS = {
'DEBUG': Icons.GEAR,
'INFO': Icons.INFO,
'WARNING': Icons.WARNING,
'ERROR': Icons.ERROR,
'CRITICAL': Icons.ERROR,
'SUCCESS': Icons.SUCCESS,
}
def __init__(self, colored=True):
super().__init__()
self.colored = colored
def format(self, record):
if self.colored:
level_color = self.LEVEL_COLORS.get(record.levelname, '')
level_icon = self.LEVEL_ICONS.get(record.levelname, '')
reset = Colors.RESET
# 타임스탬프
timestamp = datetime.fromtimestamp(record.created).strftime('%Y-%m-%d %H:%M:%S')
# 레벨명 포맷
level_name = f"{level_color}{record.levelname:<8}{reset}"
# 메시지
message = record.getMessage()
return f"[{timestamp}] {level_icon} {level_name} {message}"
else:
timestamp = datetime.fromtimestamp(record.created).strftime('%Y-%m-%d %H:%M:%S')
return f"[{timestamp}] [{record.levelname:<8}] {record.getMessage()}"
class DSLogger:
"""DS_L10N 전용 로거"""
def __init__(self, name: str = 'DS_L10N',
console_level: str = 'INFO',
file_level: str = 'DEBUG',
log_file: Optional[Path] = None,
colored: bool = True):
self.logger = logging.getLogger(name)
self.logger.setLevel(logging.DEBUG)
self.logger.handlers.clear() # 기존 핸들러 제거
# 콘솔 핸들러 (UTF-8 강제)
if sys.platform == 'win32':
# Windows에서 UTF-8 출력 스트림 사용
import io
console_stream = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', line_buffering=True)
else:
console_stream = sys.stdout
console_handler = logging.StreamHandler(console_stream)
console_handler.setLevel(getattr(logging, console_level.upper()))
console_handler.setFormatter(ColoredFormatter(colored=colored))
self.logger.addHandler(console_handler)
# 파일 핸들러
if log_file:
log_file.parent.mkdir(parents=True, exist_ok=True)
file_handler = logging.FileHandler(log_file, encoding='utf-8')
file_handler.setLevel(getattr(logging, file_level.upper()))
file_handler.setFormatter(ColoredFormatter(colored=False))
self.logger.addHandler(file_handler)
# SUCCESS 레벨 추가 (INFO와 WARNING 사이)
logging.addLevelName(25, 'SUCCESS')
def debug(self, message: str):
self.logger.debug(message)
def info(self, message: str):
self.logger.info(message)
def success(self, message: str):
self.logger.log(25, message)
def warning(self, message: str):
self.logger.warning(message)
def error(self, message: str):
self.logger.error(message)
def critical(self, message: str):
self.logger.critical(message)
def separator(self, char: str = '=', length: int = 80):
"""구분선 출력"""
self.logger.info(char * length)
def section(self, title: str, icon: str = Icons.ROCKET):
"""섹션 제목 출력"""
self.separator()
self.logger.info(f"{icon} {title}")
self.separator()
def stats(self, **kwargs):
"""통계 정보 출력"""
self.logger.info(f"{Icons.CHART} 통계:")
for key, value in kwargs.items():
self.logger.info(f" - {key}: {value}")
# 전역 로거 인스턴스
_global_logger: Optional[DSLogger] = None
def get_logger() -> DSLogger:
"""전역 로거 가져오기"""
global _global_logger
if _global_logger is None:
_global_logger = DSLogger()
return _global_logger
def init_logger(config: dict, log_file: Optional[Path] = None) -> DSLogger:
"""로거 초기화"""
global _global_logger
logging_config = config.get('logging', {})
_global_logger = DSLogger(
console_level=logging_config.get('console_level', 'INFO'),
file_level=logging_config.get('file_level', 'DEBUG'),
log_file=log_file,
colored=logging_config.get('colored_output', True)
)
return _global_logger