들어가며
Sentry는 서비스에서 발생하는 에러를 실시간으로 수집하고 대시보드에서 확인할 수 있는 모니터링 툴이다.
우테코에서 미션 요구사항으로 구축을 했으나, 처음 써보는 툴인 데다 당시 우선순위가 높지 않다 보니 기본적인 초기화 세팅만 해두었다. 에러가 발생해도 잘 활용하지 못하고 있었다.
하지만 사용자로부터 직접 에러 발생 피드백을 받으면서, 사소한 에러라도 사용자의 신뢰도를 떨어트릴 수 있고 이탈로 이어질 수 있겠다는 생각이 들었다. 실제로 연구에 따르면, 기술적 오류는 사용자의 분노와 실망에 유의한 영향을 미치고, 이는 이탈 의도로 직결된다고 한다.
그래서 방치되고 있던 Sentry를 실질적인 디버깅 도구로 만들기 위한 개선 작업을 시작했다.
문제

기존엔 에러 타입, 메시지, 상태코드, 페이지 URL은 확인할 수 있었다. 하지만 어떤 API 엔드포인트를 호출하다 실패했는지, 서버가 어떤 비즈니스 에러 코드를 반환했는지는 알 수 없었다. 대강 무슨 에러인지는 보이는데, 정확히 어떤 기능에서, 어떤 이유로 발생했는지 추적하기 어려운 상태였다.
스택 트레이스를 확인해봐도 마찬가지였다. 난독화된 위치 정보만 남아 정확한 에러 발생 지점을 추적할 수 없었다.
해결 과정
1. Tag, Context
조사 내용
Sentry 공식 문서에서 Tags와 Contexts의 역할을 확인했다. 둘 다 이벤트에 메타데이터를 추가하는 방법이지만 역할이 다르다.
- Tags: Sentry UI에서 필터링·집계가 가능하다. Sentry 내부에서 인덱싱되어 대시보드 필터로 활용된다.
- Contexts: 필터링은 되지 않지만 이벤트 상세 페이지에서 조회할 수 있다. 상세 분석용 정보를 담는 용도다.
여기서 Tags에 담을 데이터를 잘 골라야 한다는 점이 중요하다. Tags는 distinct 값이 많을수록 인덱스 낭비가 발생하고 필터 자체가 무의미해진다. http_status(401, 500 등)는 값이 유한해 "이번 주 401 에러가 몇 건인지" 바로 집계할 수 있다.
반면 /moments/123, /moments/456처럼 리소스 ID가 붙는 endpoint는 요청마다 값이 달라져 태그로는 의미가 없다. 상세 분석이 필요할 때 꺼내보는 Contexts에 배치해야 한다.
| Tags | Contexts | |
| 필터링 | O | X |
| 적합한 데이터 | 값이 유한한 것 (http_status, domain) | 값이 무한하거나 상세 분석용 (endpoint, 서버 응답) |
적용 과정
Tags/Contexts 구분 기준을 적용해 Axios 응답 인터셉터와 ErrorBoundary에 추가했다.
API 인터셉터
Sentry.captureException(error, {
tags: {
domain, // auth, moment 등 — 도메인별 에러 패턴 집계
http_method, // GET, POST 등 — 메서드별 패턴 감지
http_status, // 401, 500 등 — 인증/서버 문제 분류
error_code, // T-001, V-003 등 — 서버 비즈니스 로직 에러 추적
},
contexts: {
request: { url, endpoint },
response: { status, statusText, serverError },
},
});
ErrorBoundary
Sentry.captureException(error, {
tags: {
errorBoundary: true, // API 에러와 UI 크래시를 대시보드에서 즉시 구분
error_type: error.constructor.name, // TypeError, ReferenceError 등 — 종류별 집계
priority: 'high', // 화면이 죽는 심각한 케이스 — 최우선 처리
},
contexts: {
react: {
componentStack: errorInfo.componentStack, // 어느 컴포넌트 트리에서 크래시 발생했는지
},
error_details: {
message: error.message,
},
},
});
에러 수집 전략 - 팀원들과의 논의
처음에는 다음과 같은 방식으로 에러 코드 화이트리스트 방식으로 중요한 에러만 선별 수집하려 했다.
const MONITORED_ERRORS = ['T-001', 'T-002', 'G-001'];
const shouldCollectError = (errorCode: string, status: number) => {
if (status >= 500) return true;
return MONITORED_ERRORS.includes(errorCode);
};
하지만 이 방식은 전제가 필요했다. "어떤 에러가 중요한가"를 판단하려면 먼저 어떤 에러가 발생하는지 알아야 한다. 서비스 초기라 데이터가 없는 상황에서 기준을 세우는 건 어려운 일이었다. 잘못된 기준을 세우면 중요한 에러를 누락할 위험도 있었다.
팀원들과 논의한 결과, 일단 모든 에러를 수집하며 패턴을 파악한 뒤, 추후 필터링 기준을 수립하는 방향으로 합의했다.
2. Sentry Source Map 업로드
Source Map이란?
프로덕션 환경에 배포되는 JavaScript 코드는 번들러에 의해 압축·난독화된다. 그 결과 에러가 발생하면 스택 트레이스는 아래처럼 찍힌다.
/js/vendors.d447d480b08231bfc529.js in Le at line 1:219510
Source Map은 이 난독화된 코드를 원본 코드로 되돌리는 지도다. Source Map이 적용되면 같은 에러가 이렇게 바뀐다.
vendors.d447d480b08231bfc529.js:1:219510
→ src/features/auth/api/useSignupMutation.ts:24
단, Source Map을 번들과 함께 배포하면 원본 소스코드가 외부에 노출된다. Sentry에만 업로드하고 외부에는 노출하지 않는 방식이 필요하다.
소스맵 업로드 방법
방법 1. sentry-cli 수동 업로드
매 배포마다 직접 CLI를 실행해 수동으로 업로드 하는 방식이다.
sentry-cli releases files <release> upload-sourcemaps ./dist
방법 2. @sentry/webpack-plugin 자동화
빌드 시 Debug ID를 각 번들에 자동 주입해 에러·소스맵을 매칭한다. CI/CD에 통합하면 빌드마다 자동으로 처리된다.
적용 과정
수동 업로드는 누락 가능성이 있다고 판단했고, 이미 프로젝트에 CI/CD 플로우가 구축되어 있어 반영이 간편할 것이라 생각해 방법2를 선택했다.
Sentry 공식문서에 적용과정이 자세히 설명되어있어 그 방식을 따라갔다.
webpack 설정 파일에 관련 설정들을 기입하고, CI/CD에 기입했다.
// webpack.config.js
const { sentryWebpackPlugin } = require("@sentry/webpack-plugin");
module.exports = {
devtool: "hidden-source-map",
plugins: [
sentryWebpackPlugin({
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
sourcemaps: {
filesToDeleteAfterUpload: ["./**/*.map"],
},
}),
],
};
- hidden-source-map: 번들에 소스맵 참조 URL을 포함하지 않아 브라우저에서 접근 불가
- filesToDeleteAfterUpload: 업로드 후 소스맵 파일 자동 삭제


