들어가며
예외 처리와 로깅은 안정적이고 디버깅하기 쉬운 프로그램을 만드는 필수 요소입니다. 올바른 에러 처리로 프로그램의 견고성을 높이고, 로깅으로 문제를 추적하세요.
예외 처리의 기본
예제 1: try-except 기본
# 예외가 없는 경우
try:
result = 10 / 2
print(f"결과: {result}")
except ZeroDivisionError:
print("0으로 나눌 수 없습니다")
# 예외가 있는 경우
try:
result = 10 / 0
print(f"결과: {result}")
except ZeroDivisionError:
print("0으로 나눌 수 없습니다")
# 다양한 예외 처리
try:
value = int("abc")
except ValueError:
print("정수로 변환할 수 없습니다")
출력:
결과: 5.0
0으로 나눌 수 없습니다
정수로 변환할 수 없습니다
예제 2: 여러 예외 처리
def process_data(data):
try:
number = int(data)
result = 100 / number
return result
except ValueError:
print("ValueError: 정수가 아닙니다")
except ZeroDivisionError:
print("ZeroDivisionError: 0으로 나눌 수 없습니다")
except Exception as e:
print(f"예상치 못한 오류: {e}")
# 테스트
process_data("abc") # ValueError
process_data("0") # ZeroDivisionError
process_data("10") # 성공
출력:
ValueError: 정수가 아닙니다
ZeroDivisionError: 0으로 나눌 수 없습니다
예외 처리 심화
예제 3: else와 finally
def divide_numbers(a, b):
try:
result = a / b
except ZeroDivisionError:
print("오류: 0으로 나눌 수 없습니다")
else:
# 예외가 없을 때만 실행
print(f"성공: {a} / {b} = {result}")
finally:
# 항상 실행
print("처리 완료\n")
divide_numbers(10, 2)
divide_numbers(10, 0)
divide_numbers(20, 5)
출력:
성공: 10 / 2 = 5.0
처리 완료
오류: 0으로 나눌 수 없습니다
처리 완료
성공: 20 / 5 = 4.0
처리 완료
예제 4: 사용자 정의 예외
class CustomError(Exception):
"""사용자 정의 예외"""
pass
class AgeError(Exception):
"""나이 관련 예외"""
def __init__(self, age, message="유효하지 않은 나이입니다"):
self.age = age
self.message = message
super().__init__(self.message)
def validate_age(age):
if age < 0:
raise AgeError(age, "나이는 음수일 수 없습니다")
if age > 150:
raise AgeError(age, "나이가 너무 큽니다")
return age
try:
validate_age(25)
print("나이 검증 성공")
except AgeError as e:
print(f"오류: {e}")
try:
validate_age(-5)
except AgeError as e:
print(f"오류: {e}")
출력:
나이 검증 성공
오류: 나이는 음수일 수 없습니다
예제 5: 파일 처리와 with 문
# 수동 관리 (좋지 않음)
file = open("example.txt", "w")
try:
file.write("Hello, World!")
finally:
file.close()
# with를 사용한 관리 (권장)
with open("example.txt", "w") as file:
file.write("Hello, World!")
# 자동으로 파일이 닫힙니다
# 파일 읽기 예제
try:
with open("example.txt", "r") as file:
content = file.read()
print(f"파일 내용: {content}")
except FileNotFoundError:
print("파일을 찾을 수 없습니다")
출력:
파일 내용: Hello, World!
로깅
예제 6: 기본 로깅
import logging
# 로깅 설정
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 다양한 로그 레벨
logger.debug("디버그 메시지")
logger.info("정보 메시지")
logger.warning("경고 메시지")
logger.error("에러 메시지")
logger.critical("심각한 에러")
출력:
2024-01-15 14:30:45,123 - __main__ - DEBUG - 디버그 메시지
2024-01-15 14:30:45,124 - __main__ - INFO - 정보 메시지
2024-01-15 14:30:45,124 - __main__ - WARNING - 경고 메시지
2024-01-15 14:30:45,125 - __main__ - ERROR - 에러 메시지
2024-01-15 14:30:45,125 - __main__ - CRITICAL - 심각한 에러
예제 7: 고급 로깅 설정
import logging
# 로거 생성
logger = logging.getLogger("myapp")
logger.setLevel(logging.DEBUG)
# 콘솔 핸들러
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# 파일 핸들러
file_handler = logging.FileHandler("app.log")
file_handler.setLevel(logging.DEBUG)
# 포맷터
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
# 핸들러 추가
logger.addHandler(console_handler)
logger.addHandler(file_handler)
# 사용
logger.info("애플리케이션 시작")
logger.debug("디버그 정보")
logger.error("오류 발생")
출력:
2024-01-15 14:30:45,123 - myapp - INFO - 애플리케이션 시작
2024-01-15 14:30:45,124 - myapp - ERROR - 오류 발생
예제 8: 예외와 로깅 함께 사용
import logging
logger = logging.getLogger(__name__)
def risky_operation(value):
try:
result = 100 / value
logger.info(f"연산 성공: 100 / {value} = {result}")
return result
except ZeroDivisionError as e:
logger.error(f"0으로 나눔 오류: {e}", exc_info=True)
except Exception as e:
logger.critical(f"예상치 못한 오류: {e}", exc_info=True)
# 테스트
risky_operation(5)
risky_operation(0)
출력:
ERROR:__main__:0으로 나눔 오류: division by zero
Traceback (most recent call last):
File "...", line 7, in risky_operation
result = 100 / value
ZeroDivisionError: division by zero
흔한 실수와 해결책
실수 1: 너무 넓은 예외 처리
틀린 예제:
try:
value = int(input("숫자: "))
result = 100 / value
except: # 모든 예외를 처리 - 위험!
print("오류가 발생했습니다")
올바른 예제:
try:
value = int(input("숫자: "))
result = 100 / value
except ValueError:
print("정수를 입력하세요")
except ZeroDivisionError:
print("0이 아닌 수를 입력하세요")
실수 2: 예외 정보 손실
틀린 예제:
try:
# 코드
pass
except Exception:
print("오류 발생") # 원인을 알 수 없음
올바른 예제:
import logging
import traceback
logger = logging.getLogger(__name__)
try:
# 코드
pass
except Exception as e:
logger.error(f"오류 발생: {e}", exc_info=True)
# 또는
traceback.print_exc()
실수 3: 리소스 누수
틀린 예제:
file = open("data.txt")
lines = file.readlines()
# 예외 발생 시 파일이 닫혀지지 않음
올바른 예제:
with open("data.txt") as file:
lines = file.readlines()
# 자동으로 파일이 닫힘
연습 문제
문제 1: 안전한 계산기
사용자 입력을 받아 나누기를 수행하되, 모든 예외를 처리하세요.
문제 2: 로그 분석
로그 파일을 읽어 각 레벨별 로그 수를 세는 프로그램을 작성하세요.
문제 3: 재시도 로직
실패할 수 있는 작업을 여러 번 재시도하는 함수를 작성하세요.
풀이
문제 1 풀이
def safe_divide():
try:
a = int(input("첫 번째 숫자: "))
b = int(input("두 번째 숫자: "))
result = a / b
print(f"{a} / {b} = {result}")
except ValueError:
print("정수를 입력하세요")
except ZeroDivisionError:
print("0으로 나눌 수 없습니다")
except Exception as e:
print(f"오류: {e}")
safe_divide()
문제 2 풀이
import logging
from collections import Counter
logging.basicConfig(filename='app.log', level=logging.DEBUG)
logger = logging.getLogger(__name__)
# 로그 생성
for i in range(10):
if i % 3 == 0:
logger.info(f"정보 메시지 {i}")
elif i % 2 == 0:
logger.warning(f"경고 메시지 {i}")
else:
logger.error(f"에러 메시지 {i}")
# 로그 분석
with open('app.log') as f:
lines = f.readlines()
levels = [line.split('-')[2].strip() for line in lines]
counter = Counter(levels)
print(f"로그 통계: {dict(counter)}")
문제 3 풀이
import logging
import time
logger = logging.getLogger(__name__)
def retry_operation(func, max_retries=3, delay=1):
"""작업을 재시도하는 데코레이터"""
for attempt in range(max_retries):
try:
return func()
except Exception as e:
logger.warning(f"시도 {attempt + 1}/{max_retries} 실패: {e}")
if attempt < max_retries - 1:
time.sleep(delay)
else:
raise
def unreliable_task():
import random
if random.random() < 0.7:
raise Exception("작업 실패")
return "성공"
try:
result = retry_operation(unreliable_task)
print(result)
except Exception as e:
print(f"최종 실패: {e}")
마무리
예외 처리와 로깅은 프로덕션 코드의 안정성과 유지보수성을 크게 향상시킵니다. 항상 적절한 예외를 처리하고 충분한 로그를 남기세요.