---
type: knowledge
domain: backend
status: active
last-reviewed: 2026-07-27
---

# 에러 처리와 로깅 실전

> 한 줄 정의
> 에러 처리의 목표는 "안 죽는 것"이 아니라 **모든 실패가 분류되고, 기록되고, 필요한 것만 알림이 되는 것**이다. 장애 발생 후 대응은 [[장애 대응 실전]], PII 취급은 [[개인정보와 컴플라이언스]] — 여기는 코드와 로그의 규율이다.

## 에러 분류 — 셋을 섞으면 전부 망가진다

| 분류 | HTTP | 로그 레벨 | 알림 |
|------|------|----------|------|
| 사용자 잘못 (형식 오류·없는 리소스·권한 없음) | 4xx | INFO~WARN | 안 한다 — 400 폭증 같은 **패턴**만 감시 |
| 도메인 규칙 위반 (재고 부족·기한 초과·상태 전이 불가) | 4xx (409·422류) | INFO + 도메인 에러 코드 | 안 한다 — 정상 업무의 일부다 |
| 시스템 실패 (DB 다운·타임아웃·널 참조) | 5xx | ERROR | 예산 기반으로 한다 (아래) |

- 도메인 규칙 위반을 500으로 던지면 알림이 오염되고, 시스템 실패를 400으로 감싸면 결함이 사용자 탓으로 은폐된다 — **분류가 틀리면 이후 전부(응답·로그·알림)가 틀린다.**
- 예외 타입도 이 분류를 따라 설계한다: 도메인 예외는 도메인 계층이 던지고, 최상위 핸들러 한 곳이 HTTP로 번역한다.

## API 에러 응답 — 단일 스키마

- 프로젝트당 한 번 정의하고 전 엔드포인트가 따른다 → [[백엔드 원리]] envelope 통일. 최소 필드: **`code`(기계 판독용 안정 식별자) · `message`(사용자 표시 가능 문구) · `traceId`**.
- `code`는 계약이다 — 프론트가 분기하는 대상이므로 한 번 배포되면 이름을 바꾸지 않는다. `message`는 언제든 바뀔 수 있는 표시용이다 — 단 노출 여부와 최종 문구는 프론트가 `code` 기준으로 결정한다(서버 문구 직노출 금지, [[프론트엔드 에러 처리와 관측]]).
- 5xx의 `message`에 스택 트레이스·SQL·내부 클래스명 노출 금지 — 사용자에겐 "일시적 오류 + traceId", 상세는 로그에 → [[보안 원리]].
- `traceId`를 응답에 넣는 순간 "사용자 신고 → 로그 직행"이 된다. 없으면 시각과 정황으로 로그를 뒤지는 고고학이다.

## 예외 삼키기 금지

- **빈 catch, catch 후 null 반환, catch-log-continue**는 실패를 "나중에 다른 곳에서 터지는 미스터리"로 바꾸는 코드다.
- catch가 정당한 경우는 둘뿐: **그 자리에서 복구할 수 있거나(폴백·재시도), 맥락을 더해 다시 던지거나.** 둘 다 아니면 잡지 않는다 — 최상위 핸들러가 받게 둔다.
- 백그라운드 작업·이벤트 소비자의 예외는 최상위 핸들러가 없다 — 워커 루프에 실패 기록+상태 갱신을 명시적으로 넣지 않으면 그대로 무성 실패다 → [[백그라운드 작업과 스케줄링]].

## 구조화 로깅 — 검색이 안 되는 로그는 없는 로그

- **JSON 구조화 + 요청/상관관계 ID 전 구간 전파**가 기본 → [[백엔드 원리]]. "하나의 요청을 시간순으로 다시 살 수 있는가"가 로그 품질의 시험이다.
- 표준 필드를 고정한다: 시각·레벨·traceId·(있으면) 사용자/테넌트 ID·이벤트명·소요 시간. 자유 문장은 사람용, 필드는 기계용.
- 같은 예외를 계층마다 중복 로그 금지 — 잡아서 다시 던지는 곳은 로그하지 않고, **최종 처리 지점 한 곳**만 남긴다. 중복 로그는 에러 1건을 5건으로 보이게 해 집계를 망친다.

## 로그 레벨 — 운영 필터 기준으로 정한다

| 레벨 | 기준 | 오용 |
|------|------|------|
| ERROR | 시스템 실패 — 사람이 봐야 할 수도 있는 것만 | 도메인 규칙 위반을 ERROR로 → 진짜 장애가 소음에 묻힌다 |
| WARN | 지금은 동작하나 방치하면 사고 (재시도 성공·풀 80%·폐기 예정 API 사용) | "혹시 몰라서 WARN" 남발 |
| INFO | 업무 이벤트의 발자취 (생성·상태 전이·발송) | 루프 안 INFO — 대량 처리에서 로그가 데이터가 된다 |
| DEBUG | 개발 진단용 — 운영 기본 OFF | 운영에서 DEBUG 상시 ON = 비용+PII 노출면 확대 |

## 개인정보는 로그에 쓰지 않는다

- 이메일·전화번호·주민번호·토큰·비밀번호는 **로그 금지 또는 마스킹**. 요청/응답 전문(body) 로깅은 편하지만 PII 유출 경로 1순위다 — 필드 화이트리스트로 남긴다.
- 마스킹은 각 로그 호출부의 선의가 아니라 **로깅 파이프라인의 공통 계층**에서 강제한다 → [[개인정보와 컴플라이언스]].
- 외주 지점: 로그도 발주사 데이터다 — 발주사 환경 밖(개인 PC·외부 SaaS)으로 로그 반출 금지, 보존 기간은 계약·규제 기준을 따른다.

## 알림 기준 — 모든 5xx가 아니라 예산 기반

- 5xx 1건마다 알림 = 일주일 안에 전원이 알림을 끈다. **알림 피로는 알림 없음과 같다.**
- 기준은 에러 예산·비율로: "5분간 에러율 N% 초과", "동일 에러 급증", "DLQ에 신규 유입", "스케줄 작업 마지막 성공이 임계 초과". 절대 건수보다 **평소 대비 이상**이 신호다.
- 알림마다 받는 사람과 첫 행동(런북 링크)이 정해져 있어야 한다 — 행동 없는 알림은 소음으로 재분류한다 → [[장애 대응 실전]].

## 안티패턴

- **`Exception`을 최상위에서 잡아 전부 200 + "실패" 메시지로** — 모니터링 지표가 전부 초록인 채로 시스템이 죽는다.
- **에러 코드 즉흥 작명** — 엔드포인트마다 다른 코드 체계는 프론트에 문자열 비교 분기를 강요한다.
- **printf/콘솔 디버깅 흔적 방치** — 구조화 파이프라인 밖의 출력은 수집·검색·마스킹 전부 우회한다.
- **"로그는 많을수록 좋다"** — 신호 대 잡음이 지표다. 아무도 안 보는 로그 1TB보다 잘 설계된 이벤트 로그 1GB가 낫다.

## 관련 문서

- [[00_백엔드 허브]] — 06_백엔드 진입점
- [[백엔드 원리]] — envelope 통일·구조화 로그·최소 지표
- [[백그라운드 작업과 스케줄링]] — 작업 실패의 기록과 관측
- [[프론트엔드 에러 처리와 관측]] — 클라이언트 측 짝: 에러 수집·traceId 노출·자가 수집 엔드포인트
- [[장애 대응 실전]] — 알림 이후: 대응·런북·포스트모템
- [[개인정보와 컴플라이언스]] — PII 마스킹·보존
- [[보안 원리]] — 내부 정보 노출 최소화