3. Session Replay
조사 내용
Tags·Contexts와 소스맵으로 에러 발생 위치와 컨텍스트는 파악할 수 있게 됐다. 하지만 어떤 사용자 흐름에서 에러가 발생했는지 재현이 어려운 케이스가 있을 수 있다.
Session Replay를 사용하면 에러 발생 순간 전후 사용자 화면을 영상으로 재생할 수 있다. 설정 시 두 가지를 파악했다.
- 샘플링 구조
- replaysSessionSampleRate: 전체 세션 중 녹화할 비율. 모든 세션을 녹화하면 Sentry 할당량을 빠르게 소모하므로 적절히 조절해야 한다.
- replaysOnErrorSampleRate: 에러가 발생한 세션만 선별 녹화하는 비율. 디버깅 목적이라면 이 옵션만 활성화하는 것이 효율적이다.
- 개인정보 보호
- Replay는 사용자 화면을 그대로 녹화하므로 개인정보 노출 위험이 있다. maskAllText·maskAllInputs 옵션으로 텍스트와 입력 필드를 마스킹할 수 있다.
적용 과정
Sentry.init({
dsn: process.env.SENTRY_DSN,
replaysSessionSampleRate: 0, // 일반 세션: 녹화 안 함
replaysOnErrorSampleRate: 1.0, // 에러 세션: 100% 녹화
integrations: [
Sentry.replayIntegration({
maskAllText: true,
maskAllInputs: true,
}),
],
});
현재 사용자가 많지 않은 환경이라 Tags·Contexts에서 모든 에러를 수집했던 것 처럼, 에러 세션은 1.0으로 설정해 전부 수집하도록 했다. 마스킹 설정으로 개인정보를 보호하면서도 사용자 흐름을 파악할 수 있게 했다.

마무리
기존에는 에러가 발생해도 어디서, 왜 발생했는지 파악하기 어려워 Sentry가 사실상 방치 상태였다. 이번 개선으로 세 가지가 갖춰졌다.
- Tags·Contexts 구조화: 도메인·HTTP 상태·서버 에러 코드를 대시보드에서 즉시 필터링·집계 가능
- 소스맵 파이프라인: 스택 트레이스가 실제 소스 위치로 전환 (vendors.js:1:219510 → src/features/auth/api/useSignupMutation.ts:24)
- Session Replay: 에러 발생 당시 사용자 흐름을 영상으로 재생 가능
에러를 인지하고 원인을 찾는 전 과정이 갖춰진 모니터링 환경이 됐다. 앞으로 데이터가 쌓이면 도메인별·에러 코드별 필터링 기준을 수립해 나갈 계획이다.
'문제 해결 & 구현 기록' 카테고리의 다른 글
| 이미지 최적화 파이프라인 구축기 (2) - S3 이벤트 트리거 기반 Lambda 구현 (0) | 2026.03.26 |
|---|---|
| 이미지 최적화 파이프라인 구축기 (1) - 문제 진단과 방법 선택 (0) | 2026.03.23 |
| webpack 트리셰이킹이 안 된 진짜 이유: sideEffects 동명 옵션 혼동 (2) | 2026.03.06 |
| 이미지 로딩 속도 개선기 (webp, squoosh) (1) | 2025.12.24 |
| firebase 디지털 지문 SHA 이미 다른 프로젝트에 등록된 키 문제 해결 방법 (2) | 2024.12.10 |