1 print vs logger 개요
- Python에서 메시지를 출력하고 프로그램의 동작을 추적하는 방법은 크게 두 가지가 있다:
print()함수와logging모듈.
- 각각의 특성과 적절한 사용 시나리오를 이해하면 효과적인 디버깅과 로그 관리가 가능하다.
2 print 함수
print()는 Python의 내장 함수로, 콘솔에 즉시 메시지를 출력한다.
2.1 장점
- 간단함: 설정 없이 즉시 사용 가능
- 직관적: 코드 흐름 따라가기 쉬움
- 빠른 디버깅: 일회성 확인에 적합
2.2 단점
- 로그 레벨 제어 불가: 모든 메시지가 동일한 중요도로 취급된다.
- 파일 저장 불가: 콘솔에만 출력되며, 나중에 확인할 수 없다.
- 타임스탬프 없음: 언제 발생한 메시지인지 알 수 없다.
- 프로덕션 관리 어려움: 배포 시 모든 print문을 수동으로 제거하거나 주석 처리해야 한다.
2.3 적합한 사용 사례
- 학습 및 실험 코드
- 간단한 스크립트 디버깅
- 일회성 값 확인
- 빠른 프로토타이핑
3 logging 모듈
Python 표준 라이브러리의 logging 모듈은 체계적인 로그 관리 시스템을 제공한다.
import logging
logger = logging.getLogger(__name__)
logger.info("프로그램 시작")
logger.warning("메모리 부족")
logger.error("파일 없음")3.1 로그 레벨
로그의 중요도를 5단계로 구분한다.
| 레벨 | 값 | 용도 | 예시 |
|---|---|---|---|
| DEBUG | 10 | 상세한 디버깅 정보 | 변수 값, 함수 호출 순서 |
| INFO | 20 | 일반 정보 | 작업 시작/종료, 정상 동작 |
| WARNING | 30 | 경고 메시지 | 비권장 사용, 리소스 부족 |
| ERROR | 40 | 에러 발생 | 예외 처리, 작업 실패 |
| CRITICAL | 50 | 치명적 오류 | 시스템 중단, 데이터 손실 |
3.2 기본 설정
3.2.1 간단한 설정
import logging
# 기본 설정
logging.basicConfig(
level=logging.INFO, # INFO 이상만 출력
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
logger.info("애플리케이션 시작")출력 예시:
2026-02-14 10:30:45,123 - __main__ - INFO - 애플리케이션 시작
3.2.2 파일 저장
3.3 실용 예제
3.3.1 함수 실행 로깅
import logging
logger = logging.getLogger(__name__)
def process_data(data):
logger.info(f"데이터 처리 시작: {len(data)}건")
try:
result = []
for item in data:
if item is None:
logger.warning(f"None 값 발견, 건너뜀")
continue
processed = transform(item)
result.append(processed)
logger.info(f"처리 완료: {len(result)}건 성공")
return result
except Exception as e:
logger.error(f"처리 실패: {e}", exc_info=True)
raise3.3.2 파일 작업 로깅
import logging
from pathlib import Path
logger = logging.getLogger(__name__)
def load_json_safe(file_path: Path):
"""JSON 파일을 안전하게 로드"""
if not file_path.exists():
logger.warning(f"파일 없음: {file_path}")
return []
try:
with open(file_path, encoding='utf-8') as f:
data = json.load(f)
logger.info(f"파일 로드 성공: {file_path.name}")
return data
except json.JSONDecodeError as e:
logger.error(f"JSON 파싱 실패: {file_path.name} - {e}")
return []
except IOError as e:
logger.error(f"파일 읽기 실패: {file_path.name} - {e}")
return []3.3.3 API 호출 로깅
import logging
import requests
logger = logging.getLogger(__name__)
def fetch_data(url):
logger.info(f"API 호출: {url}")
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
logger.info(f"API 성공: {response.status_code}")
return response.json()
except requests.Timeout:
logger.error("API 타임아웃")
return None
except requests.HTTPError as e:
logger.error(f"HTTP 오류: {e.response.status_code}")
return None
except Exception as e:
logger.critical(f"예상치 못한 오류: {e}", exc_info=True)
return None3.4 고급 설정
3.4.1 로거 계층 구조
3.4.2 포맷 커스터마이징
import logging
# 상세한 포맷
formatter = logging.Formatter(
'%(asctime)s | %(name)s | %(levelname)-8s | %(filename)s:%(lineno)d | %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger = logging.getLogger(__name__)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)출력 예시:
2026-02-14 10:30:45 | myapp.main | INFO | main.py:42 | 애플리케이션 시작
3.4.3 로그 로테이션
파일 크기나 시간 기준으로 로그 파일을 자동 분할한다.
import logging
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
# 크기 기준 로테이션 (10MB마다 새 파일)
size_handler = RotatingFileHandler(
'app.log',
maxBytes=10*1024*1024, # 10MB
backupCount=5 # 최대 5개 파일 유지
)
# 시간 기준 로테이션 (매일 자정)
time_handler = TimedRotatingFileHandler(
'app.log',
when='midnight',
interval=1,
backupCount=30 # 30일치 보관
)
logger = logging.getLogger(__name__)
logger.addHandler(size_handler)
logger.setLevel(logging.INFO)4 Streamlit 환경에서의 로깅
4.1 Streamlit의 특성
Streamlit은 웹 애플리케이션이지만 터미널에서 실행되므로, 콘솔 출력을 볼 수 있다.
4.2 print 사용의 문제점
import streamlit as st
def load_data():
print("데이터 로딩 중...") # 터미널에만 출력
data = fetch_data()
print(f"로드 완료: {len(data)}건") # 사용자는 볼 수 없음
return data
st.title("데이터 대시보드")
data = load_data()문제: - 사용자는 진행 상황을 볼 수 없음 - 터미널 접근 권한 필요 - 로그 추적 불가
4.3 logger 권장 이유
4.3.1 파일 기반 로그 관리
import logging
import streamlit as st
from pathlib import Path
# 로그 디렉토리 설정
LOG_DIR = Path(__file__).parent / 'logs'
LOG_DIR.mkdir(exist_ok=True)
# 로거 설정
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(LOG_DIR / 'app.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
def load_data():
logger.info("데이터 로딩 시작")
try:
data = fetch_data()
logger.info(f"로드 완료: {len(data)}건")
return data
except Exception as e:
logger.error(f"로드 실패: {e}", exc_info=True)
st.error("데이터를 불러올 수 없습니다")
return None
st.title("데이터 대시보드")
data = load_data()4.3.2 사용자 피드백과 로그 분리
import logging
import streamlit as st
logger = logging.getLogger(__name__)
def process_file(uploaded_file):
# 사용자에게 진행 상황 표시
with st.spinner("파일 처리 중..."):
# 내부적으로 상세 로그 기록
logger.info(f"파일 처리 시작: {uploaded_file.name}")
try:
data = parse_file(uploaded_file)
logger.debug(f"파싱 결과: {len(data)}행")
result = transform_data(data)
logger.info("데이터 변환 완료")
# 사용자에게는 간단한 메시지
st.success("파일 처리 완료!")
return result
except Exception as e:
# 상세한 에러는 로그에
logger.error(f"처리 실패: {e}", exc_info=True)
# 사용자에게는 친절한 메시지
st.error("파일을 처리할 수 없습니다. 형식을 확인해주세요.")
return None4.3.3 디버깅 모드 구현
import logging
import streamlit as st
# 사이드바에 디버그 모드 토글
debug_mode = st.sidebar.checkbox("디버그 모드", value=False)
# 디버그 모드에 따라 로그 레벨 조정
if debug_mode:
logging.getLogger().setLevel(logging.DEBUG)
st.sidebar.info("디버그 로그 활성화")
else:
logging.getLogger().setLevel(logging.INFO)
logger = logging.getLogger(__name__)
@st.cache_data
def expensive_computation(param):
logger.debug(f"계산 시작: param={param}")
result = complex_calc(param)
logger.debug(f"계산 완료: result={result}")
return result4.3.4 실시간 로그 뷰어
import logging
import streamlit as st
from pathlib import Path
logger = logging.getLogger(__name__)
# 로그 파일 경로
LOG_FILE = Path("logs/app.log")
# 사이드바에 로그 뷰어 추가
with st.sidebar:
st.subheader("최근 로그")
if st.button("로그 새로고침"):
if LOG_FILE.exists():
with open(LOG_FILE, 'r') as f:
logs = f.readlines()
# 최근 20줄만 표시
st.code('\n'.join(logs[-20:]), language='log')
else:
st.info("로그 파일이 없습니다")
# 메인 애플리케이션
st.title("애플리케이션")
if st.button("작업 실행"):
logger.info("사용자가 작업 실행 버튼 클릭")
result = perform_task()
st.success("완료!")4.4 Streamlit 환경 권장 패턴
import logging
import streamlit as st
from pathlib import Path
from datetime import datetime
# ===== 로거 설정 (파일 상단) =====
LOG_DIR = Path(__file__).parent / 'logs'
LOG_DIR.mkdir(exist_ok=True)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s | %(name)s | %(levelname)s | %(message)s',
handlers=[
logging.FileHandler(LOG_DIR / f'app_{datetime.now():%Y%m%d}.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
# ===== 애플리케이션 시작 =====
logger.info("Streamlit 애플리케이션 시작")
st.title("데이터 분석 도구")
# ===== 데이터 로드 =====
@st.cache_data
def load_data():
logger.info("데이터 로드 시작")
try:
data = pd.read_csv('data.csv')
logger.info(f"데이터 로드 성공: {len(data)}행")
return data
except Exception as e:
logger.error(f"데이터 로드 실패: {e}", exc_info=True)
st.error("데이터를 불러올 수 없습니다")
return None
data = load_data()
# ===== 사용자 상호작용 =====
if st.button("분석 실행"):
logger.info("사용자가 분석 실행 요청")
with st.spinner("분석 중..."):
try:
result = analyze(data)
logger.info("분석 완료")
st.success("분석이 완료되었습니다")
st.write(result)
except Exception as e:
logger.error(f"분석 실패: {e}", exc_info=True)
st.error("분석 중 오류가 발생했습니다")5 비교 요약
| 항목 | logging | |
|---|---|---|
| 설정 | 불필요 | 필요 (한 번만) |
| 사용 난이도 | 쉬움 | 중간 |
| 로그 레벨 | 없음 | 5단계 (DEBUG~CRITICAL) |
| 파일 저장 | 불가 (리다이렉션 필요) | 기본 지원 |
| 타임스탬프 | 없음 | 자동 기록 |
| 포맷 제어 | 수동 | 자동 |
| 프로덕션 관리 | 어려움 | 쉬움 (레벨로 제어) |
| 성능 | 빠름 | 약간 느림 (무시 가능) |
| 적합한 환경 | 학습, 간단한 스크립트 | 프로덕션, 라이브러리 |
6 사용 가이드라인
6.1 print 사용
- 학습 코드 및 실험
- 간단한 스크립트 (< 100줄)
- 일회성 디버깅
- 빠른 값 확인
6.2 logger 사용
- 프로덕션 애플리케이션
- 라이브러리 및 패키지
- 웹 애플리케이션 (Django, Flask, Streamlit)
- 장기 실행 프로세스
- 팀 프로젝트
6.3 혼용 가능
개발 초기에는 print를 쓰다가, 프로덕션 준비 시 logger로 전환할 수 있다.
7 마이그레이션 전략
7.1 단계별 전환
7.1.1 1단계: 기본 설정 추가
7.1.2 2단계: 주요 print를 logger로 변경
7.1.3 3단계: 파일 저장 추가
7.1.4 4단계: 레벨 세분화
8 결론
- 학습 및 간단한 스크립트:
print사용 - 프로덕션 애플리케이션:
logging필수 - Streamlit 등 웹 앱:
logging으로 체계적 관리 - 팀 프로젝트:
logging으로 일관성 유지
로깅은 초기 설정 비용이 있지만, 장기적으로 디버깅 시간을 크게 단축하고 시스템 안정성을 향상시킨다.