์ด์ƒ ์ถ”์ ๐Ÿšซ Bugsnag์™€ Redwood๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

22363 ๋‹จ์–ด serverlesswebdevjavascript
๊ทธ๋ž˜์„œ ๋‹น์‹ ์€ ์ด๋ฏธ ๋‹น์‹ ์˜ ํ™๋ชฉ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์„ ๊ฐœ๋ฐœํ•˜์—ฌ ์ „๋ฉด์ ์ธ ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๊ณ  ์ถœ์‹œํ•  ์ค€๋น„๋ฅผ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.๋„ˆ, ์œ ํ–‰ํ•˜๋Š” ์ˆ˜์—ผ์„ ๊ธฐ๋ฅด๊ณ  ์˜คํŠธ๋ฐ€ ์ –์„ ์ž…๊ณ โ˜•๏ธ, ๋‹น์‹ ์€ ํ›Œ๋ฅญํ•œ ๊ฐœ๋ฐœ์ž์ด๊ธฐ ๋•Œ๋ฌธ์— ์ฝ”๋“œ์— ๋ฒ„๊ทธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค......์•„๋‹ˆ๋ฉด ์žˆ์–ด์š”?
๋‹น์—ฐํžˆ ํ•ซํ•œ ์ธ๋ฌผ์ด ์žˆ์ฃ .๐Ÿ›‘! ์ด๊ฒƒ์€ ๋‹น์‹ ์ด ๊ณ ๋ คํ•˜์ง€ ์•Š์€ ๊ฐ€์žฅ์ž๋ฆฌ ์ƒํ™ฉ์ผ ์ˆ˜๋„ ์žˆ๊ณ , ๋‹น์‹ ์˜ ์ธํ”„๋ผ ์‹œ์„ค ์ค‘์˜ ์–ด๋–ค ๊ฒƒ์ผ ์ˆ˜๋„ ์žˆ๊ณ , ๋งค์šฐ ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ์ œ3์ž ์„œ๋น„์Šค๊ฐ€ ์•„๋‹ ์ˆ˜๋„ ์žˆ๊ณ , ์‚ฌ์šฉ์ž๊ฐ€ ๋‹น์‹ ์ด ๊ฐ€์žฅ ๊ธฐ๋Œ€ํ•˜์ง€ ์•Š๋Š” ์ผ์„ ํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ๋“ค์€ ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ ์˜ค๋ฅ˜๋ฅผ ๋งŒ๋‚ฌ์„ ์ˆ˜๋„ ์žˆ๋‹ค.
์ƒ์‚ฐ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์— ๋Œ€ํ•ด ๋‹น์‹ ์˜ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์ด ์‚ฌ์šฉ์ž๋ฅผ ๊ธฐ์˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋ฌธ์ œ๋ฅผ ์‹ ์†ํ•˜๊ฒŒ ์ง„๋‹จํ•˜๊ณ  ๊ฐ€๋Šฅํ•œ ํ•œ ๋นจ๋ฆฌ ๋ณต๊ตฌ ๋ฐฉ์•ˆ์„ ์ œ์‹œํ•ด์•ผ ํ•œ๋‹ค.๋ฌธ์ œ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋Š” ์ข‹์€ ๋กœ๊ทธ๊ฐ€ ํ•„์š”ํ•˜๋ฉฐ, ๋„๊ตฌ๊ฐ€ ๋งˆ์ฐฐ์„ ์ตœ๋Œ€ํ•œ ์ค„์ผ ์ˆ˜ ์žˆ๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.๊ธฐ์—…๊ณ„์—์„œ๋Š” ํ‰๊ท  ๋ณต๊ตฌ ์‹œ๊ฐ„(MTTR)์„ ์‚ฌ์šฉํ•˜์—ฌ ํ‰๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ„๋‹จํžˆ ๋งํ•˜๋ฉด ๊ฐ€๋Šฅํ•œ ํ•œ ๋นจ๋ฆฌ ๋ณต๊ตฌํ•˜๊ณ  ๋ฐœ์†กํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.
  • Introduction
  • Tldr;

  • Part 1: Frontend
  • Setting up exception handler
  • Webpack setup & uploading source maps
  • Validate your setup
  • Dev & Prod configuration

  • Part 2: API and Graphql
  • Create custom plugin for graphql
  • Custom functions
  • Prod configuration
  • ์†Œ๊ฐœ


    ์ด ๊ฐ•์ขŒ๋Š” ๋ฒ„๊ทธ์Šค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์™€ ํ˜‘๋ ฅํ•˜๋Š” ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.


    ์ข‹์€ ์›นํŽ˜์ด์ง€ ์ฆ๊ฒจ์ฐพ๊ธฐ