우리 서비스의 Sentry는
개발서버 @@@@-dev.inplace.my와 운영서버 inplace.my의 에러를 모두 수집하고 있었다.
불필요하게 개발서버의 에러가 수집되니 실제 사용자의 피드백을 즉각 파악하기 어려웠고, 분석 효율성이 떨어지는 문제가 있었다.

처음엔 import.meta.env.PROD가 운영서버 inplace.my을 의미하는 줄 알고 아래와 같이 코드를 작성했다.
// Sentry 초기화
if (import.meta.env.PROD) {
const initialized = await initSentryWithRetry();
setSentryInitialized(initialized);
}
// 에러 캡처
if (import.meta.env.PROD) {
await captureErrorWithRetry(...);
}
하지만 Vite의 PROD는 빌드 명령어(vite build)로 생성된 앱인지 여부만 판단한다. 즉, vite build로 빌드된 앱은 개발/운영 서버 모두 import.meta.env.PROD = true로 설정된다.
실제 도메인과 무관한 환경 변수였던 것이 문제의 핵심이다.
공식문서의 denyUrls를 사용했다. 예외(exception) 형태의 에러 수집을 막는 옵션이다.
Sentry.init({
denyUrls: [/dev/],
...
})
해결이 안됐다. 정규식 문제인가 싶어 [/dev\.inplace\.my/]처럼 구체적으로도 작성했지만 여전히 해결되지 않았다.
현재 우리 서비스는 CI/CD 파이프라인에서 dev/main 브랜치 푸시에 따라 다른 빌드 명령어(build:dev/build)가 실행된다.
기존 서비스의 API 엔드포인트를 관리하던 커스텀 Config 시스템을 재활용했다.
기존 API config 로직
type Environment = 'production' | 'development';
interface Config {
baseURL: string;
environment: Environment;
}
const DEFAULT_CONFIGS: Record<Environment, Config> = {
production: {
baseURL: 'https://api.inplace.my',
environment: 'production',
},
development: {
baseURL: 'https://@@@@-dev.inplace.my',
environment: 'development',
},
} as const;
const getCurrentConfig = (): Config => {
// Jest 테스트 환경 체크
if (process.env.NODE_ENV === 'test') {
return DEFAULT_CONFIGS.development;
}
// Vite 환경용 (빌드 시점에 교체됨)
const currentMode = import.meta.env.MODE as Environment;
return DEFAULT_CONFIGS[currentMode];
};
export default getCurrentConfig;
Sentry 초기화 로직 적용
const currentConfig = getCurrentConfig();
if (currentConfig.environment === 'production') {
const initialized = await initSentryWithRetry();
setSentryInitialized(initialized);
}
이제 environment 속성이 빌드 모드(--mode)에 따라 결정되므로,
CI/CD 파이프라인과 연동된다.
설정 후 CI/CD 과정에서 에러가 발생했다.
error: API request failed
sentry reported an error: bad gateway (http status: 502)

원인은 개발 서버 빌드 시 SourceMap이 생성되지 않아 Sentry 업로드가 실패했기 때문.
개발 모드에 SourceMap 생성을 비활성화했었다...
// vite.config.ts
sourcemap: mode === 'production' // 개발 모드에선 SourceMap 미생성
SourceMap이란?
압축되거나 번들링된 결과물과 원본 소스 파일 간의 관계를 기록한 파일이다.
브라우저 개발자 도구나 오류 추적 시스템(Sentry 등)이 배포 파일의 특정 부분이 원본 소스의 어디에 해당하는지를 정확히 알 수 있다.
CI/CD 과정에서 Sentry에 SourceMap을 전달하는 것 자체를 막기로 했다.
if: github.ref == 'refs/heads/main'
를 추가해서, main에 push될 때만 (즉, inplace.my가 배포될때만) SourceMap을 전달!
최종적으로 이렇게 동작한다.

필요하다.
if: github.ref == 'refs/heads/main'는 Sentry 서버에 SourceMap을 보내는 작업이 'main' 일 때만 하는 것이고, 실제 Sentry를 활성화하는 건 빌드 시점에 달려있다.
즉, CD에서 SourceMap 업로드만 막으면 운영 서버에만 SourceMap이 올라가는 것이지, 개발 서버에서 Sentry가 아예 비활성화되는 것은 아니다.
Yes. 기본적인 오류 추적이 가능하지만, 스택 트레이스가 난독화된 형태로 표시되어 구체적인 오류 위치를 파악하기 어렵다.
1) 스택 트레이스 가독성 향상
// 원본 코드 (TypeScript)
function fetchData() { ... }
// 번들링된 코드 (난독화)
function a(){...}
2) 에러를 자동으로 그룹화
3) 변수명/주석 컨텍스트 활용
이건 아직 잘 모르겠다.
공식문서에는 스택 트레이스의 최상단 파일 URL에 대해 정규식 매칭을 한다고 되어있다.
즉, @@@@-dev.inplace.my에서 오류가 발생하더라도, CDN, 프록시, 번들러 등에 의해 실제 URL이 변환되면 매칭되지 않을 수 있다. 이것 때문이 가장 유력하다... 내가 뜯어 봤을 땐, 우리서비스 최상위 스택이 @@@@-dev.inplace.my가 맞던데. 아무튼 denyUrls는 안쓰는걸로...!
실제로 공식 문서에서는 denyUrls로 원하는 필터링이 안 될 경우, beforeSend 콜백에서 직접 event.exception.values.stacktrace.frames를 확인하는 방법을 권장한다.
아니면 beforeSend를 활용해서
Sentry.init({
beforeSend(event) {
if (window.location.hostname.includes('dev')) return null;
return event;
}
});
이런식으로 해도 되려나?
환경변수와 도메인은 다르다.
빌드 도구의 환경 변수는 도메인과 무관하므로 런타임 도메인 체크는 필수
개발 서버에서 Sentry를 아예 동작하지 않게 하려면 코드 내에서 환경 분기( ex. import.meta.env.MODE 등)로 Sentry를 제어해야 한다.
보안을 위한 SourceMap 관리
Sentry에 업로드 후 배포 서버에서 SourceMap 삭제를 하는것이 안전하다.