feat: initial commit - unified FGTools from static, weather, mattermost-noti
This commit is contained in:
248
core/logging_utils.py
Normal file
248
core/logging_utils.py
Normal file
@ -0,0 +1,248 @@
|
||||
# ===================================================================
|
||||
# core/logging_utils.py
|
||||
# FGTools 로깅 유틸리티 모듈
|
||||
# ===================================================================
|
||||
# 일관된 로그 포맷과 설정을 제공하는 로깅 유틸리티입니다.
|
||||
# 파일 및 콘솔 출력, 로그 레벨 설정을 지원합니다.
|
||||
# ===================================================================
|
||||
"""
|
||||
로깅 유틸리티 모듈
|
||||
|
||||
일관된 로그 포맷과 핸들러 설정을 제공합니다.
|
||||
콘솔 출력, 파일 저장, 로그 로테이션을 지원합니다.
|
||||
|
||||
사용 예시:
|
||||
from core.logging_utils import get_logger, setup_logging
|
||||
|
||||
# 간단한 로거 사용
|
||||
logger = get_logger(__name__)
|
||||
logger.info("메시지")
|
||||
|
||||
# 상세 설정이 필요한 경우
|
||||
logger = setup_logging("my_module", level="DEBUG", log_file="app.log")
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
from typing import Optional
|
||||
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
|
||||
|
||||
from .config import get_config
|
||||
|
||||
# 로그 포맷 상수
|
||||
DEFAULT_FORMAT = '[%(asctime)s] %(name)s - %(levelname)s: %(message)s'
|
||||
DETAILED_FORMAT = '[%(asctime)s] %(name)s (%(filename)s:%(lineno)d) - %(levelname)s: %(message)s'
|
||||
DATE_FORMAT = '%Y-%m-%d %H:%M:%S'
|
||||
|
||||
# 로그 디렉토리
|
||||
LOG_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'logs')
|
||||
|
||||
|
||||
def _ensure_log_dir():
|
||||
"""로그 디렉토리 생성"""
|
||||
if not os.path.exists(LOG_DIR):
|
||||
os.makedirs(LOG_DIR, exist_ok=True)
|
||||
|
||||
|
||||
def setup_logging(
|
||||
name: str,
|
||||
level: Optional[str] = None,
|
||||
log_file: Optional[str] = None,
|
||||
log_format: str = DEFAULT_FORMAT,
|
||||
max_bytes: int = 10 * 1024 * 1024, # 10MB
|
||||
backup_count: int = 5,
|
||||
console_output: bool = True
|
||||
) -> logging.Logger:
|
||||
"""
|
||||
로거 설정 및 반환
|
||||
|
||||
일관된 포맷으로 로거를 설정합니다. 콘솔 출력과 파일 저장을
|
||||
동시에 지원하며, 로그 로테이션을 자동으로 처리합니다.
|
||||
|
||||
Args:
|
||||
name: 로거 이름 (보통 __name__ 사용)
|
||||
level: 로그 레벨 (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
||||
None이면 환경 설정에서 로드
|
||||
log_file: 로그 파일명 (None이면 파일 저장 안 함)
|
||||
log_format: 로그 메시지 포맷
|
||||
max_bytes: 로그 파일 최대 크기 (로테이션 기준)
|
||||
backup_count: 보관할 백업 파일 수
|
||||
console_output: 콘솔 출력 여부
|
||||
|
||||
Returns:
|
||||
설정된 Logger 인스턴스
|
||||
"""
|
||||
# 설정에서 기본 레벨 로드
|
||||
if level is None:
|
||||
try:
|
||||
level = get_config().log_level
|
||||
except Exception:
|
||||
level = 'INFO'
|
||||
|
||||
logger = logging.getLogger(name)
|
||||
|
||||
# 이미 핸들러가 있으면 레벨만 설정하고 반환
|
||||
if logger.handlers:
|
||||
logger.setLevel(getattr(logging, level.upper(), logging.INFO))
|
||||
return logger
|
||||
|
||||
# 로그 레벨 설정
|
||||
log_level = getattr(logging, level.upper(), logging.INFO)
|
||||
logger.setLevel(log_level)
|
||||
|
||||
# 포매터 생성
|
||||
formatter = logging.Formatter(log_format, datefmt=DATE_FORMAT)
|
||||
|
||||
# 콘솔 핸들러 추가
|
||||
if console_output:
|
||||
console_handler = logging.StreamHandler(sys.stdout)
|
||||
console_handler.setLevel(log_level)
|
||||
console_handler.setFormatter(formatter)
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
# 파일 핸들러 추가 (요청 시)
|
||||
if log_file:
|
||||
_ensure_log_dir()
|
||||
file_path = os.path.join(LOG_DIR, log_file)
|
||||
|
||||
file_handler = RotatingFileHandler(
|
||||
file_path,
|
||||
maxBytes=max_bytes,
|
||||
backupCount=backup_count,
|
||||
encoding='utf-8'
|
||||
)
|
||||
file_handler.setLevel(log_level)
|
||||
file_handler.setFormatter(formatter)
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
# 부모 로거로 전파 방지
|
||||
logger.propagate = False
|
||||
|
||||
return logger
|
||||
|
||||
|
||||
def get_logger(name: str) -> logging.Logger:
|
||||
"""
|
||||
간편 로거 반환
|
||||
|
||||
기본 설정으로 로거를 반환합니다. 이미 설정된 로거가 있으면 재사용합니다.
|
||||
|
||||
Args:
|
||||
name: 로거 이름 (보통 __name__ 사용)
|
||||
|
||||
Returns:
|
||||
Logger 인스턴스
|
||||
"""
|
||||
return setup_logging(name)
|
||||
|
||||
|
||||
def setup_file_logger(
|
||||
name: str,
|
||||
log_file: str,
|
||||
level: str = 'INFO',
|
||||
rotation: str = 'size', # 'size' 또는 'time'
|
||||
when: str = 'midnight', # rotation='time'일 때 사용
|
||||
interval: int = 1, # rotation='time'일 때 사용
|
||||
) -> logging.Logger:
|
||||
"""
|
||||
파일 전용 로거 설정
|
||||
|
||||
콘솔 출력 없이 파일에만 로그를 기록합니다.
|
||||
크기 기반 또는 시간 기반 로테이션을 선택할 수 있습니다.
|
||||
|
||||
Args:
|
||||
name: 로거 이름
|
||||
log_file: 로그 파일명
|
||||
level: 로그 레벨
|
||||
rotation: 로테이션 방식 ('size' 또는 'time')
|
||||
when: 시간 기반 로테이션 주기 (midnight, H, D, W0-W6)
|
||||
interval: 로테이션 간격
|
||||
|
||||
Returns:
|
||||
설정된 Logger 인스턴스
|
||||
"""
|
||||
_ensure_log_dir()
|
||||
|
||||
logger = logging.getLogger(name)
|
||||
|
||||
if logger.handlers:
|
||||
return logger
|
||||
|
||||
log_level = getattr(logging, level.upper(), logging.INFO)
|
||||
logger.setLevel(log_level)
|
||||
|
||||
formatter = logging.Formatter(DETAILED_FORMAT, datefmt=DATE_FORMAT)
|
||||
file_path = os.path.join(LOG_DIR, log_file)
|
||||
|
||||
if rotation == 'time':
|
||||
# 시간 기반 로테이션 (예: 매일 자정)
|
||||
handler = TimedRotatingFileHandler(
|
||||
file_path,
|
||||
when=when,
|
||||
interval=interval,
|
||||
backupCount=30,
|
||||
encoding='utf-8'
|
||||
)
|
||||
else:
|
||||
# 크기 기반 로테이션
|
||||
handler = RotatingFileHandler(
|
||||
file_path,
|
||||
maxBytes=10 * 1024 * 1024, # 10MB
|
||||
backupCount=5,
|
||||
encoding='utf-8'
|
||||
)
|
||||
|
||||
handler.setLevel(log_level)
|
||||
handler.setFormatter(formatter)
|
||||
logger.addHandler(handler)
|
||||
logger.propagate = False
|
||||
|
||||
return logger
|
||||
|
||||
|
||||
class LogContext:
|
||||
"""
|
||||
로그 컨텍스트 관리자
|
||||
|
||||
특정 작업의 시작과 종료를 자동으로 로깅합니다.
|
||||
작업 소요 시간도 함께 기록됩니다.
|
||||
|
||||
사용 예시:
|
||||
with LogContext(logger, "데이터 처리"):
|
||||
process_data()
|
||||
# 출력:
|
||||
# [시작] 데이터 처리
|
||||
# [완료] 데이터 처리 (소요시간: 1.23초)
|
||||
"""
|
||||
|
||||
def __init__(self, logger: logging.Logger, task_name: str, level: int = logging.INFO):
|
||||
"""
|
||||
Args:
|
||||
logger: Logger 인스턴스
|
||||
task_name: 작업 이름
|
||||
level: 로그 레벨
|
||||
"""
|
||||
self.logger = logger
|
||||
self.task_name = task_name
|
||||
self.level = level
|
||||
self.start_time = None
|
||||
|
||||
def __enter__(self):
|
||||
"""작업 시작 로깅"""
|
||||
import time
|
||||
self.start_time = time.time()
|
||||
self.logger.log(self.level, f"[시작] {self.task_name}")
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""작업 종료 로깅"""
|
||||
import time
|
||||
elapsed = time.time() - self.start_time
|
||||
|
||||
if exc_type is not None:
|
||||
self.logger.error(f"[실패] {self.task_name} - {exc_type.__name__}: {exc_val}")
|
||||
else:
|
||||
self.logger.log(self.level, f"[완료] {self.task_name} (소요시간: {elapsed:.2f}초)")
|
||||
|
||||
return False # 예외 전파
|
||||
Reference in New Issue
Block a user