Skip to content

[FEAT] Sentry 알림이 대응 판단으로 이어지도록 이벤트 맥락 개선#469

Open
useon wants to merge 3 commits into
developfrom
feat/#468
Open

[FEAT] Sentry 알림이 대응 판단으로 이어지도록 이벤트 맥락 개선#469
useon wants to merge 3 commits into
developfrom
feat/#468

Conversation

@useon

@useon useon commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

🚩 연관 이슈

closed #468

📝 작업 내용

기존에는 이렇게 알림이 와서 알림만 보고 어떤 에러가 어떻게 발생한다는 것을 빠르게 파악하기 힘들었습니다.
image

이번 작업은 Sentry 알림을 단순히 “에러가 발생했다”는 신호로 두지 않고, 알림을 받았을 때 어떤 사용자 흐름에서 문제가 생겼고 지금 확인해야 하는지 판단할 수 있도록 이벤트 맥락을 정리한 작업입니다.

기존 Sentry 설정은 에러 수집, Replay, Source Map을 통해 디버깅 가능한 상태를 만드는 데에는 효과적이었습니다. 다만 실제로 알림을 받아보면, 알림만 보고 어떤 기능에서 발생한 문제인지, 사용자 핵심 흐름에 영향을 주는지, 바로 대응해야 하는지 판단하기 어려웠습니다.

그래서 이번 PR에서는 알림 수신자의 다음 행동을 돕는 방향으로 Sentry 이벤트의 제목, 태그, level, grouping 기준을 개선했습니다.

1. Sentry 알림 판단 기준 유틸 분리

  • Sentry 이벤트에 필요한 판단 기준을 src/util/sentry.ts로 분리했습니다.
  • endpoint 정규화, pathname 기반 feature 분류, API error level 결정, 수집 제외 기준을 순수 함수로 정리했습니다.
  • 알림 정책이 axiosInstance 내부에 섞이지 않도록 분리해서, 이후 라우트나 핵심 기능 기준이 바뀌어도 수정 범위를 좁힐 수 있게 했습니다.

이번 작업은 “어떤 에러를 어떤 맥락의 알림으로 볼 것인가”를 테스트 가능한 기준으로 만들기 위한 목적입니다.

2. 알림에서 사용자 영향 흐름이 보이도록 feature tag 추가

  • pathname을 기준으로 timer, vote, live-share, debate-end 등 사용자 흐름 단위의 feature를 분류했습니다.
  • API 에러와 렌더링 에러에 feature tag를 붙여, Sentry 알림만 봐도 어떤 화면/기능에서 문제가 발생했는지 파악할 수 있도록 했습니다.

기존에는 endpoint나 status는 확인할 수 있었지만, 이 에러가 실제로 어떤 사용자 행동에 영향을 주는지 알림만 보고 판단하기 어려웠습니다. 이번 작업에서는 알림을 여는 순간 “타이머 진행 중 문제인지”, “투표 흐름 문제인지”, “실시간 공유 문제인지”를 먼저 볼 수 있게 만드는 데 집중했습니다.

3. API 에러 이슈 제목과 grouping 기준 개선

  • API 에러를 Sentry에 보낼 때 이슈 제목에 status, method, endpoint가 드러나도록 정리했습니다.
    • 예: [network-error] POST /api/live/:id
  • endpoint의 숫자 id, uuid, query string은 정규화해서 같은 API 장애가 하나의 이슈로 묶이도록 했습니다.
  • fingerprint도 api-error + status + method + endpoint 기준으로 정리했습니다.

사용자별 id 값 때문에 같은 장애가 여러 이슈로 흩어지면 알림 피로도가 커지고 원인 파악도 늦어진다고 봤습니다. 그래서 “같은 문제는 같은 알림 이슈로 묶이게 하는 것”을 기준으로 정리했습니다.

