Error Handling
Runtime Error Case
API ์๋ฒ๋ก ์ผ์์ ์ผ๋ก ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค๋ฉด ์คํฌ๋ฆฝํธ ์๋ฌ๋ก ์ธํด ํ์ด์ง ํ์๊ฐ ์๋๋ ๋ฌธ์ ๋ฐ์
try & catch ๋ก ๊ฐ์ธ๋ ์์ ํ์ง ์๋ค.
request abort
์ ๋ฐ์๋๋ error๋AbortError
, DOMException์ด๋ฏ๋ก cause ์์ฑ์ด ์๋ค.DOMException error๋ ์ผ๋ฐ์ ์ธ ํ์ ์๋ฌ๊ฐ ์๋๊ธฐ ๋๋ฌธ์ ์๋ฌ๊ฐ ์ผ์ด๋ ์์ฑ(
error.cause
)์ด ์์ด ์คํฌ๋ฆฝํธ ์๋ฌ๋ก ์ธํด ํ์ด์ง ํ์๊ฐ ์๋จ
JSON.stringify()๋ฅผ ์ฌ์ฉํ ๋ data๊ฐ Serializable ํ์ง ์์ ๊ฒฝ์ฐ (์ค๋ธ์ ํธ๊ฐ ์ํ์ฐธ์กฐ๊ฐ ๋๋ ๊ฒฝ์ฐ) ํ์ ์๋ฌ ๋ฐ์์ผ๋ก ํ์ด์ง ํ์ ์๋จ
์ ์ดํ ์ ์๋ ์๋ฌ
์๋ฌ๋ ์ํฉ์ ๋ฐ๋ผ ๊ฐ๊ฐ ๋ค๋ฅด๊ฒ ๋ฐ์ํ๋ฉฐ ๋ชจ๋ ์ผ์ด์ค๋ฅผ ์์ ํ ์ ์ดํ๋ ๊ฒ์ ์ด๋ ต๋ค.
์ ์ดํ ์ ์๋ ๋ถ๋ถ์์ UI Blocking์ ๋ฐ์์ํค๋๊ฒ์ด ๋ฌธ์
์ ์ดํ ์ ์๋ ๋ถ๋ถ์์ ์๋ฌ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ, ํด๋น ๋ถ๋ถ์ ๊ฐ์ธ ์๋ฌ๊ฐ ์ ํ๋์ด ํ์ด์ง๊ฐ ์ค๋จ๋๋ ๊ฒ์ ๋ฐฉ์งํ๋ ๊ฒ์ด ์ค์
Error Boundary
UI ์ผ๋ถ๋ถ์ ์คํฌ๋ฆฝํธ ์๋ฌ๊ฐ ์ ์ฒด ์ ํ๋ฆฌ์ผ์ด์ ์ ์ค๋จํด์๋ ์๋๋ค.
์ ํ๋ฆฌ์ผ์ด์ ์ ์ฒด๊ฐ ์ค๋จ๋๋๊ฒ์ ๋ฐฉ์งํ๊ธฐ ์ํด React v16์์ Error Boundary๋ผ๋ ์๋ก์ด ๊ฐ๋ ์ด ๋์ ๋จ
React Component tree ํ์ ๋ด๋ถ์ ์๋ฐ์คํฌ๋ฆฝํธ error๋ฅผ catchํ๋ ์ปดํฌ๋ํธ
๋ฌธ์ ๊ฐ ๋ฐ์ํ ๋ถ๋ถ์ ๋ํด falback UI๋ฅผ ์ ๊ณตํ๋ค.
ํน์ง
ErrorBoundary ์์ ์ปดํฌ๋ํธ์์ ์๋ฌ๊ฐ ๋ฐ์๋ ๊ฒฝ์ฐ ๊ฐ์ฅ ์ธ์ ํ ์์ ErrorBoundary์ ์ ๊ณต๋ Fallback UI๋ฅผ ๋ ๋๋ง
ErrorBoundary๋
componentDidCatch
๋ผ์ดํ์ฌ์ดํด ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์๋ฌ๋ฅผ ์ฒ๋ฆฌํ๋ค.ํด๋น ๋ฉ์๋๋ Class Component์์๋ง ์ฌ์ฉ์ด ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์ ErrorBoundary ์ปดํฌ๋ํธ๋ Class๋ก ๋ง๋ค์ด์ง๋ค.
ErrorBoundary๋ฅผ ๊ตฌํํ ๋ ์ค๋ฅ์ ๋ํ ์๋ต์ผ๋ก ์ํ๋ฅผ ์ ๋ฐ์ดํธํ๊ณ ์ฌ์ฉ์์๊ฒ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ํ์ํ ์ ์๋
Static getDerivedStateFromError
,componentDidCatch method
์ด ๊ตฌํ๋์ด์ผ ํ๋ค.
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false }
}
// ํ์ ์ปดํฌ๋ํธ์์ ์๋ฌ๊ฐ ๋ฐ์๋ ๊ฒฝ์ฐ ํธ์ถ๋จ
static getDerivedStateFromError(error) {
// fallback UI๋ฅผ ๋ณด์ฌ์ฃผ๊ธฐ ์ํด ์๋ฌ ์ํ๋ฅผ ์
๋ฐ์ดํธ
return { hasError: true }
}
// ์๋ฌ๊ฐ ๋ฐ์๋ ๊ฒฝ์ฐ ์คํ๋๋ ๋ผ์ดํ์ฌ์ดํด ๋ฉ์๋
componentDidCatch(error, info) {
/*
* Example: componentStack
* in ComponentThatThrow (created by App)
*. in ErrorBoundray (created by App)
*. in div
*. in App
*/
logErrorToMyService(error, info.componentStack);
}
if (this.state.hasError){
// render fallback UI
return this.props.fallback
}
return this.props.children
}
getDerivedStateFromError()
error๋ฅผ ๋งค๊ฐ๋ณ์๋ก ๊ฐฑ์ ๋ state๋ฅผ ๋ฐํํ๋ค.
render step์์ ํธ์ถ (VDOM์ด ์์ฑ๋๊ณ ์ด์ VDOM๊ณผ ๋น๊ตํ๋ ๋จ๊ณ์์ ํธ์ถ๋จ)
VDOM ๊ตฌ์ฑ์ด ๊นจ์ง๋ฉด ์๋๊ธฐ ๋๋ฌธ์ ๋ด๋ถ์์ ์ฌ์ด๋์ดํํธ๊ฐ ๋ฐ์ ๊ฐ๋ฅํ ์์ ์ ํด์ ์๋จ
fallback UI๋ฅผ ๋ ๋๋งํ ์ง ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ํ๋ ์์ ์ผ๋ก์ ์๋ฌ ์ํ๋ฅผ ์ ๋ฐ์ดํธํ๋ ์ฝ๋๋ง ์์ฑ
componentDidCatch()
commit step์์ ํธ์ถ (VDOM ๋ณ๊ฒฝ์ฌํญ์ ์ค์ DOM์ ์ ์ฉํ๋ ๋จ๊ณ์์ ํธ์ถ๋จ)
์ฌ์ด๋์ดํํธ๊ฐ ๋ฐ์ํด๋ ๋ฌด๊ด
์๋ฌ ์ ๋ณด๋ฅผ ๋จ๊ธธ ๋ ์ฌ์ฉํ๋ค. (์๋ฌ ๋ก๊น )
error -> ์๋ฌ์ ๋ํ ์ ๋ณด
info -> ์ด๋ค ์ปดํฌ๋ํธ๊ฐ ์๋ฌ๋ฅผ ๋ฐ์์์ผฐ๋์ง์ ๋ํ ์ ๋ณด (componentStack)
ErrorBoundary๊ฐ ๋ค๋ฃจ์ง ์๋ Error
Async ๋์๋ค (Event Handler, setTimeout, rAF)
์ฌ์ฉ์ ์ธํฐ๋ ์ ๋๋ ํน์ ์ด๋ฒคํธ๋ก ๋ ๋๋ ์ดํ์ ๋ฐ์๋๊ธฐ ๋๋ฌธ์ ErrorBoundary๊ฐ catchํ์ง ๋ชปํ๋ค.
์ฆ, ๋ ๋๋ง ๋์ค์ ์ผ์ด๋ Error๋ง catch ๊ฐ๋ฅ
unhandledrejection
์ด๋ฒคํธ๋ฅผ ํตํด ํ์ ์ปดํฌ๋ํธ์ ๋น๋๊ธฐ ์๋ฌ๋ฅผ ์์ ์ปดํฌ๋ํธ์์ ์ก์๋ผ ์ ์๋ค.promise.reject๊ฐ ๋ฐ์ํ๋ฉด ์ด๋ฒคํธ ๋ฐ์๋จ
ServerSide Rendering
ErrorBoundary ๋ด๋ถ ์๋ฌ
try-catch๋ก ์ฒ๋ฆฌ๋ ์๋ฌ
react-Error-Boundary
์ง์ ErrorBoundary๋ฅผ ์ ์ํ์ง ์๊ณ ์์ฉํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉ
"use client"
import { ErrorBoundary } from 'react-error-boundray';
<ErrorBoundray fallback={<div>Error ๋ฐ์< /div>}>
<Component>
</ErrorBoundray>
fallbackRender()
-> props์ผ๋ก ์๋ฌ์ ๋ฐ๋ฅธ fallback UI ๊ตฌ์ฑ ๊ฐ๋ฅ
const fallbackRender = ({ error, resetErrorBoundary }) => {
// resetErrorBoundary()๋ฅผ ํธ์ถํ๋ฉด ์๋ฌ๋ฐ์ด๋๋ฆฌ ์ํ๋ฅผ ์ด๊ธฐํ์ํค๊ณ , ๋ฆฌ๋ ๋๋ง
return (
<div role='alert'>
<p>{error.message}</p>
</div>
)
}
<ErrorBoundray
fallbackRender={fallbackRender}
onReset={(details) => {
// ์๋ฌ๊ฐ ๋ค์ ๋ฐ์๋์ง ์๊ธฐ ์ํด ์ํ๋ฅผ ์ด๊ธฐํ์ํค๋ ์ฝ๋ ์์ฑ
}}>
<Component>
</ErrorBoundray>
useErrorBoundary() hook
-๋น๋๊ธฐ ์ฝ๋ ์คํ ์ดํ์ ๋ฐ์ํ๋ ์๋ฌ๋ฅผ ๋์ํ๊ธฐ ์ํ showBoundary() ๋ฅผ ์ ๊ณต
fallback ์ปดํฌ๋ํธ ๋ด์์ ๋ฆฌ๋ ๋๋ง์ ๋ฐ์์ํค๊ธฐ ์ํ resetBoundary()๋ฅผ ์ ๊ณต
withErrorBoundary() HOC ์ปดํฌ๋ํธ ์ ๊ณต
import { useErrorBoundary } from 'react-error-boundary'
//...
const { showBoundray } = useErrorBoundary();
useEffect(() => {
somethingFetch().then().catch(error => showBoundary(error));
}, [])
import { useErrorBoundary } from 'react-error-boundary'
const ErrorFallback = ({ error }) => {
const { resetBoundary } = useErrorBoundary();
return (
<div role='alert'>
<p>{error.message}</p>
<button type="button" onClick={resetBoundary}>Try Again</button>
</div>
)
}
import { withErrorBoundary } from 'react-error-boundary'
const ComponentWithErrorBoundary = withErrorBoundary(Component, {
fallback: <div>Error occur</div>,
onError: (error, info) {}
})
Sentry
์ฝ๋ ๊ด๋ จ ๋ฌธ์ ๋ฅผ ์๋ณํ๊ณ ์์ ํ๋๋ฐ ๋์์ ์ฃผ๋ ์ํํธ์จ์ด ๋ชจ๋ํฐ๋ง ๋๊ตฌ
๋ฐํ์ ๋จ๊ณ์ ์๋ฌ๋ฅผ ์์ง
์๋ฌ๊ฐ ๋ฐ์ํ ํ๊ฒฝ์ ๋ก๊ทธ๋ฅผ ์ง์ ํ์ธํ๋ ๊ฒ์ ๋ถ๊ฐ๋ฅํ๊ณ ์๋ฌํ๊ฒฝ์ ์ฌํ์ด ๋ถ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์ ์๋ฌ๋ฅผ ์์งํ๋๊ฒ์ด ์ค์ํ๋ค.
ํด๋ผ์ด์ธํธ ์๋ฌ๋ฅผ ์์งํ์ฌ ์ฌ๋์ ์ฐ๋ํ์ฌ ์๋ฆผ ์ ๊ณต
์์ค์ฝ๋ ์ปจํ ์คํธ์ ๋๋ถ์ด Stack trace ์ ๊ณต
Commit ์ ๋ณด ๋ฐ Stack trace ๊ธฐ๋ฐ์ผ๋ก ์์ฌ์ค๋ฌ์ด commit ๋ฐ ํ ๋น์ ์ ์
Sentry ๊ธฐ๋ฅ
Stack trace
์ ๊ณต๋ SourceMap ๊ธฐ๋ฐ Stack trece๋ก ์๋ฌ ํ๊ฒฝ ํ์ ๊ฐ๋ฅ
์ฌ๋ฌ ์๋ฌ๋ฅผ ๊ทธ๋ฃนํํ์ฌ ํ๋์ ์ด์๋ก ์ ์
์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ์ฝ๋๊ฐ ์ด๋์์ ์๋ฌ๋ฅผ ๋ฐ์ ์์ผฐ๋์ง ํ์ธ ๊ฐ๋ฅ
Breadcrumb
Error Event ๋ฐ์๊น์ง์ ๊ธฐ๋ก๊ณผ ํ์๋ผ์ธ ์ ๊ณต
HTTP Request, Console.log, Server log, DOM Event ๋ฑ์ด ํฌํจ๋๋ค.
์ด๋ค ๋์๋ค๋ก ์ฌ์ฉ์๊ฐ ์๋ฌ๊ฐ ๋ฐ์ํ๋์ง ์ด์ ๋ฅผ ๋ช ํํ๊ฒ ํ์ ํ๊ณ ํด๋น ์ปจํ ์ค๋ฅผ ์ดํดํจ์ผ๋ก์จ ์๋ฌ๋ฅผ ์ฌํํ๊ธฐ ์ฉ์ดํด์ง๋ค.
Session Replay
์ฌ์ฉ์ ์ธ์ ์ ๋น๋์ค์ฒ๋ผ ์ฌํ
์ค๋ฅ/์ฑ๋ฅ ๋ฌธ์ ๋ฐ์ ์ /ํ ์ํฉ ํ์
๋ฌธ์ ์ ๋ํ ์ฌ์ธต์ ์ธ ๋๋ฒ๊น , ๋ฌธ์ ๋ฅผ ์ฌํํ๊ณ ๋ ๋น ๋ฅด๊ฒ ํด๊ฒฐ ๊ฐ๋ฅ
UI ์ด์์ ๊ฒฝ์ฐ ์ ๊ณต
Alert
์ฝ๋์ ๋ฌธ์ / ์ฌ์ฉ์์๊ฒ ๋ฏธ์น๋ ์ํฅ์ ๋ํ ์ค์๊ฐ์ ์ธ ๊ฐ์์ฑ ์ ๊ณต
์ง์ ํ ๊ท์น์ ์ํด ๋ฐ์๋๋ alert
alert ๋ชฉ๋ก ํํฐ๋ง, alert์ ๋ํ ๋นํ์ฑํ/mute ๊ธฐ๋ฅ, notification ์ ๊ณต
์ค์น
npx @sentry/wizard@latest -i nextjs
๋ถ๊ฐ ์ฌ์ฉ ๋ฐฉ๋ฒ
Issue ํจ๋
์ ํ๋ฆฌ์ผ์ด์ ์ค๋ฅ, ์ฑ๋ฅ๋ฌธ์ ์ ๋ํ ์ ๋ณด ํ์
ํ๊ฒฝ์ ๋ฐ๋ฅธ ํํฐ๋ง ๊ฐ๋ฅ
ํ๋์ ์ด์๋ ์ ์ฌํ ์์ธ์ผ๋ก ๊ทธ๋ฃนํ๋ ์๋ฌ๋ค
์ด์์ ๋ฐ์ ๋น๋, ์ถ์ด, ์ฌ์ฉ์๋ณ ์ํฅ๋ ํ์ ๊ฐ๋ฅ
Issue ์ํ
New: ์์ฑํ์ง 7์ผ ์ด๋ด์ธ ์ด์
Ongoing: 7์ผ ์ด์ ๋์๊ฑฐ๋, ์๋์ผ๋ก reviewed ์ฒ๋ฆฌ๋ ์ด์
Escalating: ์ค๋๋ ์ด์ ์ค ๋น๋๊ฐ ๊ธ๊ฒฉํ ๋์ด๋ ์ด์
Regressed: resolved ๋์๋ค๊ฐ ๋ค์ ๋ํ๋ ์ด์
Archived: ๋ณด๊ด์ฒ๋ฆฌ๋ ์ด์
Resolved: ํด๊ฒฐ ์ฒ๋ฆฌ๋ ์ด์
Issue ํญ
Unresolved: ๊ฒํ ๊ฐ ํ์ํ, ์์ง ํด๊ฒฐ๋์ง ์์ ๋ชจ๋ ์ด์ ์ด์งํฉ
For Review: ๊ฒํ ๋ชฉ๋ก, ๊ฒํ ๊ฐ ํ์ํ ์ด์, ํด๊ฒฐ๋์ง ์์ ์ด์๋ค์ ํ์ ์งํฉ
Regressed: ํด๊ฒฐ๋ ์ด์, ํด๊ฒฐ๋์ด์๊ฐ ๋ค์ ๋ฐ์ํ ์ด์
Escalating: ์ด์ ์ archived ๋ฌ๋ ์ด์ ์ค ์์๋๋ ์ด์ ๋น๋๊ฐ ์ด๊ณผ๋ ์ด์
Archived: ํด๊ฒฐ ์ฒ๋ฆฌ๋ ์ด์๋ค
Issue ํํฐ
By Project
By Environment
By Time
Custom filter
Issue ์์ธ ํจ๋
์ด์ ์ถ์ฒ
์ํฅ ๋ฒ์
๊ทธ๋ฃนํ๋ ์ด์์ ๋ํ ์์ฝ
24์๊ฐ/30์ผ๊ฐ์ ๋น๋
๊ด๋ จ ํ๊ทธ ์ ๋ณด
Stack Trace
Breadcrumbs
Session Replay ์ค์
Sentry.init() ๋ด์ ๊ธฐ๋กํ๋ ์ค์ ๊ฐ
์๋ ์ค์ ์, sentry.client.config.ts/js ์ ์ ์
import * as Sentry from '@sentry/nextjs';
Sentry.init({
// ์ค๋ฅ ๋ฐ์ ์ ๋
นํ๋๋ ๋ฆฌํ๋ ์ด์ ์ํ๋ง ๋น์จ
// ์ด ์ ํ์ ๋ฆฌํ๋ ์ด๋ ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ธฐ ์ ์ ์ด๋ฒคํธ๋ฅผ ์ต๋ 1๋ถ๊น์ง ๋
นํ
// or ์ธ์
์ด ์ข
๋ฃ๋ ๋๊น์ง ๊ณ์ ๋
นํํจ
// 1.0์ ์ค๋ฅ๊ฐ ์๋ ๋ชจ๋ ์ธ์
์ ์บก์ฒํ๊ณ 0์ ์ํจ (0 ~ 1.0)
replayOnErrorSampleRate: 1.0,
// ์ฌ์ฉ์ ์ธ์
์ ์ฒด์ ๊ฑธ์ณ ์ง์๋๋ ๋ฆฌํ๋ ์ด์ ์ํ๋ง ๋น์จ
// 1.0์ ๋ชจ๋ ๋ฆฌํ๋ ์ด๋ฅผ ์์งํ๊ณ 0์ ์ํจ (0 ~ 1.0)
replaySessionSampleRate: 0.1,
intergrations: [
new Setnry.Replay({
// ํ์ด์ง ๋ก๋ฉ ์ ๋ฐ์ ๊ฑธ์ณ ์ฌ์ฉ์๋ฅผ ์ถ์
// ํญ๋น ์ธ์
์ ์ง, ์ฌ๋ฌ ํญ์ ์ฌ์ฉํ๋ ํ ๋ช
์ ์ฌ์ฉ์๋ ์ฌ๋ฌ ์ธ์
์ผ๋ก ๊ธฐ๋ก
// ๊ธฐ๋ณธ๊ฐ: true
stickySession: true,
// SDK๊ฐ setnry๋ก ์ ์ก์ ์์ํ๊ธฐ ์ ๊น์ง์ ๋ฆฌํ๋ ์ด ๊ธธ์ด(ms)
// max: 15000, default: 5000,
minReplayDuration: 5000,
// ๋ชจ๋ ํ
์คํธ ์ปจํ
์ธ ๋ฅผ ๋ง์คํน์ฒ๋ฆฌ
maskAllText: true,
// media element blocking
blockAllMedia: true,
})
]
})
Session Replay ํจ๋
ํ์๋ผ์ธ
ํ๋ ์ด์ด
Breadcrumbs
๊ธฐํ ์ปดํฌ๋ํธ ํจ๋๋ก ๊ตฌ๋ถ
ํ์ด์ง ์ก์
๋ฆฌํ๋ ์ด ๊ณต์
์ญ์
Last updated