본문으로 건너뛰기

"sentry" 태그로 연결된 1개 게시물개의 게시물이 있습니다.

모든 태그 보기

디버깅 시간 80% 단축, Sentry로 실시간 대응하기

· 약 5분
김현재
개발자

들어가며

1인으로 AI 에이전트를 사용해 서비스를 개발/운영하다 보면 챙겨야 할 일이 생각보다 훨씬 많습니다. 기획, 디자인, 개발, 배포, 마케팅까지 혼자 다 해야 하다 보니 어딘가 한 군데는 반드시 구멍이 생깁니다.

그 중에서도 런타임 에러 모니터링은 가장 놓치기 쉬운 부분 중 하나입니다. 개발 환경에서는 멀쩡하게 동작하던 서비스가, 실제 사용자가 쓰는 프로덕션 환경에서 발생하고 있어도 개발자가 직접 확인하기는 어렵습니다.

저도 서비스를 배포하고 나서 한동안은 에러가 없다고 생각했습니다. 그런데 Sentry를 달고 나서야 "이런 에러가 이렇게 많이 터지고 있었나?" 싶었습니다. 이번 포스팅에서는 Sentry가 무엇인지, 어떻게 설정하는지, 그리고 실제로 어떤 에러들을 잡았는지 공유해보려 합니다.

Sentry란?

Sentry실시간 에러 추적(Error Tracking) 플랫폼입니다. 프론트엔드, 백엔드, 모바일 등 다양한 환경을 지원하며, 에러가 발생했을 때 어떤 유저가, 어떤 환경에서, 어떤 코드 경로를 타다가 에러를 만났는지 상세하게 알려줍니다.

무료 플랜으로도 월 5,000건의 에러 이벤트를 사용할 수 있어서, 초기 서비스를 운영하는 개발자에게 충분합니다.

왜 필요한가

로컬에서 console.error로 에러를 확인하는 것과 실제 프로덕션 환경에서 에러를 추적하는 것은 많은 차이가 있습니다.

이번엔 우리가 서비스의 사용자가 되어본다고 가정해볼까요? 만약 처음 방문한 낯선 플랫폼에서 버튼을 눌렀다가 에러를 마주친다면, 여러분은 친절하게 문의나 신고 기능을 통해 그 사실을 개발자에게 알릴까요? 저 역시 정말 애정이 깊은 서비스가 아닌 이상, 대체로 피드백을 남기지 않고 그냥 넘어가곤 했습니다.

결국 대부분의 사용자는 에러가 발생해도 별다른 신고 없이 조용히 서비스를 떠납니다. Sentry는 이런 조용한 이탈을 막기 위한 가장 중요한 역할을 수행합니다.

Next.js에 Sentry 설정하기

패키지 설치

npx @sentry/wizard@latest -i nextjs

Sentry에서 공식으로 제공하는 wizard를 사용하면 대부분의 설정을 자동으로 해줍니다. 실행하면 다음과 같은 질문들이 나옵니다.

? Are you using Sentry SaaS or self-hosted Sentry? › Sentry SaaS (sentry.io)
? Do you already have a Sentry account? › Yes
? Select your Sentry project › my-project
? Do you want to route Sentry requests through your Next.js server? › Yes (recommended)
? Do you want to enable React component annotations? › Yes

wizard가 완료되면 아래 파일들이 자동으로 생성됩니다.

sentry.client.config.ts   # 클라이언트 사이드 설정
sentry.server.config.ts # 서버 사이드 설정
sentry.edge.config.ts # Edge runtime 설정
instrumentation.ts # Next.js instrumentation hook

생성된 설정 파일 확인

// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs';

Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,

// 에러 발생 시 Sentry로 전송되는 비율 (0.0 ~ 1.0)
// 프로덕션에서는 1.0, 트래픽이 많다면 0.1~0.5로 조정
tracesSampleRate: 1,

// 세션 리플레이 - 에러 발생 전후 사용자 화면 녹화
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,

integrations: [Sentry.replayIntegration()],

// 개발 환경에서는 비활성화
enabled: process.env.NODE_ENV === 'production',
});

무료 버전을 사용하신다면 enabled: process.env.NODE_ENV === 'production' 설정을 꼭 추가해야 합니다. 개발 중에도 Sentry로 이벤트가 전송되면 무료 한도를 금방 소진할 수 있습니다.

환경 변수 설정

.env.local에 DSN을 추가합니다. DSN은 Sentry 프로젝트 설정 > Client Keys에서 확인할 수 있습니다.

# .env.local
NEXT_PUBLIC_SENTRY_DSN=https://xxxxxxxx@o0.ingest.sentry.io/xxxxxxxx

# Source Map 업로드용
SENTRY_AUTH_TOKEN=sntrys_xxxxxxxxxxxx
SENTRY_ORG=your-org
SENTRY_PROJECT=your-project

next.config.ts 설정