4. 대응 가치 기준으로 error level과 필터링 정리

  • 핵심 흐름의 서버성 에러는 더 높은 우선순위로 볼 수 있도록 level 기준을 정리했습니다.
  • 사용자 입력이나 상태 충돌처럼 예상 가능한 에러는 warning으로 낮췄습니다.
  • 토큰 재발급으로 복구 가능한 401, 사용자 오프라인 상태의 네트워크 에러처럼 바로 대응하기 어려운 이벤트는 수집에서 제외했습니다.

여기서 중요한 기준은 에러를 덜 보는 것이 아니라, 실제로 확인해야 할 알림의 신뢰도를 높이는 것이었습니다.

5. 렌더링 에러에도 feature 맥락 추가

  • ErrorBoundary에서 잡힌 렌더링 에러는 화면이 깨진 상태로 보고 fatal로 분류했습니다.
  • 렌더링 에러에도 pathname 기반 feature tag를 추가했습니다.
  • render-error + feature 기준 fingerprint를 설정해 같은 기능의 렌더링 장애가 묶이도록 했습니다.
  • API 인터셉터에서 이미 캡처한 에러는 중복 전송되지 않도록 기준을 통일했습니다.

✅ 검증

  • src/util/sentry.test.ts
    • 8개 테스트 통과
  • tsc --build --noEmit
    • 통과
  • 수정 파일 대상 ESLint
    • 통과

🗣️ 리뷰 요구사항 (선택)

이번 PR에서는 “Sentry에 더 많이 남기기”보다 “알림을 받았을 때 바로 판단할 수 있게 만들기”에 초점을 뒀습니다.

feature 분류 기준이나 error level 기준이 실제 운영 관점에서 어색한 부분이 있다면 편하게 의견 주세요 ~ !!

Summary by CodeRabbit

  • 개선 사항

    • API 및 화면 오류 보고를 표준화해 오류 유형, 기능, 요청 정보가 일관되게 기록됩니다.
    • 중복 오류 보고를 방지하고, 복구 가능한 인증 오류와 오프라인 네트워크 오류는 불필요하게 수집하지 않습니다.
    • 오류 심각도가 상황에 따라 구분되어 문제 파악이 쉬워졌습니다.
  • 테스트

    • 오류 분류, 메타데이터 생성, 중복 및 오프라인 오류 제외 동작에 대한 검증을 추가했습니다.

@useon
useon requested a review from i-meant-to-be July 22, 2026 13:49
@useon useon self-assigned this Jul 22, 2026
@useon useon added the feat 기능 개발 label Jul 22, 2026
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@useon
useon deployed to PREVIEW_ENV July 22, 2026 13:49 — with GitHub Actions Active
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Sentry 오류 처리를 공통 유틸리티로 통합하고, API 경로·기능·상태·네트워크 상태를 기반으로 이벤트 메타데이터와 심각도를 산출하도록 변경했습니다. Axios, APIError, ErrorBoundary의 중복 캡처 방지 흐름도 공통 플래그 헬퍼를 사용하도록 조정했습니다.

Changes

Sentry 오류 맥락 처리

Layer / File(s) Summary
Sentry 오류 유틸리티
src/util/sentry.ts, src/util/sentry.test.ts
엔드포인트 정규화, 기능 분류, 심각도 계산, API 오류 제외 조건, 메타데이터·Sentry 오류 생성, 캡처 플래그 처리를 추가하고 테스트했습니다.
Axios API 오류 캡처 흐름
src/apis/axiosInstance.ts
Axios 오류 캡처가 공통 유틸리티를 사용해 스킵 여부와 메타데이터를 결정하고, Sentry scope에 태그·컨텍스트·fingerprint를 설정하도록 변경했습니다.
APIError 및 ErrorBoundary 연동
src/apis/primitives.ts, src/components/ErrorBoundary/ErrorBoundary.tsx
APIError와 ErrorBoundary가 공통 캡처 플래그를 사용하며, 렌더링 오류에 기능 태그와 렌더링 컨텍스트를 설정하도록 변경했습니다.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AxiosRequest
  participant SentryUtilities
  participant Sentry
  participant ErrorBoundary
  AxiosRequest->>SentryUtilities: AxiosError 분석
  SentryUtilities-->>AxiosRequest: 스킵 여부·메타데이터·Sentry Error
  AxiosRequest->>Sentry: API 오류 캡처 및 플래그 표시
  AxiosRequest->>ErrorBoundary: APIError 전달
  ErrorBoundary->>Sentry: 미캡처 렌더링 오류 전송
