""" 상세한 로깅 시스템 각 지표 계산 과정의 상세 로깅과 중간 결과 저장을 담당합니다. """ import logging import json import os import time import psutil import traceback from datetime import datetime from typing import Dict, Any, Optional, List from contextlib import contextmanager class DetailedLogger: """상세한 로깅을 담당하는 클래스""" def __init__(self, log_dir: str = "logs", enable_performance_monitoring: bool = True): """ 상세 로거 초기화 Args: log_dir (str): 로그 파일 저장 디렉토리 enable_performance_monitoring (bool): 성능 모니터링 활성화 여부 """ self.log_dir = log_dir self.enable_performance_monitoring = enable_performance_monitoring # 로그 디렉토리 생성 os.makedirs(log_dir, exist_ok=True) # 세션 ID 설정 (로거 설정 전에) self.current_session_id = datetime.now().strftime("%Y%m%d_%H%M%S") # 중간 결과 저장용 self.intermediate_results = [] # 로거 설정 self._setup_loggers() # 성능 모니터링 if self.enable_performance_monitoring: self.performance_monitor = PerformanceMonitor() else: self.performance_monitor = None def _setup_loggers(self): """로거들을 설정합니다.""" # 메인 로거 self.main_logger = logging.getLogger('evaluation_main') self.main_logger.setLevel(logging.INFO) # 메트릭별 로거 self.metric_loggers = {} for metric in ['2d_map', '3d_map', 'chamfer_distance', 'emd', 'class_accuracy']: logger = logging.getLogger(f'evaluation_{metric}') logger.setLevel(logging.DEBUG) self.metric_loggers[metric] = logger # 오류 로거 self.error_logger = logging.getLogger('evaluation_error') self.error_logger.setLevel(logging.ERROR) # 성능 로거 self.performance_logger = logging.getLogger('evaluation_performance') self.performance_logger.setLevel(logging.INFO) # 핸들러 설정 self._setup_handlers() def _setup_handlers(self): """로그 핸들러들을 설정합니다.""" # 메인 로그 파일 핸들러 main_handler = logging.FileHandler( os.path.join(self.log_dir, f'evaluation_main_{self.current_session_id}.log'), encoding='utf-8' ) main_handler.setFormatter(logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s' )) self.main_logger.addHandler(main_handler) # 메트릭별 로그 파일 핸들러 for metric, logger in self.metric_loggers.items(): handler = logging.FileHandler( os.path.join(self.log_dir, f'evaluation_{metric}_{self.current_session_id}.log'), encoding='utf-8' ) handler.setFormatter(logging.Formatter( '%(asctime)s - %(levelname)s - %(message)s' )) logger.addHandler(handler) # 오류 로그 파일 핸들러 error_handler = logging.FileHandler( os.path.join(self.log_dir, f'evaluation_error_{self.current_session_id}.log'), encoding='utf-8' ) error_handler.setFormatter(logging.Formatter( '%(asctime)s - %(levelname)s - %(message)s\n%(pathname)s:%(lineno)d\n%(funcName)s\n' )) self.error_logger.addHandler(error_handler) # 성능 로그 파일 핸들러 if self.enable_performance_monitoring: perf_handler = logging.FileHandler( os.path.join(self.log_dir, f'evaluation_performance_{self.current_session_id}.log'), encoding='utf-8' ) perf_handler.setFormatter(logging.Formatter( '%(asctime)s - %(levelname)s - %(message)s' )) self.performance_logger.addHandler(perf_handler) # 콘솔 핸들러 (모든 로거에 추가) console_handler = logging.StreamHandler() console_handler.setFormatter(logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s' )) for logger in [self.main_logger, self.error_logger, self.performance_logger]: logger.addHandler(console_handler) for logger in self.metric_loggers.values(): logger.addHandler(console_handler) def log_metric_start(self, metric_name: str, **kwargs): """메트릭 계산 시작을 로깅합니다.""" logger = self.metric_loggers.get(metric_name, self.main_logger) logger.info(f"=== {metric_name.upper()} 계산 시작 ===") # 입력 파라미터 로깅 for key, value in kwargs.items(): if isinstance(value, (dict, list)): logger.debug(f"입력 파라미터 {key}: {type(value).__name__} (크기: {len(value)})") else: logger.debug(f"입력 파라미터 {key}: {value}") # 성능 모니터링 시작 if self.performance_monitor: self.performance_monitor.start_metric(metric_name) def log_metric_step(self, metric_name: str, step: str, details: Dict[str, Any] = None): """메트릭 계산 단계를 로깅합니다.""" logger = self.metric_loggers.get(metric_name, self.main_logger) logger.info(f"[{metric_name}] {step}") if details: for key, value in details.items(): if isinstance(value, (dict, list)): logger.debug(f" {key}: {type(value).__name__} (크기: {len(value)})") else: logger.debug(f" {key}: {value}") def log_metric_result(self, metric_name: str, result: Any, execution_time: float = None): """메트릭 계산 결과를 로깅합니다.""" logger = self.metric_loggers.get(metric_name, self.main_logger) logger.info(f"=== {metric_name.upper()} 계산 완료 ===") logger.info(f"결과: {result}") if execution_time: logger.info(f"실행 시간: {execution_time:.2f}초") # 성능 모니터링 종료 if self.performance_monitor: self.performance_monitor.end_metric(metric_name, execution_time) # 중간 결과 저장 self._save_intermediate_result(metric_name, result, execution_time) def log_metric_error(self, metric_name: str, error: Exception, context: Dict[str, Any] = None): """메트릭 계산 오류를 로깅합니다.""" logger = self.metric_loggers.get(metric_name, self.error_logger) logger.error(f"=== {metric_name.upper()} 계산 오류 ===") logger.error(f"오류 타입: {type(error).__name__}") logger.error(f"오류 메시지: {str(error)}") logger.error(f"스택 트레이스:\n{traceback.format_exc()}") if context: logger.error("오류 발생 컨텍스트:") for key, value in context.items(): logger.error(f" {key}: {value}") # 성능 모니터링 종료 (오류 시) if self.performance_monitor: self.performance_monitor.end_metric(metric_name, None, error=True) def log_system_info(self): """시스템 정보를 로깅합니다.""" self.main_logger.info("=== 시스템 정보 ===") self.main_logger.info(f"CPU 개수: {psutil.cpu_count()}") self.main_logger.info(f"메모리 총량: {psutil.virtual_memory().total / (1024**3):.2f} GB") self.main_logger.info(f"사용 가능 메모리: {psutil.virtual_memory().available / (1024**3):.2f} GB") self.main_logger.info(f"디스크 사용량: {psutil.disk_usage('/').percent:.1f}%") def log_evaluation_start(self, model_path: str, reference_path: str): """평가 시작을 로깅합니다.""" self.main_logger.info("=" * 60) self.main_logger.info("3D 객체인식 평가 시작") self.main_logger.info("=" * 60) self.main_logger.info(f"모델 파일: {model_path}") self.main_logger.info(f"참조 파일: {reference_path}") self.main_logger.info(f"세션 ID: {self.current_session_id}") # 시스템 정보 로깅 self.log_system_info() def log_evaluation_complete(self, results: Dict[str, Any]): """평가 완료를 로깅합니다.""" self.main_logger.info("=" * 60) self.main_logger.info("3D 객체인식 평가 완료") self.main_logger.info("=" * 60) if 'comprehensive_score' in results: self.main_logger.info(f"종합 점수: {results['comprehensive_score']:.2f}") if 'grade' in results: self.main_logger.info(f"성능 등급: {results['grade']}") # 개별 지표 결과 로깅 if 'metrics' in results: self.main_logger.info("개별 지표 결과:") for metric_name, value in results['metrics'].items(): self.main_logger.info(f" {metric_name}: {value}") # 성능 요약 로깅 if self.performance_monitor: self.performance_monitor.log_summary(self.performance_logger) def _save_intermediate_result(self, metric_name: str, result: Any, execution_time: float = None): """중간 결과를 저장합니다.""" intermediate_result = { 'timestamp': datetime.now().isoformat(), 'metric_name': metric_name, 'result': result, 'execution_time': execution_time, 'session_id': self.current_session_id } self.intermediate_results.append(intermediate_result) # JSONL 파일에 저장 intermediate_file = os.path.join( self.log_dir, f"intermediate_results_{self.current_session_id}.jsonl" ) with open(intermediate_file, 'a', encoding='utf-8') as f: f.write(json.dumps(intermediate_result, ensure_ascii=False) + '\n') @contextmanager def metric_context(self, metric_name: str, **kwargs): """메트릭 계산을 위한 컨텍스트 매니저""" start_time = time.time() try: self.log_metric_start(metric_name, **kwargs) yield execution_time = time.time() - start_time self.log_metric_result(metric_name, "계산 완료", execution_time) except Exception as e: execution_time = time.time() - start_time self.log_metric_error(metric_name, e, {'execution_time': execution_time}) raise class PerformanceMonitor: """성능 모니터링을 담당하는 클래스""" def __init__(self): """성능 모니터 초기화""" self.metric_times = {} self.metric_memory = {} self.metric_cpu = {} self.current_metric = None self.start_time = None self.start_memory = None self.start_cpu = None def start_metric(self, metric_name: str): """메트릭 계산 시작 시점을 기록합니다.""" self.current_metric = metric_name self.start_time = time.time() self.start_memory = psutil.virtual_memory().used self.start_cpu = psutil.cpu_percent() def end_metric(self, metric_name: str, execution_time: float = None, error: bool = False): """메트릭 계산 종료 시점을 기록합니다.""" if self.current_metric != metric_name: return if execution_time is None: execution_time = time.time() - self.start_time end_memory = psutil.virtual_memory().used end_cpu = psutil.cpu_percent() # 메트릭별 성능 데이터 저장 self.metric_times[metric_name] = execution_time self.metric_memory[metric_name] = { 'start': self.start_memory, 'end': end_memory, 'peak': end_memory - self.start_memory } self.metric_cpu[metric_name] = { 'start': self.start_cpu, 'end': end_cpu, 'average': (self.start_cpu + end_cpu) / 2 } # 상태 초기화 self.current_metric = None self.start_time = None self.start_memory = None self.start_cpu = None def log_summary(self, logger: logging.Logger): """성능 요약을 로깅합니다.""" logger.info("=== 성능 요약 ===") total_time = sum(self.metric_times.values()) logger.info(f"총 실행 시간: {total_time:.2f}초") logger.info("메트릭별 실행 시간:") for metric, time_taken in self.metric_times.items(): percentage = (time_taken / total_time) * 100 if total_time > 0 else 0 logger.info(f" {metric}: {time_taken:.2f}초 ({percentage:.1f}%)") logger.info("메트릭별 메모리 사용량:") for metric, memory_data in self.metric_memory.items(): peak_mb = memory_data['peak'] / (1024**2) logger.info(f" {metric}: {peak_mb:.2f} MB") logger.info("메트릭별 CPU 사용률:") for metric, cpu_data in self.metric_cpu.items(): logger.info(f" {metric}: 평균 {cpu_data['average']:.1f}%") # 전역 로거 인스턴스 _global_logger = None def get_logger(log_dir: str = "logs", enable_performance_monitoring: bool = True) -> DetailedLogger: """전역 로거 인스턴스를 반환합니다.""" global _global_logger if _global_logger is None: _global_logger = DetailedLogger(log_dir, enable_performance_monitoring) return _global_logger def reset_logger(): """전역 로거를 리셋합니다.""" global _global_logger _global_logger = None