wizard가 자동으로 withSentryConfig를 추가해줍니다. 직접 설정한다면 아래를 참고하세요.

// next.config.ts
import { withSentryConfig } from '@sentry/nextjs';
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
// 기존 설정
};

export default withSentryConfig(nextConfig, {
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,

// Source Map을 Sentry에 업로드하고 빌드 결과물에서는 숨김
// 이 설정이 없으면 에러 스택 트레이스가 minify된 코드로 보임
hideSourceMaps: true,

// 빌드 시 Sentry CLI 출력 숨기기
silent: !process.env.CI,
});

Source Map 설정은 특히 중요합니다. 이 설정 없이는 Sentry에서 에러 위치가 main-abc123.js:1:48392 같은 난독화된 코드로 표시됩니다. 설정이 완료되면 Sentry에서 아래와 같이 확인할 수 있습니다.

스택 트레이스

수동으로 에러 캡처하기

자동으로 잡히지 않는 에러는 직접 캡처할 수 있습니다.

import * as Sentry from '@sentry/nextjs';

// 예외 캡처
try {
await riskyOperation();
} catch (error) {
Sentry.captureException(error, {
extra: {
userId: user.id,
action: 'riskyOperation',
},
});
}

// 메시지 캡처 (에러는 아니지만 추적이 필요한 경우)
Sentry.captureMessage('결제 프로세스 중 예상치 못한 분기 발생', {
level: 'warning',
extra: { orderId: order.id },
});

실제로 잡은 에러들

설정을 마치고 나서 제가 실제로 마주한 에러들입니다.

서비스 운영 중 발견되는 에러 케이스는 계속해서 추가·업데이트할 예정입니다.

케이스 1: 프로덕션에서 내부 API가 401을 반환하는 에러

Error: Failed to fetch company info
at getCompanyInfo (entities/company/api/company-info/client.ts:25)

단순한 API 호출 실패처럼 보였지만, Sentry breadcrumb를 열어보니 실제 원인이 달랐습니다.

GET https://gongsi-ppt0t95cd-napoldevelopers-projects.vercel.app/api/companies/00462121
→ 401 Unauthorized

사용자는 www.gongsi-gongsi.kr에 접속했는데, SSR 중 내부 API 호출이 Vercel 프리뷰 배포 URL로 향하고 있었습니다. 이 프리뷰 URL은 Vercel 인증이 걸려있어 401이 반환된 것입니다.

원인은 getBaseUrl()process.env.VERCEL_URL을 그대로 사용하는 패턴에 있었습니다.

// ❌ 문제 있는 코드
function getBaseUrl() {
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; // 배포별 고유 URL이 들어옴
}

VERCEL_URL은 Vercel이 자동으로 설정하는 배포별 고유 URL입니다 (예: my-app-abc123.vercel.app). 커스텀 도메인이 아니기 때문에 Vercel 인증을 통과하지 못했습니다.

수정은 간단합니다. NEXT_PUBLIC_APP_URL 환경변수를 직접 설정하고 우선 사용하도록 바꿉니다.

// ✅ 수정된 코드
function getBaseUrl() {
if (typeof window !== 'undefined') return window.location.origin;
if (process.env.NEXT_PUBLIC_APP_URL) return process.env.NEXT_PUBLIC_APP_URL;
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
return 'http://localhost:3000';
}

그리고 Vercel 환경변수에 추가합니다.

NEXT_PUBLIC_APP_URL=https://www.gongsi-gongsi.kr

보너스: 이 에러를 처음 발견한 게 실제 유저가 아니라 ChatGPT 크롤러 봇이었습니다. Sentry를 달지 않았다면 얼마나 많은 유저가 에러를 보고 그냥 나갔을지 알 수 없는 상황이었습니다.

openai 공식 크롤러 봇

Sentry를 더 잘 활용하는 팁

알림 설정

대시보드를 매일 확인할 수 없으니, Slack 알림을 연동해두는 것이 좋습니다.

Sentry > Settings > Integrations > Slack에서 연동하면, 새로운 에러가 처음 발생하거나 특정 임계값을 넘을 때 Slack으로 알림을 받을 수 있습니다.

[새 이슈] TypeError: Cannot read properties of undefined
프로젝트: my-service | 환경: production
첫 발생: 방금 전 | 영향받은 유저: 1명
→ Sentry에서 확인하기

민감 정보 필터링

유저의 비밀번호나 토큰 같은 민감 정보가 에러 페이로드에 담기지 않도록 필터링합니다.

Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
beforeSend(event) {
// 요청 헤더에서 Authorization 제거
if (event.request?.headers) {
delete event.request.headers['Authorization'];
}
return event;
},
});

마무리

Sentry 설정은 생각보다 간단하고, 세팅에 드는 시간 대비 얻는 가치가 크다고 느꼈습니다. 생각보다 많은 런타임 에러가 발생하고 있었고, 개발 환경에서는 이 모든 에러를 발견하지 못했습니다.

참고