Loading...
Loading...
02-reusable-code-python/structlog_utils/simple_logger.py
"""
콘솔 + 파일 동시 출력 로거 — 중복 핸들러 방지
@source 260321-lega-tech
@extracted 2026-03-22
@version 1.0.0
의존성:
- 표준 라이브러리만 사용 (설치 불필요)
사용법:
from structlog_utils.simple_logger import get_logger, log_start, log_end
logger = get_logger("my_module")
logger.info("작업 시작")
# 스크립트 시작/종료 로그
log_start(logger, "데이터 수집 스크립트")
# ... 작업 ...
log_end(logger, "데이터 수집 스크립트", success=True)
"""
import logging
import sys
from pathlib import Path
from datetime import datetime
from typing import Optional
def get_logger(
name: str,
log_file: Optional[str] = None,
log_dir: Optional[Path] = None,
level: int = logging.INFO,
) -> logging.Logger:
"""
콘솔 + 파일 동시 출력 로거를 생성합니다.
중복 핸들러 방지: 이미 핸들러가 등록된 로거는 그대로 반환합니다.
Args:
name: 로거 이름 (보통 __name__)
log_file: 로그 파일명 (None이면 {name}.log)
log_dir: 로그 파일 저장 디렉토리 (None이면 호출 파일 기준 상위/logs/)
level: 로그 레벨 (기본 INFO)
Returns:
설정된 logging.Logger 인스턴스
"""
logger = logging.getLogger(name)
# 이미 핸들러가 있으면 중복 추가 방지
if logger.handlers:
return logger
logger.setLevel(level)
# 포맷터
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
)
# 콘솔 핸들러
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
# 로그 디렉토리 결정
if log_dir is None:
# 호출 스크립트 기준 상위 디렉토리 / logs/
try:
import inspect
caller_file = Path(inspect.stack()[1].filename)
log_dir = caller_file.parent.parent / "logs"
except Exception:
log_dir = Path("logs")
log_dir.mkdir(parents=True, exist_ok=True)
# 파일 핸들러
file_name = log_file if log_file else f"{name}.log"
file_path = log_dir / file_name
file_handler = logging.FileHandler(file_path, encoding='utf-8')
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
return logger
def log_start(logger: logging.Logger, script_name: str) -> None:
"""
스크립트 시작 구분선 로그
Args:
logger: get_logger()로 생성한 로거
script_name: 스크립트/작업 이름
"""
logger.info("=" * 60)
logger.info(f"{script_name} 시작: {datetime.now().isoformat()}")
logger.info("=" * 60)
def log_end(
logger: logging.Logger,
script_name: str,
success: bool = True,
) -> None:
"""
스크립트 종료 구분선 로그
Args:
logger: get_logger()로 생성한 로거
script_name: 스크립트/작업 이름
success: 성공 여부 (True: "성공", False: "실패")
"""
status = "성공" if success else "실패"
logger.info("=" * 60)
logger.info(f"{script_name} 종료 ({status}): {datetime.now().isoformat()}")
logger.info("=" * 60)
def log_section(logger: logging.Logger, section_name: str) -> None:
"""
섹션 구분선 로그 (단계별 진행 상황 표시용)
Args:
logger: get_logger()로 생성한 로거
section_name: 섹션 이름
"""
logger.info("-" * 40)
logger.info(f"[{section_name}]")
logger.info("-" * 40)