--- id: observability-error-reporting title: Error Reporting — Sentry / 분류 / 우선순위 category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [observability, error-reporting, sentry, vibe-coding] tech_stack: { language: "TypeScript / Sentry / Bugsnag", applicable_to: ["Web", "Backend", "Mobile"] } applied_in: [] aliases: [Sentry, exception capture, source maps, fingerprinting] --- # Error Reporting > 로그만으로는 에러 우선순위 판단 어려움. **Sentry / Bugsnag 같은 도구**가 fingerprint / count / 영향 사용자 수 / breadcrumb / source map / 알림을 묶어줌. 단 **모든 throw 가 capture 대상은 아님**. ## 📖 핵심 개념 - Fingerprint: 같은 종류 에러 묶기. 1만번 발생해도 하나의 issue. - Breadcrumb: 에러 직전의 사용자/시스템 액션 시퀀스. - Source map: minified 코드 stack 을 원본 매핑. - Release: 어떤 빌드에서 발생했는지. SHA / version 으로. - Sample rate: 트래픽 큰 곳은 sampling. ## 💻 코드 패턴 ### Sentry Node ```ts import * as Sentry from '@sentry/node'; Sentry.init({ dsn: process.env.SENTRY_DSN, environment: process.env.NODE_ENV, release: process.env.GIT_SHA, tracesSampleRate: 0.1, profilesSampleRate: 0.1, beforeSend(event, hint) { // PII redact if (event.user?.email) event.user.email = '[REDACTED]'; // 무시할 에러 if (hint.originalException instanceof KnownClientError) return null; return event; }, }); // Express integration app.use(Sentry.Handlers.requestHandler()); app.use(Sentry.Handlers.tracingHandler()); // ... routes ... app.use(Sentry.Handlers.errorHandler()); ``` ### Sentry Browser (Vite/Next) ```ts import * as Sentry from '@sentry/react'; Sentry.init({ dsn, release, integrations: [Sentry.browserTracingIntegration(), Sentry.replayIntegration()], tracesSampleRate: 0.1, replaysSessionSampleRate: 0.01, replaysOnErrorSampleRate: 1.0, }); // React error boundary }>{children} ``` ### 사용자 컨텍스트 ```ts Sentry.setUser({ id: user.id, email: user.email }); // 로그아웃 Sentry.setUser(null); ``` ### Custom fingerprint — 잘못 묶이는 경우 ```ts Sentry.captureException(err, { fingerprint: ['stripe-decline', err.code], tags: { provider: 'stripe', errorCode: err.code }, extra: { orderId, userId }, }); ``` ### 의도적 무시 ```ts // 사용자가 cancel 한 fetch — 에러 X fetch(...).catch(e => { if (e.name === 'AbortError') return; // Sentry 안 보냄 Sentry.captureException(e); }); ``` ## 🤔 의사결정 기준 | 에러 종류 | capture | |---|---| | 예측된 도메인 실패 (잔액 부족) | ❌ — 정상 흐름 | | 4xx (클라이언트 잘못) | 보통 ❌ | | 5xx (서버 버그) | ✅ | | 외부 API 실패 (재시도 후 final fail) | ✅ + provider tag | | Browser unhandled rejection | ✅ (자동) | | React render error | ✅ (ErrorBoundary onError) | | AbortError | ❌ | | Network offline | ❌ 또는 별도 이벤트 | ## ❌ 안티패턴 - **모든 에러 capture**: 노이즈 → 진짜 버그 파묻힘. - **PII 그대로**: 이메일 / 카드 / 토큰. beforeSend 에서 redact. - **source map 안 업로드**: stack 이 minified — 디버깅 불가. CI 가 release 와 함께 업로드. - **release 태그 없음**: 어떤 버전 사고인지 모름. git SHA 자동. - **fingerprint 잘못**: 같은 에러가 1000개 issue 로 분리. 또는 다른 에러가 한 issue 로. - **알림 없음 / 알림 폭주**: critical 레벨만 PagerDuty. 나머지는 Slack/이메일. - **breadcrumb 에 민감정보**: form 입력 자동 캡처가 비밀번호 leak. denyUrls / mask. - **dev 에서도 Sentry on**: 노이즈. environment 분리. ## 🤖 LLM 활용 힌트 - 새 catch 블록: "예측 가능 vs 시스템 에러 분류 후 system 만 capture" 강제. - ErrorBoundary onError + captureException + extra context. ## 🔗 관련 문서 - [[Error_Handling_Result_vs_Throw]] - [[Observability_Structured_Logging]]