import logging from logging.handlers import RotatingFileHandler from PySide6.QtCore import QObject, Signal import traceback import inspect class Logger(QObject): log_signal = Signal(str) # GUI로 로그 메시지를 전달할 시그널 def __init__(self, gui_logger=None, log_file="app.log", logger_name="MainLogger", level=logging.INFO): """ Logger 초기화 :param gui_logger: GUI에 로그를 출력할 콜백 함수 :param log_file: 로그 파일 이름 :param logger_name: 로거 이름 :param level: 기본 로그 레벨 """ super().__init__() self.gui_logger = gui_logger # 로그 설정 self.logger = logging.getLogger(logger_name) self.logger.setLevel(level) # 로거 레벨 설정 # 포맷 설정 self.simple_format = "[%(asctime)s] [%(levelname)s] %(message)s" self.detailed_format = ( "[%(asctime)s] [%(threadName)s] [%(levelname)s] " "[%(filename)s:%(funcName)s:%(lineno)d] %(message)s" ) # 핸들러 추가 self._add_console_handler(level) self._add_file_handler(log_file, level) # GUI Logger 연결 if self.gui_logger: self.log_signal.connect(self.gui_logger) def _add_console_handler(self, level): """콘솔 핸들러 추가""" console_handler = logging.StreamHandler() console_handler.setLevel(level) formatter = logging.Formatter( self.detailed_format if level <= logging.DEBUG else self.simple_format ) console_handler.setFormatter(formatter) self.logger.addHandler(console_handler) def _add_file_handler(self, log_file, level): """파일 핸들러 추가""" file_handler = RotatingFileHandler( log_file, maxBytes=10 * 1024 * 1024, backupCount=5, encoding="utf-8" ) file_handler.setLevel(level) formatter = logging.Formatter( self.detailed_format if level <= logging.DEBUG else self.simple_format ) file_handler.setFormatter(formatter) self.logger.addHandler(file_handler) def log(self, message, level=logging.INFO, exc_info=False): """로그 메시지 기록""" if exc_info: message = f"{message}\n{traceback.format_exc()}" # 호출 위치 정보를 동적으로 추출 caller_frame = logging.currentframe().f_back record = self.logger.makeRecord( self.logger.name, level, caller_frame.f_code.co_filename, caller_frame.f_lineno, message, None, None, caller_frame.f_code.co_name ) # # 호출 위치 정보를 동적으로 추출 # stack = inspect.stack() # if len(stack) > 2: # 호출 스택이 충분히 깊은지 확인 # caller_frame = stack[2] # filename = caller_frame.filename # lineno = caller_frame.lineno # func_name = caller_frame.function # else: # # 호출 스택이 충분히 깊지 않으면 기본값 설정 # filename = "unknown" # lineno = 0 # func_name = "unknown" # record = self.logger.makeRecord( # self.logger.name, level, filename, lineno, message, None, None, func_name # ) # 로거에 메시지 전달 self.logger.handle(record) # GUI 로그로 전달 (포맷 적용) if self.gui_logger: formatter = logging.Formatter( self.detailed_format if self.logger.level <= logging.DEBUG else self.simple_format ) formatted_message = formatter.format(record) colored_message = self.format_gui_message(formatted_message, level) self.log_signal.emit(colored_message) def format_gui_message(self, message, level): """GUI 로그 메시지의 HTML 색상 지정""" color_map = { logging.DEBUG: "gray", logging.INFO: "black", logging.WARNING: "orange", logging.ERROR: "red", logging.CRITICAL: "purple", } color = color_map.get(level, "black") return f'{message}'