135 lines
4.9 KiB
Python
135 lines
4.9 KiB
Python
import logging
|
|
from logging.handlers import RotatingFileHandler, BaseRotatingHandler
|
|
import os
|
|
import glob
|
|
import traceback
|
|
import inspect
|
|
|
|
|
|
class Logger():
|
|
def __init__(self, log_file="worker.log", logger_name="GUI_WorkerLogger",
|
|
file_log_level=logging.DEBUG):
|
|
"""
|
|
Logger 초기화
|
|
:param log_file: 로그 파일 이름
|
|
:param logger_name: 로거 이름
|
|
:param file_log_level: 파일 로거의 로그 레벨
|
|
"""
|
|
super().__init__()
|
|
self.file_log_level = file_log_level
|
|
|
|
# 로그 설정
|
|
self.logger = logging.getLogger(logger_name)
|
|
self.logger.setLevel(file_log_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(file_log_level)
|
|
self._add_file_handler(log_file, file_log_level)
|
|
|
|
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):
|
|
"""파일 핸들러 추가"""
|
|
# 확장자가 .log가 아니면 .log로 변경
|
|
if not log_file.endswith('.log'):
|
|
base_name, _ = os.path.splitext(log_file)
|
|
log_file = base_name + '.log'
|
|
|
|
# 커스텀 로테이팅 핸들러 사용
|
|
file_handler = CustomRotatingFileHandler(
|
|
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
|
|
)
|
|
|
|
# 파일 로거에 메시지 전달
|
|
if level >= self.file_log_level:
|
|
self.logger.handle(record)
|
|
|
|
class CustomRotatingFileHandler(RotatingFileHandler):
|
|
"""`.log` 확장자를 강제하고 파일명을 `base_번호.log` 형식으로 롤오버하는 핸들러"""
|
|
|
|
def __init__(self, filename, mode="a", maxBytes=0, backupCount=0, encoding=None):
|
|
# 확장자를 무조건 .log 로 통일
|
|
if not filename.endswith(".log"):
|
|
base, _ = os.path.splitext(filename)
|
|
filename = base + ".log"
|
|
|
|
# 부모(RotatingFileHandler) 초기화
|
|
super().__init__(filename, mode, maxBytes, backupCount, encoding)
|
|
|
|
# 편의를 위해 확장자 없는 베이스 이름 저장
|
|
self._base_name, _ = os.path.splitext(self.baseFilename)
|
|
|
|
def doRollover(self):
|
|
"""`RotatingFileHandler`의 롤오버 방식(emit → shouldRollover) 그대로 사용하되
|
|
파일명을 `base_번호.log` 패턴으로 변경한다."""
|
|
|
|
# 현재 스트림 닫기
|
|
if self.stream:
|
|
self.stream.close()
|
|
self.stream = None
|
|
|
|
# existing backup logs
|
|
existing_logs = glob.glob(f"{self._base_name}_*.log")
|
|
existing_logs.sort()
|
|
|
|
# backupCount 초과 시 오래된 파일 삭제
|
|
if self.backupCount > 0 and len(existing_logs) >= self.backupCount:
|
|
for old_log in existing_logs[: len(existing_logs) - self.backupCount + 1]:
|
|
try:
|
|
os.remove(old_log)
|
|
except Exception:
|
|
pass
|
|
|
|
# 새 인덱스 계산
|
|
max_idx = 0
|
|
for path in existing_logs:
|
|
idx_str = os.path.splitext(os.path.basename(path))[0].split("_")[-1]
|
|
if idx_str.isdigit():
|
|
max_idx = max(max_idx, int(idx_str))
|
|
|
|
new_idx = max_idx + 1
|
|
rollover_target = f"{self._base_name}_{new_idx}.log"
|
|
|
|
# 현재 파일명을 rollover_target 으로 변경
|
|
try:
|
|
os.rename(self.baseFilename, rollover_target)
|
|
except Exception:
|
|
pass
|
|
|
|
# 새로운 스트림 오픈 (write 모드)
|
|
self.mode = "w"
|
|
self.stream = self._open()
|