except 블록에서 새 예외를 던졌더니 원래 원인이 로그에서 사라진 이유
문제 발생
낮은 레벨의 예외를 잡아서 더 의미 있는 커스텀 예외로 바꿔 던지도록 만들었는데, 로그에 찍힌 트레이스백만으로는 원래 무엇 때문에 실패했는지(원본 예외의 원인) 알 수 없었습니다.
try:
connect_to_db()
except ConnectionError:
raise ServiceUnavailableError("DB 연결 실패")원인 분석
Python은 except 블록 안에서 새 예외를 던질 때 자동으로 암묵적 예외 체이닝을 합니다 — 트레이스백에 "During handling of the above exception, another exception occurred"라는 문구와 함께 원본 예외도 함께 출력됩니다. 하지만 이건 암묵적이라서, 로그 포맷터나 예외 처리 도구에 따라 이 연결 정보가 잘리거나 무시되는 경우가 있고, 코드를 읽는 사람 입장에서도 두 예외가 진짜로 인과관계가 있는지 명시적으로 드러나지 않습니다.
해결 방안
raise ... from ... 구문으로 원인 예외를 명시적으로 지정합니다.
try:
connect_to_db()
except ConnectionError as e:
raise ServiceUnavailableError("DB 연결 실패") from e이렇게 하면 새 예외의 __cause__ 속성에 원본 예외가 명시적으로 연결되고, 트레이스백도 "암묵적" 대신 "직접적인 원인(the above exception was the direct cause)"으로 명확하게 표시됩니다 — 로깅 도구나 에러 트래킹 서비스(Sentry 등)가 이 명시적 체인을 더 안정적으로 인식하는 경우가 많습니다.
- 원본 예외가 정말로 무관한 세부 구현 오류라 사용자/호출부에게 굳이 노출하고 싶지 않다면
raise ... from None으로 체이닝 자체를 명시적으로 끊을 수 있습니다 — 이 경우도 "의도적으로 숨겼다"는 것이 코드에 드러나므로, 그냥 아무 처리 없이 새 예외만 던지는 것보다 낫습니다. - 로그를 남길 때는
logging.exception()이나traceback.print_exc()처럼 전체 트레이스백(체인 포함)을 남기는 방법을 쓰고, 예외 메시지 문자열 하나만 남기는 방식은 피합니다 — 체이닝 정보 자체가 메시지에는 포함되지 않기 때문입니다. - 커스텀 예외 계층을 설계할 때, "저수준 예외를 도메인 예외로 감싸 던진다"는 패턴 자체는 좋은 설계입니다 — 다만 그 과정에서 원인 정보를 잃지 않도록 항상
from을 쓰는 습관이 필요합니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.