import logging from logging.handlers import RotatingFileHandler, BaseRotatingHandler import os import glob import traceback import inspect class Logger1(): def __init__(self, log_file="ITServer.log", logger_name="MainLogger", 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(BaseRotatingHandler): """로그 파일을 모두 .log 확장자로 생성하는 커스텀 핸들러""" def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None): super().__init__(filename, mode, encoding) self.maxBytes = maxBytes self.backupCount = backupCount # 기존 로그 파일 확인 및 인덱스 설정 self._base_filename = filename self._extension = '.log' def doRollover(self): """롤오버 수행 - 파일이 최대 크기에 도달하면 새 파일 생성""" if self.stream: self.stream.close() self.stream = None # 기존 로그 파일 이름을 기반으로 백업 파일 생성 base_name, ext = os.path.splitext(self._base_filename) # 현재 디렉토리의 모든 로그 파일 확인 log_dir = os.path.dirname(self._base_filename) or '.' existing_logs = glob.glob(f"{base_name}*.log") existing_logs.sort() # 최대 백업 수 초과하는 파일 제거 while len(existing_logs) >= self.backupCount: try: oldest_file = existing_logs.pop(0) os.remove(oldest_file) except Exception: pass # 새 로그 파일 이름 생성 (주 로그 파일 이름은 그대로 유지) # 예: log.log, log_1.log, log_2.log, ... if os.path.exists(self._base_filename): # 인덱스가 있는 로그 파일들 찾기 indexed_logs = [f for f in existing_logs if f != self._base_filename] max_index = 0 for log_file in indexed_logs: try: # 파일 이름에서 인덱스 부분 추출 name_part = os.path.basename(log_file) name_without_ext = os.path.splitext(name_part)[0] if '_' in name_without_ext: idx_str = name_without_ext.split('_')[-1] if idx_str.isdigit(): max_index = max(max_index, int(idx_str)) except Exception: pass # 새 인덱스로 파일 이름 설정 new_index = max_index + 1 new_log_file = f"{base_name}_{new_index}.log" # 기존 파일 이름 변경 try: os.rename(self._base_filename, new_log_file) except Exception: pass # 스트림 다시 열기 self.mode = 'w' self.stream = self._open() def shouldRollover(self, record): """롤오버가 필요한지 확인""" if self.stream is None: # 첫 번째 로그 쓰기 시도 self.stream = self._open() if self.maxBytes > 0: # 최대 크기가 지정된 경우만 검사 self.stream.seek(0, 2) # 파일 끝으로 이동 if self.stream.tell() >= self.maxBytes: return True return False