113 lines
4.2 KiB
Python
113 lines
4.2 KiB
Python
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'<span style="color:{color};">{message}</span>'
|