Loading

Possibly related PRs

Suggested reviewers: i-meant-to-be

Poem

토끼가 당근처럼 오류를 모아
경로와 기능을 곱게 정리해요.
중복 전송은 살짝 건너뛰고,
중요한 신호엔 귀를 쫑긋,
Sentry 숲에 맥락을 심었답니다.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed API 및 렌더링 에러에 대응 판단용 맥락을 추가해 Sentry 알림 우선순위 판단 개선이라는 요구를 충족합니다.
Out of Scope Changes check ✅ Passed 변경은 Sentry 유틸 추가와 관련 호출부 리팩터링에 집중되어 있으며, 명백한 무관한 범위는 보이지 않습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Sentry 알림에 대응 판단용 맥락을 추가하는 이번 변경의 핵심을 잘 요약합니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#468

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

🚀 Preview 배포 완료!

환경 URL
Preview 열기
API Dev 환경

PR이 닫히면 자동으로 정리됩니다.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/util/sentry.ts (2)

17-17: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

상수 네이밍 컨벤션 위반.

criticalFeatures는 새로 추가된 모듈 레벨 상수 배열인데 camelCase로 작성되어 있습니다. As per coding guidelines, **/*.{ts,tsx} 파일은 "UPPER_SNAKE_CASE for constants"를 요구합니다.

♻️ 제안
-const criticalFeatures: DebateFeature[] = ['timer', 'vote', 'live-share'];
+const CRITICAL_FEATURES: DebateFeature[] = ['timer', 'vote', 'live-share'];

(사용처인 resolveApiErrorLevel의 참조도 함께 변경 필요)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/util/sentry.ts` at line 17, Rename the module-level constant
criticalFeatures to the required UPPER_SNAKE_CASE convention, and update its
reference in resolveApiErrorLevel accordingly while preserving the existing
array contents and behavior.

Source: Coding guidelines


147-157: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

sentryError에 설정한 __sentry_captured__ 플래그는 사용되지 않는 죽은 코드입니다.

154번 줄에서 새로 생성한 sentryError에 플래그를 설정하지만, 이 객체는 Sentry.captureException에만 전달되고 버려집니다. 실제 중복 캡처 방지는 axiosInstance.ts에서 원본 error에 대해 별도로 호출하는 markSentryCaptured(error)가 담당합니다. 이 줄은 오해를 유발할 수 있으니 제거를 고려해 주세요.

♻️ 제안
   const sentryError = new Error(error.message);
   sentryError.name = `[${metadata.statusLabel}] ${metadata.method} ${metadata.endpoint}`;
   sentryError.stack = error.stack;
-  (sentryError as SentryCapturedError).__sentry_captured__ = true;
 
   return sentryError;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/util/sentry.ts` around lines 147 - 157, Remove the __sentry_captured__
assignment from createSentryApiError; duplicate-capture prevention is handled by
markSentryCaptured(error) on the original AxiosError, so leave the constructed
sentryError without this unused flag.
src/util/sentry.test.ts (1)

2-9: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

markSentryCaptured/isSentryCaptured에 대한 직접 테스트 추가를 권장합니다.

이 두 함수는 axiosInstance, primitives, ErrorBoundary 세 곳이 공유하는 중복 캡처 방지 계약의 핵심입니다. 계약이 깨지면 조용히 중복 알림 또는 알림 누락이 발생할 수 있으므로, import 목록에 추가해 간단한 단위 테스트를 붙이면 좋겠습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/util/sentry.test.ts` around lines 2 - 9, sent​​ry 유틸리티의
markSentryCaptured와 isSentryCaptured를 직접 import하고, 동일한 오류 객체에 캡처 표시를 설정한 뒤
isSentryCaptured가 표시 전후에 올바른 값을 반환하는 단위 테스트를 추가하세요. 서로 다른 오류 객체는 독립적으로 처리되는지와 기존
중복 캡처 방지 계약을 검증하세요.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/apis/axiosInstance.ts`:
- Around line 71-83: Prevent raw user-controlled data from reaching Sentry by
introducing a shared masking or allowlist utility. In
src/apis/axiosInstance.ts#L71-L83, apply it to request params and response data
before setContext; in src/components/ErrorBoundary/ErrorBoundary.tsx#L49-L53,
sanitize window.location.search by masking or removing sensitive parameters such
as token, code, and state before setting the render context.

In `@src/util/sentry.ts`:
- Around line 48-96: Update resolveFeatureFromPathname so every startsWith route
check enforces a path-segment boundary, matching removeLanguagePrefix behavior:
accept the route only when the pathname ends at that prefix or the next
character is /. Apply this to /composition, /overview, /vote, /table/customize,
/oauth, /share, and /live while preserving the existing route order and feature
mappings.

---

Nitpick comments:
In `@src/util/sentry.test.ts`:
- Around line 2-9: sent​​ry 유틸리티의 markSentryCaptured와 isSentryCaptured를 직접
import하고, 동일한 오류 객체에 캡처 표시를 설정한 뒤 isSentryCaptured가 표시 전후에 올바른 값을 반환하는 단위 테스트를
추가하세요. 서로 다른 오류 객체는 독립적으로 처리되는지와 기존 중복 캡처 방지 계약을 검증하세요.

In `@src/util/sentry.ts`:
- Line 17: Rename the module-level constant criticalFeatures to the required
UPPER_SNAKE_CASE convention, and update its reference in resolveApiErrorLevel
accordingly while preserving the existing array contents and behavior.
- Around line 147-157: Remove the __sentry_captured__ assignment from
createSentryApiError; duplicate-capture prevention is handled by
markSentryCaptured(error) on the original AxiosError, so leave the constructed
sentryError without this unused flag.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 90ced027-62ba-45c6-b28c-234f5bfa68e1

📥 Commits

Reviewing files that changed from the base of the PR and between 2bb54aa and 7d9d167.

📒 Files selected for processing (5)
  • src/apis/axiosInstance.ts
  • src/apis/primitives.ts
  • src/components/ErrorBoundary/ErrorBoundary.tsx
  • src/util/sentry.test.ts
  • src/util/sentry.ts

Comment thread src/apis/axiosInstance.ts
Comment on lines +71 to +83
scope.setContext('request', {
pathname: metadata.pathname,
search: window.location.search,
url: config?.url,
method: config?.method,
params: config?.params,
timeout: config?.timeout ?? requestTimeoutMs,
errorCode: code,
},
fingerprint: [
url: error.config?.url,
method: error.config?.method,
params: error.config?.params,
timeout: error.config?.timeout ?? requestTimeoutMs,
errorCode: error.code,
});
scope.setContext('response', {
status: metadata.status,
data: error.response?.data,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Sentry 컨텍스트에 원본 요청/쿼리 데이터를 가공 없이 전송하고 있어 개인정보 유출 위험이 있습니다.

두 위치 모두 사용자 입력이 포함될 수 있는 원본 데이터(쿼리스트링, 요청 파라미터, 응답 바디)를 그대로 Sentry(제3자 서비스)로 전송합니다. 여기에는 이메일, 초대 코드, OAuth code/state, 서버 에러 메시지에 담긴 사용자 식별정보 등 민감정보가 포함될 수 있습니다. 두 사이트는 동일한 근본 원인(원본 데이터를 그대로 전달)을 공유하므로 공통 마스킹/allowlist 유틸을 만들어 적용하는 것을 권장합니다.

  • src/apis/axiosInstance.ts#L71-L83: setContext('request', ...)paramssetContext('response', { data: error.response?.data })data를 민감 필드 마스킹 또는 allowlist 기반 축약 후 전송하도록 수정.
  • src/components/ErrorBoundary/ErrorBoundary.tsx#L49-L53: setContext('render', ...)search: window.location.search를 알려진 민감 쿼리 파라미터(토큰, code, state 등)를 제거/마스킹한 값으로 대체.
📍 Affects 2 files
  • src/apis/axiosInstance.ts#L71-L83 (this comment)
  • src/components/ErrorBoundary/ErrorBoundary.tsx#L49-L53
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/apis/axiosInstance.ts` around lines 71 - 83, Prevent raw user-controlled
data from reaching Sentry by introducing a shared masking or allowlist utility.
In src/apis/axiosInstance.ts#L71-L83, apply it to request params and response
data before setContext; in
src/components/ErrorBoundary/ErrorBoundary.tsx#L49-L53, sanitize
window.location.search by masking or removing sensitive parameters such as
token, code, and state before setting the render context.

Comment thread src/util/sentry.ts
Comment on lines +48 to +96
export function resolveFeatureFromPathname(pathname: string): DebateFeature {
const path = removeLanguagePrefix(pathname);

if (path === '/home') {
return 'landing';
}

if (path === '/') {
return 'table-list';
}

if (path.startsWith('/composition')) {
return 'table-composition';
}

if (path.startsWith('/overview')) {
return 'table-overview';
}

if (path.includes('/end/vote') || path.startsWith('/vote')) {
return 'vote';
}

if (path.includes('/end/feedback')) {
return 'timer';
}

if (path.includes('/end')) {
return 'debate-end';
}

if (path.startsWith('/table/customize')) {
return 'timer';
}

if (path.startsWith('/oauth')) {
return 'auth';
}

if (path.startsWith('/share')) {
return 'share';
}

if (path.startsWith('/live')) {
return 'live-share';
}

return 'unknown';
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

startsWith 접두사 매칭에 경로 구분자 경계 검사가 빠져 있습니다.

/vote, /live, /share, /oauth, /composition, /overview, /table/customize 등의 검사가 모두 단순 startsWith로만 이뤄져, 예를 들어 /livechat, /sharepoint, /overviewer처럼 우연히 같은 접두사로 시작하는 무관한 경로도 잘못된 feature로 분류될 수 있습니다. 같은 파일의 removeLanguagePrefix(?=\/|$) lookahead로 세그먼트 경계를 정확히 검사하는데, 이 함수에는 동일한 안전장치가 없습니다. 이 유틸의 목적이 알림 우선순위 판단(예: vote/live-share는 fatal로 승격)이기 때문에, 오분류는 실제 대응 우선순위 판단을 왜곡할 수 있습니다.

🐛 제안 수정
+function matchesSegment(path: string, prefix: string) {
+  return path === prefix || path.startsWith(`${prefix}/`);
+}
+
 export function resolveFeatureFromPathname(pathname: string): DebateFeature {
   ...
-  if (path.startsWith('/composition')) {
+  if (matchesSegment(path, '/composition')) {
     return 'table-composition';
   }
   ... (다른 startsWith 검사에도 동일하게 적용)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/util/sentry.ts` around lines 48 - 96, Update resolveFeatureFromPathname
so every startsWith route check enforces a path-segment boundary, matching
removeLanguagePrefix behavior: accept the route only when the pathname ends at
that prefix or the next character is /. Apply this to /composition, /overview,
/vote, /table/customize, /oauth, /share, and /live while preserving the existing
route order and feature mappings.

@i-meant-to-be i-meant-to-be left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 매번 센트리 오류 오는 거 잘 안 봤는데 이렇게 개선되면 확실히 보기 편하겠네요... 조은 PR 감사합니다!

딱히 문제 될 내용은 없어 보여 승인 남깁니다 깔끔한 작업 최고 👍👍👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] Sentry 알림이 대응 판단으로 이어지도록 이벤트 맥락 개선

2 participants