예외 · 에러 표시 · 로깅
본 문서는 실패를 어디서 잡고, 사용자에게 무엇을 보여주며, 무엇을 기록하는지를 정의합니다.
1. 책임 분담
실패를 각 화면이 개별적으로 처리하면 표시 방식이 화면마다 달라지고 누락이 생깁니다. 공통 처리와 화면 처리의 경계를 고정합니다.
| 계층 | 책임 |
|---|---|
HTTP 인터셉터 (shared/api) |
에러 응답 해석, 인증 만료 처리, 재시도, 관측 전송 |
전역 에러 핸들러 (app) |
처리되지 않은 예외 포착, 관측 전송 |
화면 (pages) |
사용자에게 보여줄 내용과 위치 결정 |
인터셉터는 무엇이 일어났는지를 판별하고, 화면은 무엇을 보여줄지를 정합니다. 인터셉터가 토스트를 띄우기 시작하면 화면이 표시를 통제할 수 없게 됩니다.
2. 에러 분류
응답을 다음 다섯 가지로 분류하고, 분류에 따라 처리 위치가 갈립니다.
| 분류 | 조건 | 처리 위치 | 사용자에게 |
|---|---|---|---|
| 네트워크 실패 | 응답 없음, 타임아웃 | 인터셉터가 재시도 후 화면에 전달 | 연결 문제 안내와 재시도 수단 |
| 인증 만료 | 401 | 인터셉터가 세션 정리 후 로그인 이동 | 이동 사유 안내 |
| 권한 없음 | 403 | 화면 | 접근 불가 안내 |
| 입력 오류 | 400, 422 | 화면 | 필드별 메시지 |
| 서버 오류 | 5xx | 화면 | 일시적 오류 안내와 재시도 수단 |
401만 인터셉터가 이동까지 처리합니다. 세션이 끊긴 상태에서 화면이 각자 판단하면 여러 화면이 동시에 로그인으로 이동시키려 하기 때문입니다.
3. 사용자에게 보여줄 것
3.1 메시지 원칙
| 구분 | 준수 지침 (Do) | 금지 지침 (Don't) |
|---|---|---|
| 내용 | 무엇이 실패했고 다음에 무엇을 할 수 있는지 씁니다 | 기술 용어와 상태 코드를 그대로 노출합니다 |
| 어조 | 사실을 진술합니다 | 사용자를 탓하거나 사과를 반복합니다 |
| 출처 | 서버가 준 사용자 대상 메시지를 우선 사용합니다 | 프론트엔드가 임의로 문구를 지어냅니다 |
| 내부 정보 | 추적 식별자만 노출합니다 | 스택 트레이스, 쿼리, 내부 식별자를 표시합니다 |
서버가 사용자 대상 메시지를 내려주면 그것을 표시합니다. 프론트엔드가 상태 코드를 보고 문구를 만들면 서버가 아는 맥락이 소실됩니다.
문의를 위해 추적 식별자를 함께 보여줍니다. 사용자가 그 값을 전달하면 로그에서 해당 요청을 찾을 수 있습니다.
3.2 표시 위치
실패의 범위로 결정합니다.
| 범위 | 표시 위치 |
|---|---|
| 화면 전체를 그릴 수 없음 | 화면 영역 전체를 에러 상태로 대체 |
| 화면 일부 영역만 실패 | 해당 영역만 에러 상태로 대체 |
| 사용자 동작의 결과 | 토스트 또는 동작 지점 인근 |
| 입력값 문제 | 해당 필드 아래 |
목록 조회가 실패했는데 토스트만 띄우고 빈 목록을 보여주는 것을 금지합니다. 사용자는 데이터가 없는 것으로 오해합니다. 실패한 영역은 실패했음을 그 자리에서 표시합니다.
3.3 재시도
일시적 실패(네트워크, 5xx)에는 재시도 수단을 함께 제공합니다. httpResource의 reload()를 호출하는 버튼이면 충분합니다.
인터셉터의 자동 재시도는 조회 요청에만 적용합니다. 변경 요청을 자동 재시도하면 중복 처리가 발생할 수 있습니다.
4. 전역 에러 핸들러
처리되지 않은 예외를 포착합니다. 기본 제공되는 리스너를 등록합니다.
// app/app.config.ts
providers: [provideBrowserGlobalErrorListeners()]전역 핸들러의 역할은 기록과 최소한의 안내입니다. 여기서 업무 로직을 수행하지 않습니다. 이 지점에 도달했다는 것은 예상하지 못한 상태라는 뜻이며, 추가 로직은 상황을 악화시킬 수 있습니다.
5. 로깅
5.1 무엇을 기록하는가
| 기록 | 기록하지 않음 |
|---|---|
| 실패한 요청의 경로와 상태 코드 | 요청 본문 전체 |
| 추적 식별자 | 인증 토큰 |
| 발생 화면의 라우트 | 개인식별정보 (이름, 연락처, 주민번호 등) |
| 브라우저와 화면 크기 | 사용자가 입력한 값 |
주의
개인정보와 토큰을 로그에 출력하는 것을 금지합니다
개발 중 편의로 추가한 console.log(response)가 운영 빌드에 남으면 브라우저 콘솔과 관측 도구에 개인정보가 그대로 축적됩니다. 응답 객체 전체를 출력하는 습관이 가장 흔한 경로입니다. 필요한 필드만 골라 출력합니다.
5.2 콘솔 사용
console.log는 개발 중 임시 확인에만 사용하고 커밋하지 않습니다. 운영에서 의미를 갖는 기록은 관측 도구로 보냅니다.
ESLint의 no-console 규칙으로 차단하되, console.error는 전역 핸들러에서 필요하므로 예외로 둡니다.
5.3 관측
관측 도구 도입 여부는 프로젝트가 결정합니다. 도입하면 다음을 보냅니다.
- 처리되지 않은 예외
- 5xx 응답
- 네트워크 실패
400·403 같은 정상적인 거부는 보내지 않습니다. 잡음이 되어 실제 문제를 가립니다.
전송 코드는 shared/api/에 두고 인터셉터와 전역 핸들러만 호출합니다. 화면이 직접 관측 도구를 호출하는 것을 금지합니다.
6. 개발 환경과 운영 환경
| 항목 | 개발 | 운영 |
|---|---|---|
| 콘솔 상세 출력 | 허용 | 금지 |
| 스택 트레이스 화면 표시 | 허용 | 금지 |
| 원본 에러 객체 보존 | 보존 | 보존 (기록용). 화면에는 미표시 |
빌드 설정으로 분기하며 런타임 조건문으로 판단하지 않습니다. 런타임 분기는 운영 번들에 개발용 코드를 남깁니다.
7. 금지 사항
| 금지 | 사유 |
|---|---|
| 화면마다 401을 개별 처리 | 여러 화면이 동시에 로그인 이동을 시도합니다 |
| 인터셉터에서 토스트 표시 | 화면이 표시를 통제할 수 없게 됩니다 |
| 조회 실패를 빈 상태로 표시 | 사용자가 데이터 없음으로 오해합니다 |
| 변경 요청의 자동 재시도 | 중복 처리가 발생합니다 |
| 스택 트레이스·내부 식별자 화면 노출 | 내부 구조가 드러납니다 |
| 응답 객체 전체를 콘솔 출력 | 개인정보와 토큰이 함께 출력됩니다 |
console.log 커밋 |
운영 빌드에 남습니다 |
| 400·403을 관측 도구로 전송 | 잡음이 실제 문제를 가립니다 |