์ด์ ์ถ์ ๐ซ Bugsnag์ Redwood๊ฐ ์์ต๋๋ค.
๋น์ฐํ ํซํ ์ธ๋ฌผ์ด ์์ฃ .๐! ์ด๊ฒ์ ๋น์ ์ด ๊ณ ๋ คํ์ง ์์ ๊ฐ์ฅ์๋ฆฌ ์ํฉ์ผ ์๋ ์๊ณ , ๋น์ ์ ์ธํ๋ผ ์์ค ์ค์ ์ด๋ค ๊ฒ์ผ ์๋ ์๊ณ , ๋งค์ฐ ์ ๋ขฐํ ์ ์๋ ์ 3์ ์๋น์ค๊ฐ ์๋ ์๋ ์๊ณ , ์ฌ์ฉ์๊ฐ ๋น์ ์ด ๊ฐ์ฅ ๊ธฐ๋ํ์ง ์๋ ์ผ์ ํ ์๋ ์๋ค. ๊ทธ๋ฆฌ๊ณ ๊ทธ๋ค์ ์ฒ๋ฆฌ๋์ง ์์ ์ค๋ฅ๋ฅผ ๋ง๋ฌ์ ์๋ ์๋ค.
์์ฐ ์์ฉ ํ๋ก๊ทธ๋จ์ ๋ํด ๋น์ ์ ์์ฉ ํ๋ก๊ทธ๋จ์ด ์ฌ์ฉ์๋ฅผ ๊ธฐ์๊ฒ ํ๊ธฐ ์ํด์๋ ๋ฌธ์ ๋ฅผ ์ ์ํ๊ฒ ์ง๋จํ๊ณ ๊ฐ๋ฅํ ํ ๋นจ๋ฆฌ ๋ณต๊ตฌ ๋ฐฉ์์ ์ ์ํด์ผ ํ๋ค.๋ฌธ์ ๋ฅผ ๋ณผ ์ ์๋ ์ข์ ๋ก๊ทธ๊ฐ ํ์ํ๋ฉฐ, ๋๊ตฌ๊ฐ ๋ง์ฐฐ์ ์ต๋ํ ์ค์ผ ์ ์๊ธฐ๋ฅผ ๋ฐ๋๋๋ค.๊ธฐ์ ๊ณ์์๋ ํ๊ท ๋ณต๊ตฌ ์๊ฐ(MTTR)์ ์ฌ์ฉํ์ฌ ํ๊ฐํฉ๋๋ค. ๊ฐ๋จํ ๋งํ๋ฉด ๊ฐ๋ฅํ ํ ๋นจ๋ฆฌ ๋ณต๊ตฌํ๊ณ ๋ฐ์กํ์๊ธฐ ๋ฐ๋๋๋ค.
Part 1: Frontend
Part 2: API and Graphql
์๊ฐ
์ด ๊ฐ์ข๋ ๋ฒ๊ทธ์คnag๋ฅผ ์ค์ ํ๋ ๋ฐฉ๋ฒ์ ์๊ฐํ ๊ฒ์
๋๋ค. ์ด๊ฒ์ ์ด์ ์ถ์ ๋๊ตฌ(์ฃผ๋ก ๋ชจ๋ฐ์ผ ๊ฐ๋ฐ์๊ฐ ์ฌ์ฉํจ)์ด์ง๋ง ์น๊ณผ ๋
ธ๋๋ฅผ ์ง์ํฉ๋๋ค.js, ๋ถ์ ๋๋ฌด - ์๋ถ๋ถ๊ณผapi ์ธก๋ฉด์์.
์ฐ๋ฆฌ๊ฐ ์ฒ์์ผ๋ก Tape.sh(๋ชจ๋ฐ์ผ ๊ฐ๋ฐ์๋ฅผ ์ํ ์คํฌ๋ฆฐ ๊ธฐ๋ก ๋๊ตฌ)์ ๋ด๋์ ์ด๋๋ก ์ฐ๋ฆฌ๋ ์ด๋ฐ ๊ฐ์์ฑ์ ๊ฐ์ง๊ณ ์ฌํํ๊ธฐ ์ด๋ ค์ด ๋ฌธ์ ๋ฅผ ๋ณต๊ตฌํด์ผ ํ๋ค.๊ทธ๊ฒ์ ์ฌ์ง์ด ์ฐ๋ฆฌ๊ฐ ํ์ผ์ ๋๋ ค์ฃผ๋ ๊ฒ์ ๋์๋ค!
์ฐ๋ฆฌ๋ ์ ๋ง Bugsnag๋ฅผ ์ข์ํ์ง๋ง, ๋๋ ์์ ํ ๊ฐ์ ๊ณผ์ ์ ๋ฐ๋ผ์, ๋ค๊ฐ ์ ํํ ๋๊ตฌ๋ฅผ ์ฌ์ฉํ ์ ์๋ค. ๋๋ ๊ทธ๊ฒ์ด ๋๋ฌด ๋ค๋ฅด๋ค๊ณ ์๊ฐํ์ง ์๋๋ค.
๋๋ฌด ๊ธธ์ด์ ์ฝ์ ์๊ฐ ์์ด์.
์๋ถ๋ถ์ ๋ฒ๊ทธ์คnag ์ด์ ์ฒ๋ฆฌ ํ๋ก๊ทธ๋จ ๊ตฌ์ฑ ์์๋ฅผ ์ฌ์ฉํ์ฌ Redwood ์์ฉ ํ๋ก๊ทธ๋จ์ ํฌ์ฅํ๊ณ sourcemaps(์นpack ํ๋ฌ๊ทธ์ธ์ด๋ ๋ค๋ฅธ ๋ฐฉ์์ ์ฌ์ฉ)๋ฅผ ์
๋ก๋ํฉ๋๋ค.
๋ฐฑ์๋์ ๋ํด ๋ง์ถคํ apollo ์๋ฒ ํ๋ฌ๊ทธ์ธ์ ๋ง๋ค๊ณ ๋ฒ๊ทธnag์ ์ด์์ ์ ๋ฌํฉ๋๋ค.์ฌ์ฉ์ ์ ์ ํจ์์์ ์ฌ์ฉํ ์ ์๋๋ก ์ค๋ฅ๋ฅผ ๋ณด๊ณ ํ ์ ์๋ ๋ฐฉ๋ฒ์ด ์๋์ง ํ์ธํ์ญ์์ค.
์น์
1: ํ๋ฐํธ์๋
๋จผ์ bugsnag ๋์๋ณด๋์ ๋ก๊ทธ์ธํ ๋ค์ ํญ๋ชฉ์ ๋ง๋ญ๋๋ค.
์ ํญ๋ชฉ > ๋ธ๋ผ์ฐ์ > ๋ฐ์
API ํค๋ฅผ ์ก์ผ๋ฉด ๋์ค์ ์ฌ์ฉํ ๊ฒ์
๋๋ค.
์์ธ ์ฒ๋ฆฌ ํ๋ก๊ทธ๋จ ์ค์
Bugsnag ๋ผ์ด๋ธ๋ฌ๋ฆฌ์react ํ๋ฌ๊ทธ์ธ ์ถ๊ฐ
# -W because we'll use it in both web and api
yarn add -W @bugsnag/js
yarn workspace web add @bugsnag/plugin-react
ํ์ฌ, ์ฐ๋ฆฌ๋ ์ด์ ์ฒ๋ฆฌ ํ๋ก๊ทธ๋จ์ผ๋ก ์ ์ฒด ์ ๋จreact ํ๋ก๊ทธ๋จ์ ํฌ์ฅํด์ผ ํฉ๋๋ค.์น/src/index์์ํ์ฌ ๋ช
+ import Bugsnag from '@bugsnag/js'
+ import BugsnagPluginReact from '@bugsnag/plugin-react'
+ Bugsnag.start({
+ apiKey: process.env.BUGSNAG_NOTIFIER_API_KEY,
+ plugins: [new BugsnagPluginReact()],
+ releaseStage: process.env.CONTEXT || process.env.NODE_ENV,
+ appVersion: process.env.DEPLOY_ID,
+ })
+ const BugsnagBoundary = Bugsnag.getPlugin('react').createErrorBoundary(React)
ReactDOM.render(
- <FatalErrorBoundary page={FatalErrorPage}>
+ <BugsnagBoundary
+ FallbackComponent={<FatalErrorBoundary page={FatalErrorPage} />}
+ >
<RedwoodProvider>
<Routes />
</RedwoodProvider>
+ </BugsnagBoundar>
- </FatalErrorBoundary>,
document.getElementById('redwood-app')
)
๋ด๊ฐ ์ฌ์ฉํ๋ ์๋น ๊ตฌ์ฑ ์์๋ ๊ธฐ๋ณธ Redwood FatalErrorBoundary์ด์ง๋ง, ์ฌ๊ธฐ์์ ์์ ์ ๊ตฌ์ฑ ์์๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.process.env
๊ฐ์ ๋ณ์๋ฅผ ์ด๋ป๊ฒ ์ฌ์ฉํ๋์ง ์ฃผ์ํ์ญ์์ค.๊ธฐ๋ณธ์ ์ผ๋ก ํ์ผ(์ ํ!)ํ๊ฒฝ ๋ณ์๋ฅผ ์ ๋ฉด์ ๊ณต๊ฐํ์ง ์์ต๋๋ค.๊ทธ๋์ ํ๋ชฉ์ ๊ณ ์น์.toml์ ์ด๋ฌํ ๋ณ์๋ฅผ ํฌํจํ๋ค
[web]
port = 8910
apiProxyPath = "/.netlify/functions"
+ includeEnvironmentVariables = ['BUGSNAG_NOTIFIER_API_KEY', 'CONTEXT', 'NODE_ENV', 'DEPLOY_ID']
[api]
port = 8911
[browser]
๊ธฐ์ตํด!
๋ง์ง๋ง์ผ๋ก BUGSNAG_NOTIFIER_API_KEY
์ ์ ์ถ๊ฐํ๋ ๊ฒ์ ๊ธฐ์ตํ์ญ์์ค.ํ๊ฒฝ ํ์ผ
๋์โจ! ํ์ฌ ์ฌ์ฉ์๊ฐ ์ด์์ด ๋ฐ์ํ๋ฉด ์๋ฆผ์ ๋ฐ์ ์ ์์ต๋๋ค.ํ์ง๋ง ์๋ฐ์คํฌ๋ฆฝํธ๊ฐ ์ถ์๋์๊ธฐ ๋๋ฌธ์ ๋ณผ ์ ์๋ ๋ก๊ทธ๋ ์์ง ๊ทธ๋ ๊ฒ ์ ์ฉํ์ง ์์ต๋๋ค.์ง๊ธ๊น์ง ์ฐ๋ฆฌ๋ ๋ฌด์จ ์ผ์ด ์ผ์ด๋ฌ๋์ง ์๊ณ ์๋ค. ์ด์ ์
์น ํ์ด์ง ์ค์ ๋ฐ ์๋ณธ ์ง๋ ์
๋ก๋
Bugsnag์ ํจํค์ง ํ๋ฌ๊ทธ์ธ์ ์ฌ์ฉํ์ฌ ์ค์ ํฉ๋๋ค.์ค์น ๋ฐฉ๋ฒ:
yarn workspace web add webpack-bugsnag-plugins
ํ์ผ์ ์ํ ์น ์ค์ ์ ์ฌ์ฉ์ ์ ์ํ๋ ค๋ฉด web/config/webpack.config.js
์ ํ์ผ์ ๋ง๋ค์ด์ผ ํฉ๋๋ค.๋ง์ฝ ๋ค๊ฐ ์ด๋ฏธ ๊ทธ๊ฒ์ ๊ฐ์ง๊ณ ์๋ค๋ฉด, ์ข์, ๊ทธ๊ฒ์ ์ถ๊ฐํ๊ธฐ๋ง ํ๋ฉด ๋๋ค.
/* ==== web/config/webpack.config.js ==== */
// Important, so webpack can use the environment variables
require('dotenv-defaults').config()
const {
BugsnagSourceMapUploaderPlugin,
BugsnagBuildReporterPlugin,
} = require('webpack-bugsnag-plugins')
module.exports = (config) => {
// Check if its building in netlify
// No need to upload source maps when building locally
const netlifyBuild = !!process.env.NETLIFY
const bugsnagPlugins = netlifyBuild
? [
new BugsnagBuildReporterPlugin({
apiKey: process.env.BUGSNAG_NOTIFIER_API_KEY,
appVersion: process.env.DEPLOY_ID,
releaseStage: process.env.CONTEXT || process.env.NODE_ENV,
sourceControl: {
provider: 'github',
repository: process.env.REPOSITORY_URL,
revision: process.env.COMMIT_REF,
},
}),
new BugsnagSourceMapUploaderPlugin({
apiKey: process.env.BUGSNAG_NOTIFIER_API_KEY,
appVersion: process.env.DEPLOY_ID,
}),
]
: []
config.plugins = [...config.plugins, ...bugsnagPlugins]
return config
}
์ฃผ์, ์ฐ๋ฆฌ๊ฐ ์ฌ์ฉํ๋ ๊ฒ์ process.env.NETLIFY
ํ๊ฒฝ ๋ณ์์
๋๋ค.์ด๋ ๊ฒ ํ๋ฉด ์ฐ๋ฆฌ๋ ํ์ง ๋ฒ์ ์ ์๋ณธ ์ง๋๋ฅผ ์
๋ก๋ํ์ง ์์ ๊ฒ์ด๋ค.ํ๊ฒฝ ๋ณ์ REPOSITORY_URL
, COMMIT_REF
, DEPLOY_ID
๊ณผ CONTEXT
์ Netlify์์ ์์ผ๋ฏ๋ก ์ฝ๋๋ฅผ ๋ฐฐ์นํ ์์น์ ๋ฐ๋ผ ์์ ํ์ญ์์ค.
์ค์ ํ์ธ
์ด์ ์ค์ ์ ๊ฒ์ฆํด ๋ณด๊ฒ ์ต๋๋ค.์ถ๊ฐํ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค.
throw new Error('Catch me bugsnag!')
์ ๋ฐฉ ์ฝ๋์ ์ด๋ ๊ณณ์์๋ ์ง, ๊ทธ๊ฒ์ด ์ด๋ฐ๋์์ ๋, ๊ณ๊ธฐํ (๋ฐ ์ ์๋ฉ์ผ) ์์ ๊ทธ๊ฒ์ ๋ณด์์ผ ํ๋ค.๋๋ ๋นต ๋ถ์ค๋ฌ๊ธฐ ๋ผ๋ฒจ์ ํตํด ๋ฌด์จ ์ผ์ด ์ผ์ด๋ฌ๋์ง, ์ ์ผ์ด๋ฌ๋์ง, ๊ทธ๋ฆฌ๊ณ ๊ทธ๊ฒ์ด ์ด๋ป๊ฒ ์ผ์ด๋ฌ๋์ง ๋ณผ ์ ์๋ค.
A note on sourcemaps with Netlify. If you have "optimise JS bundles" enabled on Netlify, they move your bundles to the Cloudfront CDN, and Bugsnag isn't able to match up the sourcemap to the js file.
Netlify UI์์ ์ค์ ์ ํด์ ํ๋ฉด ์คํ ์ถ์ ์ด ์์ ํ ํ์๋ฉ๋๋ค.๋ถํํ๊ฒ๋ Netlify๊ฐ ์
๋ก๋ํ๊ธฐ ์ ์ ํ์ผ์ ์ ์ด๋ฆ์ ๋ฐ๊ฟจ๋์ง, ํธ์คํธ ์ด๋ฆ์ด ๋ฌด์์ธ์ง ๊ณต๊ฐํ์ง ์์๊ธฐ ๋๋ฌธ์ Cloudfront์ ํจ๊ป ์ผํ ์๊ฐ์ ์์ต๋๋ค.
๊ฐ๋ฐ ๋ฐ ์ ํ ๊ตฌ์ฑ
์ข์ต๋๋ค. ์ค๋ฅ๋ฅผ ๋ณด์์ต๋๋ค. dev๋ฅผ ์ฌ์ฉํ์ง ์์ผ๋ ค๊ณ ํฉ๋๋ค. <EnvironmentAwareErrorBoundary>
์ ๋ง๋ญ๋๋ค.
const EnvironmentAwareErrorBoundary = React.memo(({ children, ...otherProps }) => {
if (process.env.NODE_ENV === 'development') {
return (
<FatalErrorBoundary page={FatalErrorBoundary} {...otherProps}>
{children}
</FatalErrorBoundary>
)
} else {
Bugsnag.start({
apiKey: process.env.BUGSNAG_NOTIFIER_API_KEY,
plugins: [new BugsnagPluginReact()],
releaseStage: process.env.CONTEXT || process.env.NODE_ENV,
appVersion: process.env.DEPLOY_ID,
})
const BugsnagBoundary = Bugsnag.getPlugin('react').createErrorBoundary(
React
)
return (
<BugsnagBoundary
FallbackComponent={<FatalErrorBoundary page={FatalErrorPage} />}
{...otherProps}
>
{children}
</BugsnagBoundary>
)
}
})
์ด๋ ๊ฒ ํ๋ ๋ชฉ์ ์ ๊ฐ๋ฐ ์ค์ ๊ธฐ๋ณธ Redwood FatalErrorBoundary๋ฅผ ์ฌ์ฉํ์ง๋ง, ์์ฐ ์ค์ ๋ฒ๊ทธ์คnag์ ์ด์์ ๋ณด๊ณ ํ๋ ๊ฒ์ด๋ค.
๋ค์ ๊ทธ๋ฆผ๊ณผ ๊ฐ์ด ์ด ๊ตฌ์ฑ ์์์ ์์ฉ ํ๋ก๊ทธ๋จ์ ํฌ์ฅํ ์ ์์ต๋๋ค.
ReactDOM.render(
+ <EnvironmentAwareErrorBoundary>
{*/ your other stuff */}
<RedwoodProvider>
<Routes />
</RedwoodProvider>
+ </EnvironmentAwareErrorBoundary>,
document.getElementById('redwood-app')
)
์น์
2: API ๋ฐ Graphql
graphql์ ์ฌ์ฉ์ ์ ์ ํ๋ฌ๊ทธ์ธ ๋ง๋ค๊ธฐ
๋ฐฑ์๋์ ๋ํด, ์ฐ๋ฆฌ๋graphql์์ ์ค๋ฅ๋ฅผ ํฌ์ฐฉํ๊ธฐ๋ฅผ ํฌ๋งํฉ๋๋ค.๋ฐ๋ผ์ Bugsnag ์ฝ๋๋ฅผ ์์ฉํ๊ธฐ ์ํ util ๋ชจ๋์ ๋ง๋๋ ๊ฒ๋ถํฐ ์์ํฉ๋๋ค.
api/src/lib/bugsnag.ํ์ฌ ๋ช
import Bugsnag from '@bugsnag/js'
import { isEmpty } from 'lodash'
Bugsnag.start({
apiKey: process.env.BUGSNAG_SERVER_API_KEY,
releaseStage: process.env.CONTEXT || process.env.NODE_ENV,
appVersion: process.env.DEPLOY_ID,
})
export const reportErrorFromContext = (requestContext) => {
const { errors, metrics, request, context } = requestContext
// Call bugsnag here
// But you could easily use something else here
Bugsnag.notify(new Error(errors), function (event) {
event.severity = 'error'
event.addMetadata('metrics', metrics)
event.addMetadata('errors', errors)
event.addMetadata('query', request)
})
}
export const reportError = (error) => {
Bugsnag.notify(error)
}
๋ง์ถคํ ์ํด๋ก ์๋ฒ ํ๋ฌ๊ทธ์ธ์์ ์ฌ์ฉํ ์ ์๋๋ก reportReportErrorFromContext
์ ๊ณต๊ฐํ์ง๋ง, reportError
์ ๋ค๋ฅธ ๊ณณ์์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์ด์ ํ๋ฌ๊ทธ์ธ์ ๋ง๋ค์ด์ ์๋ฒ ์ค์ ์ ์ถ๊ฐํฉ์๋ค
// === api/src/functions/graphql.js ===
+ import { reportErrorFromContext } from 'src/lib/bugsnag'
+ const bugSnagExceptionPlugin = {
+ requestDidStart() {
+ return {
+ didEncounterErrors(requestContext) {
+ reportErrorFromContext(requestContext)
+ },
+ }
+ },
+ }
export const handler = createGraphQLHandler({
getCurrentUser,
+ plugins: [bugSnagExceptionPlugin],
schema: makeMergedSchema({
schemas,
services: makeServices({ services }),
// ....rest of the file omitted for brevity
์ฌ์ฉ์ ์ ์ ํจ์
์ฐ๋ฆฌ๊ฐ ์ด๋ป๊ฒ reportError
๋ฐฉ๋ฒ์ ๋ง๋ค์๋์ง ๊ธฐ์ตํ์ญ๋๊น?์ด์ ์ฌ์ฉ์ ์ ์ ํจ์์์ ์ฌ์ฉํ ์ ์์ต๋๋ค
์ ํ ๊ตฌ์ฑ
Quick headsup here, Netlifyโs functions annoyingly donโt set the NODE_ENV value at runtime by default. So if you wanted to check if youโre running on prod, youโll have to make sure you set the value in the Netlify UI, but then in your netlify.toml set NODE_ENV to development for the build env (otherwise the build will fail).
์ ๋จ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก, ์ฐ๋ฆฌ๋ ๊ฐ๋ฐ ๊ณผ์ ์์ ๋ก๊ทธ ๊ธฐ๋ก ์ด์์ ์ฌ์ฉํ์ง ์๊ธฐ๋ฅผ ํฌ๋งํฉ๋๋ค. ๋ฐ๋ผ์, ์ฝ๋๋ฅผ ์ผ๋ถif๋ฌธ์ฅ์ ๋ด์ธํ๋ฉด ์์ฑ๋ฉ๋๋ค!์ฐ๋ฆฌ์ ์์์ ์ฐ๋ฆฌ๊ฐ ์ฌ์ฉํ๋ ๊ฒ์ process.env.LOG_EXCEPTIONS
๋ณ์๋ค.
api/src/lib/bugsnag.ํ์ฌ ๋ช
+ if (!isEmpty(process.env.LOG_EXCEPTIONS)) {
Bugsnag.start({
apiKey: process.env.BUGSNAG_SERVER_API_KEY,
releaseStage: process.env.CONTEXT || process.env.NODE_ENV,
appVersion: process.env.DEPLOY_ID,
})
+ }
export const reportReportErrorFromContext = (requestContext) => {
const { errors, metrics, request, context } = requestContext
// Note that netlify doesn't set node_env at runtime in functions
+ if (isEmpty(process.env.LOG_EXCEPTIONS)) {
+ return
+ }
์ด๊ฒ ๋ค์ผ!๐ ๋น์ ์ ์ง๊ธ ์์ฉ ํ๋ก๊ทธ๋จ์ ์์ํ ์ค๋น๊ฐ ๋์ด ์์ต๋๋ค. ์ด์์ ๋ฐ๊ฒฌํ๊ณ ์ถ์ ํ๋ฉฐ ๋ณต๊ตฌํ ์์ ์ด ์์ต๋๋ค. (๋ง์ฝ ๋ฐ์ํ๋ค๋ฉด)
๐๐ฝ PS ๋ Redwood์ ํ๋ ฅํ๋ ๋ด์ฉ์
๋๋ค.
Reference
์ด ๋ฌธ์ ์ ๊ดํ์ฌ(์ด์ ์ถ์ ๐ซ Bugsnag์ Redwood๊ฐ ์์ต๋๋ค.), ์ฐ๋ฆฌ๋ ์ด๊ณณ์์ ๋ ๋ง์ ์๋ฃ๋ฅผ ๋ฐ๊ฒฌํ๊ณ ๋งํฌ๋ฅผ ํด๋ฆญํ์ฌ ๋ณด์๋ค
https://dev.to/dac09/exception-tracking-with-bugsnag-and-redwood-4kk0
ํ
์คํธ๋ฅผ ์์ ๋กญ๊ฒ ๊ณต์ ํ๊ฑฐ๋ ๋ณต์ฌํ ์ ์์ต๋๋ค.ํ์ง๋ง ์ด ๋ฌธ์์ URL์ ์ฐธ์กฐ URL๋ก ๋จ๊ฒจ ๋์ญ์์ค.
์ฐ์ํ ๊ฐ๋ฐ์ ์ฝํ
์ธ ๋ฐ๊ฒฌ์ ์ ๋
(Collection and Share based on the CC Protocol.)
์๋ถ๋ถ์ ๋ฒ๊ทธ์คnag ์ด์ ์ฒ๋ฆฌ ํ๋ก๊ทธ๋จ ๊ตฌ์ฑ ์์๋ฅผ ์ฌ์ฉํ์ฌ Redwood ์์ฉ ํ๋ก๊ทธ๋จ์ ํฌ์ฅํ๊ณ sourcemaps(์นpack ํ๋ฌ๊ทธ์ธ์ด๋ ๋ค๋ฅธ ๋ฐฉ์์ ์ฌ์ฉ)๋ฅผ ์ ๋ก๋ํฉ๋๋ค.
๋ฐฑ์๋์ ๋ํด ๋ง์ถคํ apollo ์๋ฒ ํ๋ฌ๊ทธ์ธ์ ๋ง๋ค๊ณ ๋ฒ๊ทธnag์ ์ด์์ ์ ๋ฌํฉ๋๋ค.์ฌ์ฉ์ ์ ์ ํจ์์์ ์ฌ์ฉํ ์ ์๋๋ก ์ค๋ฅ๋ฅผ ๋ณด๊ณ ํ ์ ์๋ ๋ฐฉ๋ฒ์ด ์๋์ง ํ์ธํ์ญ์์ค.
์น์
1: ํ๋ฐํธ์๋
๋จผ์ bugsnag ๋์๋ณด๋์ ๋ก๊ทธ์ธํ ๋ค์ ํญ๋ชฉ์ ๋ง๋ญ๋๋ค.
์ ํญ๋ชฉ > ๋ธ๋ผ์ฐ์ > ๋ฐ์
API ํค๋ฅผ ์ก์ผ๋ฉด ๋์ค์ ์ฌ์ฉํ ๊ฒ์
๋๋ค.
์์ธ ์ฒ๋ฆฌ ํ๋ก๊ทธ๋จ ์ค์
Bugsnag ๋ผ์ด๋ธ๋ฌ๋ฆฌ์react ํ๋ฌ๊ทธ์ธ ์ถ๊ฐ
# -W because we'll use it in both web and api
yarn add -W @bugsnag/js
yarn workspace web add @bugsnag/plugin-react
ํ์ฌ, ์ฐ๋ฆฌ๋ ์ด์ ์ฒ๋ฆฌ ํ๋ก๊ทธ๋จ์ผ๋ก ์ ์ฒด ์ ๋จreact ํ๋ก๊ทธ๋จ์ ํฌ์ฅํด์ผ ํฉ๋๋ค.์น/src/index์์ํ์ฌ ๋ช
+ import Bugsnag from '@bugsnag/js'
+ import BugsnagPluginReact from '@bugsnag/plugin-react'
+ Bugsnag.start({
+ apiKey: process.env.BUGSNAG_NOTIFIER_API_KEY,
+ plugins: [new BugsnagPluginReact()],
+ releaseStage: process.env.CONTEXT || process.env.NODE_ENV,
+ appVersion: process.env.DEPLOY_ID,
+ })
+ const BugsnagBoundary = Bugsnag.getPlugin('react').createErrorBoundary(React)
ReactDOM.render(
- <FatalErrorBoundary page={FatalErrorPage}>
+ <BugsnagBoundary
+ FallbackComponent={<FatalErrorBoundary page={FatalErrorPage} />}
+ >
<RedwoodProvider>
<Routes />
</RedwoodProvider>
+ </BugsnagBoundar>
- </FatalErrorBoundary>,
document.getElementById('redwood-app')
)
๋ด๊ฐ ์ฌ์ฉํ๋ ์๋น ๊ตฌ์ฑ ์์๋ ๊ธฐ๋ณธ Redwood FatalErrorBoundary์ด์ง๋ง, ์ฌ๊ธฐ์์ ์์ ์ ๊ตฌ์ฑ ์์๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.process.env
๊ฐ์ ๋ณ์๋ฅผ ์ด๋ป๊ฒ ์ฌ์ฉํ๋์ง ์ฃผ์ํ์ญ์์ค.๊ธฐ๋ณธ์ ์ผ๋ก ํ์ผ(์ ํ!)ํ๊ฒฝ ๋ณ์๋ฅผ ์ ๋ฉด์ ๊ณต๊ฐํ์ง ์์ต๋๋ค.๊ทธ๋์ ํ๋ชฉ์ ๊ณ ์น์.toml์ ์ด๋ฌํ ๋ณ์๋ฅผ ํฌํจํ๋ค
[web]
port = 8910
apiProxyPath = "/.netlify/functions"
+ includeEnvironmentVariables = ['BUGSNAG_NOTIFIER_API_KEY', 'CONTEXT', 'NODE_ENV', 'DEPLOY_ID']
[api]
port = 8911
[browser]
๊ธฐ์ตํด!
๋ง์ง๋ง์ผ๋ก BUGSNAG_NOTIFIER_API_KEY
์ ์ ์ถ๊ฐํ๋ ๊ฒ์ ๊ธฐ์ตํ์ญ์์ค.ํ๊ฒฝ ํ์ผ
๋์โจ! ํ์ฌ ์ฌ์ฉ์๊ฐ ์ด์์ด ๋ฐ์ํ๋ฉด ์๋ฆผ์ ๋ฐ์ ์ ์์ต๋๋ค.ํ์ง๋ง ์๋ฐ์คํฌ๋ฆฝํธ๊ฐ ์ถ์๋์๊ธฐ ๋๋ฌธ์ ๋ณผ ์ ์๋ ๋ก๊ทธ๋ ์์ง ๊ทธ๋ ๊ฒ ์ ์ฉํ์ง ์์ต๋๋ค.์ง๊ธ๊น์ง ์ฐ๋ฆฌ๋ ๋ฌด์จ ์ผ์ด ์ผ์ด๋ฌ๋์ง ์๊ณ ์๋ค. ์ด์ ์
์น ํ์ด์ง ์ค์ ๋ฐ ์๋ณธ ์ง๋ ์
๋ก๋
Bugsnag์ ํจํค์ง ํ๋ฌ๊ทธ์ธ์ ์ฌ์ฉํ์ฌ ์ค์ ํฉ๋๋ค.์ค์น ๋ฐฉ๋ฒ:
yarn workspace web add webpack-bugsnag-plugins
ํ์ผ์ ์ํ ์น ์ค์ ์ ์ฌ์ฉ์ ์ ์ํ๋ ค๋ฉด web/config/webpack.config.js
์ ํ์ผ์ ๋ง๋ค์ด์ผ ํฉ๋๋ค.๋ง์ฝ ๋ค๊ฐ ์ด๋ฏธ ๊ทธ๊ฒ์ ๊ฐ์ง๊ณ ์๋ค๋ฉด, ์ข์, ๊ทธ๊ฒ์ ์ถ๊ฐํ๊ธฐ๋ง ํ๋ฉด ๋๋ค.
/* ==== web/config/webpack.config.js ==== */
// Important, so webpack can use the environment variables
require('dotenv-defaults').config()
const {
BugsnagSourceMapUploaderPlugin,
BugsnagBuildReporterPlugin,
} = require('webpack-bugsnag-plugins')
module.exports = (config) => {
// Check if its building in netlify
// No need to upload source maps when building locally
const netlifyBuild = !!process.env.NETLIFY
const bugsnagPlugins = netlifyBuild
? [
new BugsnagBuildReporterPlugin({
apiKey: process.env.BUGSNAG_NOTIFIER_API_KEY,
appVersion: process.env.DEPLOY_ID,
releaseStage: process.env.CONTEXT || process.env.NODE_ENV,
sourceControl: {
provider: 'github',
repository: process.env.REPOSITORY_URL,
revision: process.env.COMMIT_REF,
},
}),
new BugsnagSourceMapUploaderPlugin({
apiKey: process.env.BUGSNAG_NOTIFIER_API_KEY,
appVersion: process.env.DEPLOY_ID,
}),
]
: []
config.plugins = [...config.plugins, ...bugsnagPlugins]
return config
}
์ฃผ์, ์ฐ๋ฆฌ๊ฐ ์ฌ์ฉํ๋ ๊ฒ์ process.env.NETLIFY
ํ๊ฒฝ ๋ณ์์
๋๋ค.์ด๋ ๊ฒ ํ๋ฉด ์ฐ๋ฆฌ๋ ํ์ง ๋ฒ์ ์ ์๋ณธ ์ง๋๋ฅผ ์
๋ก๋ํ์ง ์์ ๊ฒ์ด๋ค.ํ๊ฒฝ ๋ณ์ REPOSITORY_URL
, COMMIT_REF
, DEPLOY_ID
๊ณผ CONTEXT
์ Netlify์์ ์์ผ๋ฏ๋ก ์ฝ๋๋ฅผ ๋ฐฐ์นํ ์์น์ ๋ฐ๋ผ ์์ ํ์ญ์์ค.
์ค์ ํ์ธ
์ด์ ์ค์ ์ ๊ฒ์ฆํด ๋ณด๊ฒ ์ต๋๋ค.์ถ๊ฐํ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค.
throw new Error('Catch me bugsnag!')
์ ๋ฐฉ ์ฝ๋์ ์ด๋ ๊ณณ์์๋ ์ง, ๊ทธ๊ฒ์ด ์ด๋ฐ๋์์ ๋, ๊ณ๊ธฐํ (๋ฐ ์ ์๋ฉ์ผ) ์์ ๊ทธ๊ฒ์ ๋ณด์์ผ ํ๋ค.๋๋ ๋นต ๋ถ์ค๋ฌ๊ธฐ ๋ผ๋ฒจ์ ํตํด ๋ฌด์จ ์ผ์ด ์ผ์ด๋ฌ๋์ง, ์ ์ผ์ด๋ฌ๋์ง, ๊ทธ๋ฆฌ๊ณ ๊ทธ๊ฒ์ด ์ด๋ป๊ฒ ์ผ์ด๋ฌ๋์ง ๋ณผ ์ ์๋ค.
A note on sourcemaps with Netlify. If you have "optimise JS bundles" enabled on Netlify, they move your bundles to the Cloudfront CDN, and Bugsnag isn't able to match up the sourcemap to the js file.
Netlify UI์์ ์ค์ ์ ํด์ ํ๋ฉด ์คํ ์ถ์ ์ด ์์ ํ ํ์๋ฉ๋๋ค.๋ถํํ๊ฒ๋ Netlify๊ฐ ์
๋ก๋ํ๊ธฐ ์ ์ ํ์ผ์ ์ ์ด๋ฆ์ ๋ฐ๊ฟจ๋์ง, ํธ์คํธ ์ด๋ฆ์ด ๋ฌด์์ธ์ง ๊ณต๊ฐํ์ง ์์๊ธฐ ๋๋ฌธ์ Cloudfront์ ํจ๊ป ์ผํ ์๊ฐ์ ์์ต๋๋ค.
๊ฐ๋ฐ ๋ฐ ์ ํ ๊ตฌ์ฑ
์ข์ต๋๋ค. ์ค๋ฅ๋ฅผ ๋ณด์์ต๋๋ค. dev๋ฅผ ์ฌ์ฉํ์ง ์์ผ๋ ค๊ณ ํฉ๋๋ค. <EnvironmentAwareErrorBoundary>
์ ๋ง๋ญ๋๋ค.
const EnvironmentAwareErrorBoundary = React.memo(({ children, ...otherProps }) => {
if (process.env.NODE_ENV === 'development') {
return (
<FatalErrorBoundary page={FatalErrorBoundary} {...otherProps}>
{children}
</FatalErrorBoundary>
)
} else {
Bugsnag.start({
apiKey: process.env.BUGSNAG_NOTIFIER_API_KEY,
plugins: [new BugsnagPluginReact()],
releaseStage: process.env.CONTEXT || process.env.NODE_ENV,
appVersion: process.env.DEPLOY_ID,
})
const BugsnagBoundary = Bugsnag.getPlugin('react').createErrorBoundary(
React
)
return (
<BugsnagBoundary
FallbackComponent={<FatalErrorBoundary page={FatalErrorPage} />}
{...otherProps}
>
{children}
</BugsnagBoundary>
)
}
})
์ด๋ ๊ฒ ํ๋ ๋ชฉ์ ์ ๊ฐ๋ฐ ์ค์ ๊ธฐ๋ณธ Redwood FatalErrorBoundary๋ฅผ ์ฌ์ฉํ์ง๋ง, ์์ฐ ์ค์ ๋ฒ๊ทธ์คnag์ ์ด์์ ๋ณด๊ณ ํ๋ ๊ฒ์ด๋ค.
๋ค์ ๊ทธ๋ฆผ๊ณผ ๊ฐ์ด ์ด ๊ตฌ์ฑ ์์์ ์์ฉ ํ๋ก๊ทธ๋จ์ ํฌ์ฅํ ์ ์์ต๋๋ค.
ReactDOM.render(
+ <EnvironmentAwareErrorBoundary>
{*/ your other stuff */}
<RedwoodProvider>
<Routes />
</RedwoodProvider>
+ </EnvironmentAwareErrorBoundary>,
document.getElementById('redwood-app')
)
์น์
2: API ๋ฐ Graphql
graphql์ ์ฌ์ฉ์ ์ ์ ํ๋ฌ๊ทธ์ธ ๋ง๋ค๊ธฐ
๋ฐฑ์๋์ ๋ํด, ์ฐ๋ฆฌ๋graphql์์ ์ค๋ฅ๋ฅผ ํฌ์ฐฉํ๊ธฐ๋ฅผ ํฌ๋งํฉ๋๋ค.๋ฐ๋ผ์ Bugsnag ์ฝ๋๋ฅผ ์์ฉํ๊ธฐ ์ํ util ๋ชจ๋์ ๋ง๋๋ ๊ฒ๋ถํฐ ์์ํฉ๋๋ค.
api/src/lib/bugsnag.ํ์ฌ ๋ช
import Bugsnag from '@bugsnag/js'
import { isEmpty } from 'lodash'
Bugsnag.start({
apiKey: process.env.BUGSNAG_SERVER_API_KEY,
releaseStage: process.env.CONTEXT || process.env.NODE_ENV,
appVersion: process.env.DEPLOY_ID,
})
export const reportErrorFromContext = (requestContext) => {
const { errors, metrics, request, context } = requestContext
// Call bugsnag here
// But you could easily use something else here
Bugsnag.notify(new Error(errors), function (event) {
event.severity = 'error'
event.addMetadata('metrics', metrics)
event.addMetadata('errors', errors)
event.addMetadata('query', request)
})
}
export const reportError = (error) => {
Bugsnag.notify(error)
}
๋ง์ถคํ ์ํด๋ก ์๋ฒ ํ๋ฌ๊ทธ์ธ์์ ์ฌ์ฉํ ์ ์๋๋ก reportReportErrorFromContext
์ ๊ณต๊ฐํ์ง๋ง, reportError
์ ๋ค๋ฅธ ๊ณณ์์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์ด์ ํ๋ฌ๊ทธ์ธ์ ๋ง๋ค์ด์ ์๋ฒ ์ค์ ์ ์ถ๊ฐํฉ์๋ค
// === api/src/functions/graphql.js ===
+ import { reportErrorFromContext } from 'src/lib/bugsnag'
+ const bugSnagExceptionPlugin = {
+ requestDidStart() {
+ return {
+ didEncounterErrors(requestContext) {
+ reportErrorFromContext(requestContext)
+ },
+ }
+ },
+ }
export const handler = createGraphQLHandler({
getCurrentUser,
+ plugins: [bugSnagExceptionPlugin],
schema: makeMergedSchema({
schemas,
services: makeServices({ services }),
// ....rest of the file omitted for brevity
์ฌ์ฉ์ ์ ์ ํจ์
์ฐ๋ฆฌ๊ฐ ์ด๋ป๊ฒ reportError
๋ฐฉ๋ฒ์ ๋ง๋ค์๋์ง ๊ธฐ์ตํ์ญ๋๊น?์ด์ ์ฌ์ฉ์ ์ ์ ํจ์์์ ์ฌ์ฉํ ์ ์์ต๋๋ค
์ ํ ๊ตฌ์ฑ
Quick headsup here, Netlifyโs functions annoyingly donโt set the NODE_ENV value at runtime by default. So if you wanted to check if youโre running on prod, youโll have to make sure you set the value in the Netlify UI, but then in your netlify.toml set NODE_ENV to development for the build env (otherwise the build will fail).
์ ๋จ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก, ์ฐ๋ฆฌ๋ ๊ฐ๋ฐ ๊ณผ์ ์์ ๋ก๊ทธ ๊ธฐ๋ก ์ด์์ ์ฌ์ฉํ์ง ์๊ธฐ๋ฅผ ํฌ๋งํฉ๋๋ค. ๋ฐ๋ผ์, ์ฝ๋๋ฅผ ์ผ๋ถif๋ฌธ์ฅ์ ๋ด์ธํ๋ฉด ์์ฑ๋ฉ๋๋ค!์ฐ๋ฆฌ์ ์์์ ์ฐ๋ฆฌ๊ฐ ์ฌ์ฉํ๋ ๊ฒ์ process.env.LOG_EXCEPTIONS
๋ณ์๋ค.
api/src/lib/bugsnag.ํ์ฌ ๋ช
+ if (!isEmpty(process.env.LOG_EXCEPTIONS)) {
Bugsnag.start({
apiKey: process.env.BUGSNAG_SERVER_API_KEY,
releaseStage: process.env.CONTEXT || process.env.NODE_ENV,
appVersion: process.env.DEPLOY_ID,
})
+ }
export const reportReportErrorFromContext = (requestContext) => {
const { errors, metrics, request, context } = requestContext
// Note that netlify doesn't set node_env at runtime in functions
+ if (isEmpty(process.env.LOG_EXCEPTIONS)) {
+ return
+ }
์ด๊ฒ ๋ค์ผ!๐ ๋น์ ์ ์ง๊ธ ์์ฉ ํ๋ก๊ทธ๋จ์ ์์ํ ์ค๋น๊ฐ ๋์ด ์์ต๋๋ค. ์ด์์ ๋ฐ๊ฒฌํ๊ณ ์ถ์ ํ๋ฉฐ ๋ณต๊ตฌํ ์์ ์ด ์์ต๋๋ค. (๋ง์ฝ ๋ฐ์ํ๋ค๋ฉด)
๐๐ฝ PS ๋ Redwood์ ํ๋ ฅํ๋ ๋ด์ฉ์
๋๋ค.
Reference
์ด ๋ฌธ์ ์ ๊ดํ์ฌ(์ด์ ์ถ์ ๐ซ Bugsnag์ Redwood๊ฐ ์์ต๋๋ค.), ์ฐ๋ฆฌ๋ ์ด๊ณณ์์ ๋ ๋ง์ ์๋ฃ๋ฅผ ๋ฐ๊ฒฌํ๊ณ ๋งํฌ๋ฅผ ํด๋ฆญํ์ฌ ๋ณด์๋ค
https://dev.to/dac09/exception-tracking-with-bugsnag-and-redwood-4kk0
ํ
์คํธ๋ฅผ ์์ ๋กญ๊ฒ ๊ณต์ ํ๊ฑฐ๋ ๋ณต์ฌํ ์ ์์ต๋๋ค.ํ์ง๋ง ์ด ๋ฌธ์์ URL์ ์ฐธ์กฐ URL๋ก ๋จ๊ฒจ ๋์ญ์์ค.
์ฐ์ํ ๊ฐ๋ฐ์ ์ฝํ
์ธ ๋ฐ๊ฒฌ์ ์ ๋
(Collection and Share based on the CC Protocol.)
# -W because we'll use it in both web and api
yarn add -W @bugsnag/js
yarn workspace web add @bugsnag/plugin-react
+ import Bugsnag from '@bugsnag/js'
+ import BugsnagPluginReact from '@bugsnag/plugin-react'
+ Bugsnag.start({
+ apiKey: process.env.BUGSNAG_NOTIFIER_API_KEY,
+ plugins: [new BugsnagPluginReact()],
+ releaseStage: process.env.CONTEXT || process.env.NODE_ENV,
+ appVersion: process.env.DEPLOY_ID,
+ })
+ const BugsnagBoundary = Bugsnag.getPlugin('react').createErrorBoundary(React)
ReactDOM.render(
- <FatalErrorBoundary page={FatalErrorPage}>
+ <BugsnagBoundary
+ FallbackComponent={<FatalErrorBoundary page={FatalErrorPage} />}
+ >
<RedwoodProvider>
<Routes />
</RedwoodProvider>
+ </BugsnagBoundar>
- </FatalErrorBoundary>,
document.getElementById('redwood-app')
)
[web]
port = 8910
apiProxyPath = "/.netlify/functions"
+ includeEnvironmentVariables = ['BUGSNAG_NOTIFIER_API_KEY', 'CONTEXT', 'NODE_ENV', 'DEPLOY_ID']
[api]
port = 8911
[browser]
yarn workspace web add webpack-bugsnag-plugins
/* ==== web/config/webpack.config.js ==== */
// Important, so webpack can use the environment variables
require('dotenv-defaults').config()
const {
BugsnagSourceMapUploaderPlugin,
BugsnagBuildReporterPlugin,
} = require('webpack-bugsnag-plugins')
module.exports = (config) => {
// Check if its building in netlify
// No need to upload source maps when building locally
const netlifyBuild = !!process.env.NETLIFY
const bugsnagPlugins = netlifyBuild
? [
new BugsnagBuildReporterPlugin({
apiKey: process.env.BUGSNAG_NOTIFIER_API_KEY,
appVersion: process.env.DEPLOY_ID,
releaseStage: process.env.CONTEXT || process.env.NODE_ENV,
sourceControl: {
provider: 'github',
repository: process.env.REPOSITORY_URL,
revision: process.env.COMMIT_REF,
},
}),
new BugsnagSourceMapUploaderPlugin({
apiKey: process.env.BUGSNAG_NOTIFIER_API_KEY,
appVersion: process.env.DEPLOY_ID,
}),
]
: []
config.plugins = [...config.plugins, ...bugsnagPlugins]
return config
}
throw new Error('Catch me bugsnag!')
A note on sourcemaps with Netlify. If you have "optimise JS bundles" enabled on Netlify, they move your bundles to the Cloudfront CDN, and Bugsnag isn't able to match up the sourcemap to the js file.
const EnvironmentAwareErrorBoundary = React.memo(({ children, ...otherProps }) => {
if (process.env.NODE_ENV === 'development') {
return (
<FatalErrorBoundary page={FatalErrorBoundary} {...otherProps}>
{children}
</FatalErrorBoundary>
)
} else {
Bugsnag.start({
apiKey: process.env.BUGSNAG_NOTIFIER_API_KEY,
plugins: [new BugsnagPluginReact()],
releaseStage: process.env.CONTEXT || process.env.NODE_ENV,
appVersion: process.env.DEPLOY_ID,
})
const BugsnagBoundary = Bugsnag.getPlugin('react').createErrorBoundary(
React
)
return (
<BugsnagBoundary
FallbackComponent={<FatalErrorBoundary page={FatalErrorPage} />}
{...otherProps}
>
{children}
</BugsnagBoundary>
)
}
})
ReactDOM.render(
+ <EnvironmentAwareErrorBoundary>
{*/ your other stuff */}
<RedwoodProvider>
<Routes />
</RedwoodProvider>
+ </EnvironmentAwareErrorBoundary>,
document.getElementById('redwood-app')
)
graphql์ ์ฌ์ฉ์ ์ ์ ํ๋ฌ๊ทธ์ธ ๋ง๋ค๊ธฐ
๋ฐฑ์๋์ ๋ํด, ์ฐ๋ฆฌ๋graphql์์ ์ค๋ฅ๋ฅผ ํฌ์ฐฉํ๊ธฐ๋ฅผ ํฌ๋งํฉ๋๋ค.๋ฐ๋ผ์ Bugsnag ์ฝ๋๋ฅผ ์์ฉํ๊ธฐ ์ํ util ๋ชจ๋์ ๋ง๋๋ ๊ฒ๋ถํฐ ์์ํฉ๋๋ค.
api/src/lib/bugsnag.ํ์ฌ ๋ช
import Bugsnag from '@bugsnag/js'
import { isEmpty } from 'lodash'
Bugsnag.start({
apiKey: process.env.BUGSNAG_SERVER_API_KEY,
releaseStage: process.env.CONTEXT || process.env.NODE_ENV,
appVersion: process.env.DEPLOY_ID,
})
export const reportErrorFromContext = (requestContext) => {
const { errors, metrics, request, context } = requestContext
// Call bugsnag here
// But you could easily use something else here
Bugsnag.notify(new Error(errors), function (event) {
event.severity = 'error'
event.addMetadata('metrics', metrics)
event.addMetadata('errors', errors)
event.addMetadata('query', request)
})
}
export const reportError = (error) => {
Bugsnag.notify(error)
}
๋ง์ถคํ ์ํด๋ก ์๋ฒ ํ๋ฌ๊ทธ์ธ์์ ์ฌ์ฉํ ์ ์๋๋ก reportReportErrorFromContext
์ ๊ณต๊ฐํ์ง๋ง, reportError
์ ๋ค๋ฅธ ๊ณณ์์ ์ฌ์ฉํ ์ ์์ต๋๋ค.์ด์ ํ๋ฌ๊ทธ์ธ์ ๋ง๋ค์ด์ ์๋ฒ ์ค์ ์ ์ถ๊ฐํฉ์๋ค
// === api/src/functions/graphql.js ===
+ import { reportErrorFromContext } from 'src/lib/bugsnag'
+ const bugSnagExceptionPlugin = {
+ requestDidStart() {
+ return {
+ didEncounterErrors(requestContext) {
+ reportErrorFromContext(requestContext)
+ },
+ }
+ },
+ }
export const handler = createGraphQLHandler({
getCurrentUser,
+ plugins: [bugSnagExceptionPlugin],
schema: makeMergedSchema({
schemas,
services: makeServices({ services }),
// ....rest of the file omitted for brevity
์ฌ์ฉ์ ์ ์ ํจ์
์ฐ๋ฆฌ๊ฐ ์ด๋ป๊ฒ
reportError
๋ฐฉ๋ฒ์ ๋ง๋ค์๋์ง ๊ธฐ์ตํ์ญ๋๊น?์ด์ ์ฌ์ฉ์ ์ ์ ํจ์์์ ์ฌ์ฉํ ์ ์์ต๋๋ค์ ํ ๊ตฌ์ฑ
Quick headsup here, Netlifyโs functions annoyingly donโt set the NODE_ENV value at runtime by default. So if you wanted to check if youโre running on prod, youโll have to make sure you set the value in the Netlify UI, but then in your netlify.toml set NODE_ENV to development for the build env (otherwise the build will fail).
์ ๋จ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก, ์ฐ๋ฆฌ๋ ๊ฐ๋ฐ ๊ณผ์ ์์ ๋ก๊ทธ ๊ธฐ๋ก ์ด์์ ์ฌ์ฉํ์ง ์๊ธฐ๋ฅผ ํฌ๋งํฉ๋๋ค. ๋ฐ๋ผ์, ์ฝ๋๋ฅผ ์ผ๋ถif๋ฌธ์ฅ์ ๋ด์ธํ๋ฉด ์์ฑ๋ฉ๋๋ค!์ฐ๋ฆฌ์ ์์์ ์ฐ๋ฆฌ๊ฐ ์ฌ์ฉํ๋ ๊ฒ์
process.env.LOG_EXCEPTIONS
๋ณ์๋ค.api/src/lib/bugsnag.ํ์ฌ ๋ช
+ if (!isEmpty(process.env.LOG_EXCEPTIONS)) {
Bugsnag.start({
apiKey: process.env.BUGSNAG_SERVER_API_KEY,
releaseStage: process.env.CONTEXT || process.env.NODE_ENV,
appVersion: process.env.DEPLOY_ID,
})
+ }
export const reportReportErrorFromContext = (requestContext) => {
const { errors, metrics, request, context } = requestContext
// Note that netlify doesn't set node_env at runtime in functions
+ if (isEmpty(process.env.LOG_EXCEPTIONS)) {
+ return
+ }
์ด๊ฒ ๋ค์ผ!๐ ๋น์ ์ ์ง๊ธ ์์ฉ ํ๋ก๊ทธ๋จ์ ์์ํ ์ค๋น๊ฐ ๋์ด ์์ต๋๋ค. ์ด์์ ๋ฐ๊ฒฌํ๊ณ ์ถ์ ํ๋ฉฐ ๋ณต๊ตฌํ ์์ ์ด ์์ต๋๋ค. (๋ง์ฝ ๋ฐ์ํ๋ค๋ฉด)๐๐ฝ PS ๋ Redwood์ ํ๋ ฅํ๋ ๋ด์ฉ์ ๋๋ค.
Reference
์ด ๋ฌธ์ ์ ๊ดํ์ฌ(์ด์ ์ถ์ ๐ซ Bugsnag์ Redwood๊ฐ ์์ต๋๋ค.), ์ฐ๋ฆฌ๋ ์ด๊ณณ์์ ๋ ๋ง์ ์๋ฃ๋ฅผ ๋ฐ๊ฒฌํ๊ณ ๋งํฌ๋ฅผ ํด๋ฆญํ์ฌ ๋ณด์๋ค https://dev.to/dac09/exception-tracking-with-bugsnag-and-redwood-4kk0ํ ์คํธ๋ฅผ ์์ ๋กญ๊ฒ ๊ณต์ ํ๊ฑฐ๋ ๋ณต์ฌํ ์ ์์ต๋๋ค.ํ์ง๋ง ์ด ๋ฌธ์์ URL์ ์ฐธ์กฐ URL๋ก ๋จ๊ฒจ ๋์ญ์์ค.
์ฐ์ํ ๊ฐ๋ฐ์ ์ฝํ ์ธ ๋ฐ๊ฒฌ์ ์ ๋ (Collection and Share based on the CC Protocol.)
์ข์ ์นํ์ด์ง ์ฆ๊ฒจ์ฐพ๊ธฐ
๊ฐ๋ฐ์ ์ฐ์ ์ฌ์ดํธ ์์ง
๊ฐ๋ฐ์๊ฐ ์์์ผ ํ ํ์ ์ฌ์ดํธ 100์ ์ถ์ฒ ์ฐ๋ฆฌ๋ ๋น์ ์ ์ํด 100๊ฐ์ ์์ฃผ ์ฌ์ฉํ๋ ๊ฐ๋ฐ์ ํ์ต ์ฌ์ดํธ๋ฅผ ์ ๋ฆฌํ์ต๋๋ค