@howmuchhome-web/error
에러 클래스, 에러 바운더리, Sentry 전송 파이프라인을 제공하는 패키지입니다.
실제 로그 전송은 @howmuchhome-web/logger를 경유하므로, 로깅 개요는 로깅 가이드를 함께 참고하세요.
진입점
| import 경로 | 대상 | 노출 |
|---|---|---|
@howmuchhome-web/error | web (Next.js) | 모든 심볼 |
@howmuchhome-web/error/native | native (Expo) | RN-safe 한 서브셋만 |
native 진입점은 web 전용 transport(@sentry/nextjs, logger 의 Datadog 브라우저 SDK)를 끌어오지 않기 위해 노출을 좁혔습니다. @howmuchhome-web/ui/native 와 같은 패턴입니다.
/native 배럴이 내보내는 것은 다음뿐입니다.
ErrorBoundary—ErrorBoundary.native.tsx구현 (web 과 별도 파일)reportErrorclasses(HowmuchhomeError)types(ErrorLevel)
ErrorFallback, withErrorHandler, sentryBaseConfig, sentryBeforeSend 등 web 전용 심볼은 native 배럴에 없습니다.
native 에서도
reportError는 web 과 동일하게Logger.captureError를 호출합니다. metro resolver 가 logger 내부의@sentry/nextjs를@sentry/react-native로 redirect 하므로, native 에서는 RN Sentry 로 전송됩니다. 자세한 내용은 native 로깅을 참고하세요.
HowmuchhomeError
프로젝트 전역에서 쓰는 커스텀 에러 클래스입니다. Sentry 로 전송될 때 사용할 메타데이터(레벨, transaction, fingerprint, tags, extra)를 함께 담습니다.
import {HowmuchhomeError, ErrorLevel} from '@howmuchhome-web/error';
throw new HowmuchhomeError('결제 검증 실패', {
level: ErrorLevel.WARNING,
tags: {feature: 'payment'},
extra: {orderId},
});시그니처
new HowmuchhomeError(
input: string | Error,
props?: {
cause?: unknown;
level?: ErrorLevel; // 기본값: ErrorLevel.ERROR
transactionName?: string;
fingerprint?: string[];
tags?: Record<string, string>;
extra?: Record<string, unknown>;
},
)input이Error이면 그message를 재사용하고,cause를 명시하지 않으면 원본 에러를cause로 연결합니다.- 생성 시
setErrorLevel(level)이 호출됩니다.
프로퍼티
| 프로퍼티 | 타입 | 설명 |
|---|---|---|
level | ErrorLevel | Sentry severity 로 매핑됨 |
transactionName | string? | Sentry transaction. 전송 시 normalizeErrorPath 로 정규화됨 |
fingerprint | string[]? | Sentry 그룹핑 지문 |
tags | Record<string, string>? | Sentry tags 에 병합됨 |
extra | Record<string, unknown>? | Sentry extra 에 병합됨 |
setErrorLevel
error.setErrorLevel(level: ErrorLevel): thislevel 을 갱신합니다. ErrorLevel.IGNORE 이면 에러 객체에 silent 마커를 설정하고, 그 외 레벨이면 마커를 제거합니다. 체이닝을 위해 자신을 반환합니다.
ErrorLevel
export const enum ErrorLevel {
FATAL = 'fatal',
ERROR = 'error',
WARNING = 'warning',
INFO = 'info',
IGNORE = 'ignore',
}const enum 이므로 컴파일 시 문자열 리터럴로 인라인됩니다. native(Babel/metro)에서도 문제없이 사용됩니다.
FATAL/ERROR/WARNING/INFO— Sentry severity 로 그대로 매핑됩니다.IGNORE— Sentry 로 전송하지 않습니다(silent 처리).
reportError
에러를 Sentry 로 전송하는 함수입니다. ErrorBoundary 와 ErrorFallback 이 내부적으로 이 함수를 호출합니다.
import {reportError} from '@howmuchhome-web/error';
reportError(error: Error, errorLevel?: ErrorLevel): string | undefined동작:
isSilentError(error)이거나errorLevel === ErrorLevel.IGNORE이면 아무것도 하지 않고 종료합니다.error가HowmuchhomeError이면level(인자 우선),transactionName,fingerprint,tags,extra를 함께Logger.captureError로 넘깁니다.- 그 외 일반
Error는 객체에level을 얹은 뒤Logger.captureError(error)를 호출합니다. - 전송한 경우
Logger.captureError가 반환한 SentryeventId를 반환합니다. silent / IGNORE 로 초기 종료하면undefined를 반환합니다. 호출부에서는 반환값의 truthy 여부로 판단하면 됩니다(전역 바운더리도 이 패턴).
ErrorBoundary
react-error-boundary 의 ErrorBoundary 를 감싼 컴포넌트입니다. 에러 발생 시 onError 콜백을 실행하고 reportError 로 자동 전송한 뒤, 반환된 eventId 를 onGenerateEventId 로 전달합니다.
web 과 native 는 파일이 분리되어 있습니다.
| 파일 | 대상 | 차이 |
|---|---|---|
ErrorBoundary.tsx | web | 정적 fallback: ReactNode |
ErrorBoundary.native.tsx | native | fallback 에 더해 fallbackRender(재시도) 지원 |
web Props
type Props = {
errorLevel?: ErrorLevel;
children: ReactNode;
fallback: ReactNode;
onError?: (error: Error, info: {componentStack: string}) => void;
onGenerateEventId?: (eventId: string) => void;
};native Props
type Props = {
errorLevel?: ErrorLevel;
children: ReactNode;
fallback?: ReactNode;
fallbackRender?: (props: {resetErrorBoundary: () => void}) => ReactNode;
onError?: (error: Error, info: {componentStack: string}) => void;
onGenerateEventId?: (eventId: string) => void;
};fallbackRender가 있으면fallback은 무시됩니다.- native 에서 재시도가 필요한 화면은 보통
fallbackRender를 씁니다.resetErrorBoundary가 자식 트리를 재마운트하므로, web 의window.location.reload를 대신합니다. (native 는 expo-updates 를 Hermes release 에서 리로드에 쓸 수 없어 트리 재마운트로 대체합니다.)
앱의 전역 바운더리는 이 컴포넌트를 감싼 HowmuchhomeErrorBoundary(web: apps/app, native: apps/native-app)에서 401 처리, chunk-load 재시도(web), 에러 화면 UI 를 붙여 사용합니다.
ErrorFallback
컴포넌트가 아닌 에러 화면(예: Next.js error.tsx)에서 에러를 신고할 때 쓰는 얇은 컴포넌트입니다. 'use client' 이며, 마운트 시 useEffect 로 reportError 를 한 번 호출하고 children 을 그대로 렌더합니다.
import {ErrorFallback} from '@howmuchhome-web/error';
// app/error.tsx 등
<ErrorFallback error={error} errorLevel={ErrorLevel.ERROR}>
{/* 에러 UI */}
</ErrorFallback>| 프로퍼티 | 타입 | 설명 |
|---|---|---|
error | Error | 신고할 에러 |
errorLevel | ErrorLevel? | reportError 에 전달 |
children | ReactNode | 그대로 렌더링됨 |
native 배럴에는 없습니다.
withErrorHandler
콜백을 감싸, 실행 중 던져진 Error 를 지정 레벨의 HowmuchhomeError 로 다시 던지도록 만드는 헬퍼입니다. 동기/비동기(Promise) 콜백을 모두 지원합니다.
import {withErrorHandler, ErrorLevel} from '@howmuchhome-web/error';
const run = withErrorHandler(async () => {
await doSomething();
}, {errorLevel: ErrorLevel.WARNING});- 반환값이 thenable 이면
.catch에서, 동기면try/catch에서 래핑합니다. Error인스턴스만 래핑합니다. 그 외 값(문자열 등)은 원본 그대로 재던집니다.options기본값은{errorLevel: ErrorLevel.ERROR}입니다.
native 배럴에는 없습니다.
Sentry 설정 (web 전용)
Sentry beforeSend 파이프라인과 초기화용 기본 설정을 제공합니다. native 에서는 사용하지 않습니다.
sentryBaseConfig
Sentry.init 에 넘길 기본 옵션 객체입니다. dsn(SENTRY_DSN / NEXT_PUBLIC_SENTRY_DSN), environment(VERCEL_ENV), 그리고 beforeSend 가 설정되어 있습니다. beforeSend 는 sentryBeforeSend 를 호출하면서, 특정 메시지('Store is not initialized.', 'Request has been canceled', 'NEXT_HTTP_ERROR_FALLBACK')를 포함하는 에러를 drop 하는 추가 콜백을 넘깁니다.
logger 의 initSentry(/packages/logger)가 이 설정을 사용합니다.
// packages/logger/sentry.ts (참고)
import {sentryBaseConfig} from '@howmuchhome-web/error';
import * as Sentry from '@sentry/nextjs';
export function initSentry() {
if (process.env.NEXT_PUBLIC_ENV === 'production') {
Sentry.init(sentryBaseConfig);
}
}sentryBeforeSend
Sentry 이벤트를 전송 직전에 가공하는 beforeSend 구현입니다.
sentryBeforeSend(
event: Sentry.ErrorEvent,
hint: Sentry.EventHint,
additionalCallback?: (event, hint) => Sentry.ErrorEvent | null | void,
): Sentry.ErrorEvent | null- silent 에러(
isSilentError)이면null을 반환해 전송을 막습니다. HowmuchhomeError이면level→event.level,transactionName→ 정규화된event.transaction,message,fingerprint, 병합된tags/extra를 이벤트에 반영합니다.- 그 외 에러는
SendbirdError를WARNING으로 낮추고, 에러에level필드가 있으면 반영하며,event.transaction을 정규화합니다. additionalCallback이undefined가 아닌 값을 반환하면 그 값을 최종 결과로 사용합니다(예:null로 drop).
유틸리티
normalizeErrorPath
경로 문자열에서 쿼리스트링을 제거하고, 숫자/UUID/32자리 hex 세그먼트를 {id} 로 치환합니다. Sentry transaction 그룹핑 노이즈를 줄이기 위해 사용됩니다.
import {normalizeErrorPath} from '@howmuchhome-web/error';
normalizeErrorPath('/users/12345/posts?page=2'); // '/users/{id}/posts'isSilentError
에러 객체에 silent 마커가 붙어 있는지 판단합니다(내부적으로 __silentError__ own-property 로 표시). ErrorLevel.IGNORE 로 만든 에러가 여기에 해당합니다.
import {isSilentError} from '@howmuchhome-web/error';toSentryLevel
임의의 값을 Sentry SeverityLevel 로 변환합니다. 매핑 불가하면 undefined, 'warn' 은 'warning' 으로 정규화합니다.
@howmuchhome-web/api 와의 관계
@howmuchhome-web/api의 ApiServerError 는 이 패키지의 HowmuchhomeError 를 상속합니다.
// packages/api/error.ts (참고)
import {ErrorLevel, HowmuchhomeError, normalizeErrorPath} from '@howmuchhome-web/error';
export class ApiServerError<...> extends HowmuchhomeError { ... }즉 API 에러도 HowmuchhomeError 이므로, reportError / sentryBeforeSend 의 HowmuchhomeError 경로를 그대로 탑니다. ApiServerError 는 생성 시 transactionName, fingerprint(경로·메서드·상태코드·에러코드 조합, normalizeErrorPath 적용), extra(detail·requestBody)를 채웁니다.
전역 바운더리에서 401 을 걸러 로그아웃 처리하는 코드가 이 상속 관계를 이용합니다.
if (ApiServerError.isApiServerError(e) && e.statusCode === 401) {
// 세션 정리
}서버 액션 경계에서
ApiServerError는 클라이언트로 그대로 전달되지 않으므로,serializeForServerAction()/deserializeForServerAction()를 사용해야 합니다. 자세한 내용은@howmuchhome-web/api를 참고하세요.
무엇을 언제 쓰나
| 상황 | 사용 |
|---|---|
| 컴포넌트 트리 에러 격리 + 폴백 UI | ErrorBoundary (web) / ErrorBoundary from /native |
Next.js error.tsx 등에서 에러 신고 | ErrorFallback |
| 도메인 에러를 메타데이터와 함께 던지기 | new HowmuchhomeError(...) |
임의 콜백의 에러를 HowmuchhomeError 로 래핑 | withErrorHandler |
| 명시적으로 Sentry 전송 | reportError (또는 직접 Logger.captureError) |
| Sentry 전송에서 제외 | ErrorLevel.IGNORE / silent 처리 |
에러 로깅의 큰 그림과 Datadog/Sentry 사용 규칙은 로깅 가이드, 로그 API 는 @howmuchhome-web/logger를 참고하세요.