Next.js: ์„œ๋ฒ„ ์ธก์—์„œ ๋กœ๊ทธ๋ฅผ ๋ณด๊ณ ํ•˜๊ธฐ ์œ„ํ•ด ์˜ค๋ฅ˜๋ฅผ ํฌ์ฐฉํ•˜๊ณ  ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•

์— ๋งŒ๋“  2017๋…„ 05์›” 02์ผ  ยท  74์ฝ”๋ฉ˜ํŠธ  ยท  ์ถœ์ฒ˜: vercel/next.js

์•ˆ๋…•,
์ €๋Š” ์„œ๋ฒ„ ์ธก๊ณผ ํด๋ผ์ด์–ธํŠธ ์ธก ๋ชจ๋‘์—์„œ Sentry ๋„๊ตฌ๋กœ ์˜ค๋ฅ˜๋ฅผ ๋ณด๋‚ด๊ณ  ์‹ถ์€ ์ƒํ™ฉ์— ์žˆ์Šต๋‹ˆ๋‹ค.

์šฐ๋ฆฌ ์•ฑ์€ Express๋ฅผ ์ปค์Šคํ…€ ์„œ๋ฒ„๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ์ต์Šคํ”„๋ ˆ์Šค ์•ฑ์„ ๋งŒ๋“ค๊ณ  ์ผ๋ถ€ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ ์šฉํ•˜์ง€๋งŒ ๋ชจ๋“  ์‹ค์ œ ์ž‘์—…์„ next.js ํ•ธ๋“ค์— ์œ„์ž„ํ•ฉ๋‹ˆ๋‹ค.

  const app = nextJs({ dev: process.env.NODE_ENV !== 'production' });
  const handler = routes.getRequestHandler(app);
  const expressApp = express();

  ...
  ...

  expressApp.use(morgan('combined', { stream: logger.stream }));
  expressApp.use(statsdMiddleware);

  // Add security
  expressApp.use(helmet());

  // Sentry handler
  expressApp.use(sentry.requestHandler());

  // Load locale and translation messages
  expressApp.use(i18n);

  // Next.js handler
  expressApp.use(handler);

  // Sentry error handler.
  // MUST be placed before any other express error handler !!!
  expressApp.use(sentry.errorHandler());

์ด ์ ‘๊ทผ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋ฉด next.js๊ฐ€ ๋ Œ๋”๋ง ํ”„๋กœ์„ธ์Šค๋ฅผ ์ œ์–ดํ•˜๊ณ  ๋ชจ๋“  ์˜ค๋ฅ˜๋Š” next.js์— ์˜ํ•ด catch๋˜๋ฉฐ ๋‚ด๊ฐ€ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๋Š” ์œ ์ผํ•œ ๋ฐฉ๋ฒ•์€ _error.js ํŽ˜์ด์ง€ ํŒŒ์ผ์„ ์žฌ์ •์˜ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๊ทธ _error.js ํŒŒ์ผ ๋‚ด์—์„œ Sentry์— ์˜ค๋ฅ˜๋ฅผ ๋ณด๊ณ ํ•˜๋Š” ๋ณดํŽธ์ ์ธ ๋ฐฉ๋ฒ•์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ ๋‘ ๊ฐœ์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(๋…ธ๋“œ์˜ ๊ฒฝ์šฐ raven ๋ฐ ์ž๋ฐ” ์Šคํฌ๋ฆฝํŠธ์˜ ๊ฒฝ์šฐ raven-js )๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฌธ์ œ๋Š” raven ๊ฐ€ SSR์—์„œ ์ž‘๋™ํ•˜์ง€๋งŒ webpack์ด ๋ฒˆ๋“ค์„ ๋นŒ๋“œํ•  ๋•Œ ์‹คํŒจํ•˜๊ณ  XMLHTTPRequest ์ข…์†์„ฑ์œผ๋กœ ์ธํ•ด raven-js ์‹คํŒจํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋‘˜ ๋‹ค ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์„œ๋ฒ„ ์ธก์—์„œ next.js ์˜ค๋ฅ˜๋ฅผ ์•Œ๋ฆด ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๊นŒ?

story feature request

๊ฐ€์žฅ ์œ ์šฉํ•œ ๋Œ“๊ธ€

_unortodox_ ์†”๋ฃจ์…˜์„ ์‚ฌ๋ž‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

function installErrorHandler(app) {
  const _renderErrorToHTML = app.renderErrorToHTML.bind(app)
  const errorHandler = rollbar.errorHandler()

  app.renderErrorToHTML = (err, req, res, pathname, query) => {
    if (err) {
      errorHandler(err, req, res, () => {})
    }

    return _renderErrorToHTML(err, req, res, pathname, query)
  }

  return app
}
// ยฏ\_(ใƒ„)_/ยฏ

๋ชจ๋“  74 ๋Œ“๊ธ€

ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ์˜ค๋ฅ˜๋ฅผ ๊ธฐ๋กํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค.
https://gist.github.com/jgautheron/044b88307d934d486f59ae87c5a5a5a0

๊ธฐ๋ณธ์ ์œผ๋กœ stdout ์ธ์‡„๋˜๊ณ  Docker ๋กœ๊น… ๋“œ๋ผ์ด๋ฒ„์— ์˜ํ•ด ํฌ์ฐฉ๋˜๋Š” ์˜ค๋ฅ˜๋ฅผ ์„œ๋ฒ„์— ๋ณด๋ƒ…๋‹ˆ๋‹ค.
ํ•ต์‹ฌ Next ํŒŒ์ผ์„ ์žฌ์ •์˜ํ•  ํ•„์š” ์—†์ด react-guard ๋กœ SSR ์˜ค๋ฅ˜๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ํฌ์ฐฉํ–ˆ์Šต๋‹ˆ๋‹ค.

์ €๋„ ์ด ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์˜ handleRequest ์—์„œ ๊ฑฐ๋ถ€๋œ ํ”„๋ผ๋ฏธ์Šค๋ฅผ ๋‹จ์ˆœํžˆ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ์ถฉ๋ถ„ํ• ๊นŒ์š”? ์ฆ‰, ์—ฌ๊ธฐ ์—์„œ ์ฝ”๋“œ๋ฅผ

handleRequest (req, res, parsedUrl) {
  // .....snip....

   return this.run(req, res, parsedUrl)
     .catch((err) => {
       if (!this.quiet) console.error(err)
       res.statusCode = 500
       res.end(STATUS_CODES[500])

       // rethrow error to create new, rejected promise 
       throw err;
     })
}

๊ทธ๋Ÿฐ ๋‹ค์Œ ์‚ฌ์šฉ์ž ์ฝ”๋“œ์—์„œ:

const app = nextJs({ dev: process.env.NODE_ENV !== 'production' });
const nextJsHandler = app.getRequestHandler();
const expressApp = express();

app.prepare().then(() => {
   // invoke express middlewares
   // ...

   // time to run next
   expressApp.use(function(req, res, next) {
     nextJsHandler(req, res).catch(e => {
       // use rejected promise to forward error to next express middleware
       next(e)
     })
   });

   // Use standard express error middlewares to handle the error from next
   // this makes it easy to log to sentry etc.
})

@arunoda @rauchg ๋ฐ”๋กœ ์œ„์—์„œ ์ œ์•ˆํ•œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ํšจ๊ณผ๊ฐ€ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•˜์‹ญ๋‹ˆ๊นŒ? ๊ทธ๋ ‡๋‹ค๋ฉด ๊ธฐ๊บผ์ด PR์„ ์ œ์ถœํ•˜์‹ญ์‹œ์˜ค.

์šฐ๋ฆฌ๊ฐ€ ๊ทธ๊ฒƒ์„ ๊ฐ€์ง€๊ณ  ๋†€ ์ˆ˜ ์žˆ๋„๋ก ์˜ค๋ฅ˜๋ฅผ ๋‹ค์‹œ ๋˜์ง€๋Š” ๋ฐ ๋™์˜ํ•˜์‹ญ์‹œ์˜ค.
๋˜ํ•œ renderToHTML ์—์„œ ๋‹ค์‹œ ๋˜์ ธ์•ผ ํ•ฉ๋‹ˆ๋‹ค...

๋‚˜๋Š” ๋˜ํ•œ ์„œ๋ฒ„ ์ธก๊ณผ ํด๋ผ์ด์–ธํŠธ ์ธก ๋ชจ๋‘์—์„œ Sentry์™€ ์œ ์‚ฌํ•œ ์„œ๋น„์Šค์— ์˜ค๋ฅ˜๋ฅผ ๋ณด๋‚ด๊ณ  ์‹ถ์€ ์ƒํ™ฉ์— ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ์„œ๋น„์Šค/๋„๊ตฌ์˜ ๊ฐ€์žฅ ๊ฐ€์น˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์€ ๋‚ด ๊ฒฝํ—˜์ƒ ์•ผ์ƒ(์ฆ‰, ํด๋ผ์ด์–ธํŠธ ์ธก)์—์„œ ๋ฐœ๊ฒฌ๋˜์ง€ ์•Š์€ ์˜ค๋ฅ˜์ธ ๊ฐ€์žฅ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋ฌธ์ œ๋ฅผ ๋ณด๊ณ ํ•˜๋Š” ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๋ถˆํ–‰ํžˆ๋„ ์ด์ „์— ๊ด€๋ จ ๋ฌธ์ œ #2334์—์„œ ๊ฐ•์กฐํ–ˆ๋“ฏ์ด Next.js์˜ ํด๋ผ์ด์–ธํŠธ ์ธก ํ•ธ๋“ค๋Ÿฌ๋Š” ์ด๋Ÿฌํ•œ ์˜ค๋ฅ˜๋ฅผ ๋‹ค๋ฅธ ๋„๊ตฌ์˜ Sentry์— ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ• ์—†์ด ์ด๋Ÿฌํ•œ ์˜ค๋ฅ˜๋ฅผ ์ž์ฒด์ ์œผ๋กœ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.

์šฐ๋ฆฌ๋ฅผ ํŠนํžˆ ํž˜๋“ค๊ฒŒ ํ•œ ๊ฒƒ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. React๊ฐ€ ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ๋ Œ๋”๋งํ•˜๊ธฐ ์ „์— ์žกํžˆ์ง€ ์•Š์€ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ œ๋Œ€๋กœ ์„œ๋ฒ„ ์ธก์—์„œ ๋ Œ๋”๋ง๋œ ํŽ˜์ด์ง€๊ฐ€ ์˜ค๋ฅ˜ ํŽ˜์ด์ง€๋กœ ๋‹ค์‹œ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค . ์ด๊ฒƒ์€ ํ›Œ๋ฅญํ•œ ๊ธฐ๋Šฅ์œผ๋กœ ๋ณผ ์ˆ˜ ์žˆ์ง€๋งŒ ๋†€๋ž๊ฒŒ๋„ ํด๋ผ์ด์–ธํŠธ์˜ ์ผ๋ถ€์—์„œ ์ด๋ฏธ ๋ Œ๋”๋ง๋œ ๋ฌธ์„œ๋ฅผ ์ œ๊ณตํ•˜๋Š” ์ด์ ์„ ๊ทผ๋ณธ์ ์œผ๋กœ ๋ง์น˜๊ธฐ ๋•Œ๋ฌธ์— ์‹ค๋ง์Šค๋Ÿฌ์šด ๊ฐœ๋ฐœ์ž ๊ฒฝํ—˜์œผ๋กœ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ์€ ๋ฌธ์ œ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ์ƒ˜ํ”Œ ํŽ˜์ด์ง€์ž…๋‹ˆ๋‹ค.

import React from 'react';

// Works well in Node 8, but crashes in Chrome<56, Firefox<48, Edge<15, Safari<10, any IEโ€ฆ
const whoops = 'Phew, I made it to the client-sideโ€ฆ'.padEnd(80);

export default () => <pre>{whoops}</pre>;

์œ„์˜ ์ฝ”๋“œ๋Š” ์„œ๋ฒ„ ์ธก์—์„œ ์™„๋ฒฝํ•˜๊ฒŒ ๋ Œ๋”๋ง๋˜์–ด ํด๋ผ์ด์–ธํŠธ์— ์ „๋‹ฌ๋  ์ˆ˜ ์žˆ์ง€๋งŒ, ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ์ „์ฒด ํŽ˜์ด์ง€๊ฐ€ ๋Œ€๋ถ€๋ถ„์˜ ๋ธŒ๋ผ์šฐ์ €์—์„œ "์˜ˆ๊ธฐ์น˜ ์•Š์€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค"๋ผ๋Š” ๋‘๋ ค์šด ๋ฉ”์‹œ์ง€๋กœ ๋Œ€์ฒด๋˜๊ธฐ ์ „์— ์›ํ•˜์ง€ ์•Š๋Š” ์ฝ˜ํ…์ธ  ์˜ Sentry (๋˜๋Š” ๋‹ค๋ฅธ ์„œ๋น„์Šค/๋„๊ตฌ)์—

์ด "์˜ค๋ฅ˜ ์‚ผํ‚ค๊ธฐ"๋Š” ๋˜ํ•œ ๋Œ€๋ถ€๋ถ„์˜ ํด๋ผ์ด์–ธํŠธ ์ธก ์˜ค๋ฅ˜ ๋ณด๊ณ  ๋„๊ตฌ๊ฐ€ ์—ฐ๊ฒฐํ•˜๋Š” ํ‘œ์ค€ onerror ํ•ธ๋“ค๋Ÿฌ(๋˜๋Š” ๋” ํ˜„๋Œ€์ ์ด์ง€๋งŒ ๋„๋ฆฌ ํผ์ ธ ์žˆ์ง€ ์•Š์€ onunhandledrejection , ํด๋ผ์ด์–ธํŠธ ์ธก์˜ ๋น„๋™๊ธฐ ํŠน์„ฑ์„ ๊ณ ๋ คํ•  ๋•Œ ๋” ์ ํ•ฉํ•  ์ˆ˜ ์žˆ์Œ)์˜ ํ™œ์šฉ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค. ์•”ํ˜ธ).

๊ฐ€๋Šฅํ•œ ํด๋ผ์ด์–ธํŠธ ์ธก ์†”๋ฃจ์…˜

๋‚ด๊ฐ€ ๋งํ•  ์ˆ˜ ์žˆ๋Š” ํ•œ, ์ด๋Ÿฌํ•œ ์œ ํ˜•์˜ pre-React ํด๋ผ์ด์–ธํŠธ ์ธก ์˜ค๋ฅ˜๋Š” Next.js์˜ client/index.js ์— ์žˆ๋Š” try / catch ๋ธ”๋ก์—์„œ ์‚ผ์ผœ์ง‘๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ Component ๋ Œ๋”๋ง๋˜๊ธฐ์ด ๊ฐ’์„ ์žฌ ํ• ๋‹น ErrorComponent (ํ˜„์žฌ ๋ผ์ธ 67-72 ).

Next.js ์ž‘์„ฑ์ž ๋ฐ ์œ ์ง€ ๊ด€๋ฆฌ์ž์—๊ฒŒ, ๋ Œ๋”๋ง๋˜๋Š” ๋‚ด์šฉ์„ ์ œ์–ดํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ ์•„์ด๋””์–ด ์ค‘ ์–ด๋–ค ๊ฒƒ์ด ํ—ˆ์šฉ/๊ฐ€๋Šฅํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•˜์‹ญ๋‹ˆ๊นŒ?

  • ์ด๋Ÿฐ ์ข…๋ฅ˜์˜ ์˜ค๋ฅ˜๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด client/index.js์˜ catch ๋ธ”๋ก์— ํ›„ํฌ๋ฅผ ๋„์ž…ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?
  • ๊ฐ์ง€๋œ ๊ฒฝ์šฐ ์˜ค๋ฅ˜๋ฅผ onerror/onunhandledrejection ์ฒ˜๋ฆฌ๊ธฐ๋กœ ์ „์†กํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?
  • ์˜ค๋ฅ˜๋ฅผ ๋‹ค์‹œ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ์˜ต์…˜์„ ์ œ๊ณตํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?
  • ์˜ค๋ฅ˜ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ํ‘œ์‹œํ•˜์ง€ ์•Š๋Š” ์˜ต์…˜์„ ์ œ๊ณตํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?
  • ์‚ฌ์šฉ์ž ์ง€์ • ์˜ค๋ฅ˜ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ํ‘œ์‹œํ•˜๋Š” ์˜ต์…˜์„ ์ œ๊ณตํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?

์ด๋Ÿฐ ์ข…๋ฅ˜์˜ ์˜ค๋ฅ˜๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด client/index.js์˜ catch ๋ธ”๋ก์— ํ›„ํฌ๋ฅผ ๋„์ž…ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?
๊ฐ์ง€๋œ ๊ฒฝ์šฐ ์˜ค๋ฅ˜๋ฅผ onerror/onunhandledrejection ์ฒ˜๋ฆฌ๊ธฐ๋กœ ์ „์†กํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?

๋‚ด๋ถ€์ ์œผ๋กœ ์ด์•ผ๊ธฐํ•œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ณง ๋‹ค๋ฃฐ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ €๋Š” Sentry.io๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์˜ค๋ฅ˜๋ฅผ ๋ณด๊ณ ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ ์šฐ๋ฆฌ๊ฐ€ ์ ์šฉํ•˜๋Š” ์†”๋ฃจ์…˜์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

1- ์„œ๋ฒ„ ์ธก์—์„œ raven-node ๊ตฌ์„ฑ
2- ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ravenjs๋ฅผ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค( _document ์—์„œ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

์ด ๋‘ ๋‹จ๊ณ„๋ฅผ ํ†ตํ•ด ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ๋ชจ๋‘์—์„œ ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ ์˜ˆ์™ธ๋ฅผ ํฌ์ฐฉํ•ฉ๋‹ˆ๋‹ค.

3- _error ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. nextjs๊ฐ€ ํ•ด๋‹น ํŽ˜์ด์ง€๊ฐ€ ๋ Œ๋”๋ง๋˜๋Š” ์š”์ฒญ(ํด๋ผ์ด์–ธํŠธ ์ธก์ด๋“  ์„œ๋ฒ„ ์ธก์ด๋“  ์ƒ๊ด€์—†์ด)์„ ์ฒ˜๋ฆฌํ•˜๋ฉด ์ƒ์„ฑ๋˜๋Š” ๋ชจ๋“  ์˜ค๋ฅ˜์ž…๋‹ˆ๋‹ค. _error ํŽ˜์ด์ง€์˜ getInitialProps ๋ฉ”์†Œ๋“œ์—์„œ ์šฐ๋ฆฌ๋Š” sentry์— ์˜ค๋ฅ˜๋ฅผ ๋ณด๊ณ ํ•ฉ๋‹ˆ๋‹ค.

raven-node ๋˜๋Š” ravenjs๊ฐ€ ํด๋ผ์ด์–ธํŠธ const Raven = require('raven-js'); ๋˜๋Š” ์„œ๋ฒ„ ์ธก const Raven = require('raven'); ์ธ์ง€์— ๋”ฐ๋ผ ๋™์ ์œผ๋กœ ๊ฐ€์ ธ์˜ค๊ธฐ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๊ฒฝ์šฐ ๋กœ๋“œ ๋ฐฉ๋ฒ•์„ ๊ฒฐ์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.
next.config.js ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” raven ๋ชจ๋“ˆ(์„œ๋ฒ„ ์ธก ๋ชจ๋“ˆ)์„ ๋‹ค์Œ๊ณผ ํ•จ๊ป˜ ๋ฒˆ๋“คํ•˜์ง€ ์•Š๋„๋ก webpack์„ ๊ตฌ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

const webpack = require('webpack');

module.exports = {
  // Do not show the X-Powered-By header in the responses
  poweredByHeader: false,
  webpack: (config) => {
    config.plugins.push(new webpack.IgnorePlugin(/^raven$/));
    return config;
  },
};

@์บ๋‹ˆ๋ฉ€

2- ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ravenjs๋ฅผ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค(_document.js์—์„œ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

_document.js ์—์„œ raven-js๋ฅผ ์–ด๋–ป๊ฒŒ ๊ตฌ์„ฑํ–ˆ๋Š”์ง€ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ? ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์„ผํŠธ๋ฆฌ์—์„œ ์•„๋ฌด ์ผ๋„ ์ผ์–ด๋‚˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

_error.js ํŽ˜์ด์ง€์˜ ๋ชจ๋“  ์˜ค๋ฅ˜๋ฅผ ์ˆ˜๋™์œผ๋กœ ์„ผํŠธ๋ฆฌ๋กœ ๋ณด๋‚ด์•ผ ํ•ฉ๋‹ˆ๊นŒ?

// _document constructor
constructor(props) {
    super(props);
    Raven
      .config('...')
      .install();
}

Next.js๋Š” ์กฐ์šฉํžˆ ์„ค์ •ํ•˜์ง€ ์•Š๋Š” ํ•œ ์„œ๋ฒ„์˜ console.error ์— ์—ฌ์ „ํžˆ ์˜ค๋ฅ˜๋ฅผ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.

Sentry๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด autoBreadcrumbs ๋ฅผ ํ™œ์„ฑํ™” ํ•˜์—ฌ ์ด ์ถœ๋ ฅ ์„ ์บก์ฒ˜ํ•œ ๋‹ค์Œ ์ž์‹ ์˜ ๋ฉ”์‹œ์ง€๋ฅผ ์ˆ˜๋™์œผ๋กœ ์บก์ฒ˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ œ๋ชฉ์€ ๋œ ์„ค๋ช…์ ์ด์ง€๋งŒ ์—ฌ์ „ํžˆ ์ „์ฒด ์Šคํƒ ์ถ”์ ์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

๊ตฌํ˜„ ์˜ˆ:

const express = require('express');
const nextjs = require('next');
const Raven = require('raven');

const dev = process.env.NODE_ENV !== 'production';

// Must configure Raven before doing anything else with it
if (!dev) {
  Raven.config('__DSN__', {
    autoBreadcrumbs: true,
    captureUnhandledRejections: true,
  }).install();
}

const app = nextjs({ dev });
const handle = app.getRequestHandler();

const captureMessage = (req, res) => () => {
  if (res.statusCode > 200) {
    Raven.captureMessage(`Next.js Server Side Error: ${res.statusCode}`, {
      req,
      res,
    });
  }
};

app
  .prepare()
  .then(() => {
    const server = express();

    if (!dev) {
      server.use((req, res, next) => {
        res.on('close', captureMessage(req, res));
        res.on('finish', captureMessage(req, res));
        next();
      });
    }

    [...]

    server.get('/', (req, res) => {
      return app.render(req, res, '/home', req.query)
    })

    server.get('*', (req, res) => {
      return handle(req, res)
    })

    server.listen('3000', (err) => {
      if (err) throw err
      console.log(`> Ready on http://localhost:${port}`)
    })
  })
  .catch(ex => {
    console.error(ex.stack);
    process.exit(1);
  });

์ด๊ฒƒ์€ ์‹ค์ œ ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•œ ๋งค์šฐ ์ธ์œ„์ ์ธ ์˜ˆ์ž…๋‹ˆ๋‹ค. ์ด ํ˜•์‹์œผ๋กœ ํ…Œ์ŠคํŠธํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ๊ณ ์žฅ๋‚˜๋ฉด ์•Œ๋ ค์ฃผ์„ธ์š”.

๋ฌผ๋ก , Next.js๊ฐ€ ์ด๋Ÿฌํ•œ ์˜ค๋ฅ˜๋ฅผ Express๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ๊ฐ€์žฅ ์ข‹์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด Sentry/Express ํ†ตํ•ฉ์„ ์ฆ‰์‹œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@tusgavomelo ๋‹ต๋ณ€์ด ๋Šฆ์–ด ์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค.

์—…๋ฐ์ดํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ ์•ฑ์—๋Š” ํด๋ผ์ด์–ธํŠธ ์ธก์ด๋‚˜ ์„œ๋ฒ„ ์ธก์—์„œ ๊ณ ๋ คํ•˜๋Š” Raven ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์—ญํ• ์„ ํ•˜๋Š” ๋ฉ”์†Œ๋“œ๊ฐ€ ์žˆ๋Š” ๋„์šฐ๋ฏธ ํŒŒ์ผ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

let clientInstance;
let serverInstance;

const getRavenInstance = (key, config) => {
  const clientSide = typeof window !== 'undefined';

  if (clientSide) {
    if (!clientInstance) {
      const Raven = require('raven-js');  // eslint-disable-line global-require
      Raven.config(key, config).install();
      clientInstance = Raven;
    }

    return clientInstance;
  }

  if (!serverInstance) {
    // NOTE: raven (for node) is not bundled by webpack (see rules in next.config.js).
    const RavenNode = require('raven'); // eslint-disable-line global-require
    RavenNode.config(key, config).install();
    serverInstance = RavenNode;
  }
  return serverInstance;
};

์ด ์ฝ”๋“œ๋Š” ์„œ๋ฒ„ ์ธก(์š”์ฒญ์ด ๋„์ฐฉํ•  ๋•Œ)๊ณผ ํด๋ผ์ด์–ธํŠธ ์ธก ๋ชจ๋‘๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๊ฐ€ ํ•œ ๊ฒƒ์€ raven ํŒจํ‚ค์ง€๋ฅผ ๋ฒˆ๋“คํ•˜์ง€ ์•Š๋„๋ก webpack( next.config.js ํŒŒ์ผ)์„ ๊ตฌ์„ฑํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

@acanimal ์ž‘์—… ์˜ˆ์ œ๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ? ์ „์ฒด ์Šคํƒ ์ถ”์ ์„ ์–ป์ง€ ๋ชปํ•˜๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๊นŒ? ์•„๋‹ˆ๋ฉด _error.js ๋ฅผ ๊ฒŒ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?

๋‚ด๊ฐ€ ์ข‹์•„ํ•˜๋Š” ๋ญ”๊ฐ€๋ฅผํ•˜๊ณ ์žˆ๋Š” ์ค‘์ด ์•ผ RavenInstance.captureException(err) ๋‚ด์—์„œ _error.js ,ํ•˜์ง€๋งŒ ๋‚œ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๊ณณ์œผ๋กœ ๋ฐœ์ƒํ•œ ์˜ค๋ฅ˜์˜ ์œ ํ˜•์„ ๋ณผ ์ˆ˜ ์—†์–ด์š”?

export default class Error extends React.Component {
  static getInitialProps({ res, err }) {
    const RavenInstance = getRavenInstance('__SENTRY__')
    if (!(err instanceof Error)) {
      err = new Error(err && err.message)
    }
    RavenInstance.captureException(err)
    // const statusCode = res ? res.statusCode : err ? err.statusCode : null;

    return { }
  }

  render() {
    return (
      <div>
        <p>An error occurred on server</p>
      </div>
    )
  }
}

์ด๊ฒƒ์€ quiet ๊ฐ€ false ๋กœ ์„ค์ •๋˜์–ด ๋ชจ๋“ˆ์„ ๋‹ค์‹œ ๋นŒ๋“œํ•  ๋•Œ ๋‹ค์Œ ํ™”๋ฉด์„ ์ง€์šฐ๊ณ  ๋ณด๊ธฐ์—์„œ ์˜ค๋ฅ˜๋ฅผ ์ œ๊ฑฐํ•˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉ์ž ์ •์˜ ๋กœ๊ฑฐ ์ง€์›์„ ์š”์ฒญํ•˜๋Š” ์žฅ์†Œ์ฒ˜๋Ÿผ ๋ณด์ด์ง€๋งŒ ์—ฌ๊ธฐ์—์„œ ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ์œ ์ผํ•œ ํ•ดํ‚น์€ ๋‹ค์Œ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. quiet ๋ฅผ false๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

์˜ค๋ฅ˜ ์„ธ๋ถ€ ์ •๋ณด๋Š” stderr ์ŠคํŠธ๋ฆผ์—์„œ๋„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
process.stderr.write = error => yourErrorLog(error);

_unortodox_ ์†”๋ฃจ์…˜์„ ์‚ฌ๋ž‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

function installErrorHandler(app) {
  const _renderErrorToHTML = app.renderErrorToHTML.bind(app)
  const errorHandler = rollbar.errorHandler()

  app.renderErrorToHTML = (err, req, res, pathname, query) => {
    if (err) {
      errorHandler(err, req, res, () => {})
    }

    return _renderErrorToHTML(err, req, res, pathname, query)
  }

  return app
}
// ยฏ\_(ใƒ„)_/ยฏ

์‚ฌ์šฉ์ž ์ •์˜ ์›นํŒฉ ๊ตฌ์„ฑ ์—†์ด ๋…ธ๋“œ ๋ฐ ๋ธŒ๋ผ์šฐ์ €์— ๋Œ€ํ•ด ์˜ฌ๋ฐ”๋ฅธ Raven์„ ์–ป๋Š” ๋ฐฉ๋ฒ•์˜ ์••์ถ• ๋ฒ„์ „. @acanimal ๋Œ“๊ธ€์—์„œ ์˜๊ฐ์„

// package.json
"browser": {
    "raven": "raven-js"
}

// getRaven.js
const Raven = require('raven')

if (process.env.NODE_ENV === 'production') {
  Raven.config('YOUR_SENTRY_DSN').install()
}

module.exports = Raven

์š”์ 

์ด ๋ฌธ์ œ๋ฅผ ์กฐ์‚ฌํ•˜๋Š” ๋ชจ๋“  ์‚ฌ๋žŒ์„ ์œ„ํ•œ ๊ฐ„๋žตํ•œ ์š”์•ฝ์ž…๋‹ˆ๋‹ค.

// pages/_error.js
import Raven from 'raven';
...
static async getInitialProps({ store, err, isServer }) {
   if (isServer && err) {
      // https://github.com/zeit/next.js/issues/1852
      // eslint-disable-next-line global-require
      const Raven = require('raven');

      Raven.captureException(err);
    }
    ...
// next.config.js
config.plugins.push(new webpack.IgnorePlugin(/^raven$/));

@acanimal ๋‹˜ ์˜ ๋Œ“๊ธ€ ๋•๋ถ„์ž…๋‹ˆ๋‹ค.

raven (์ด์ „ ์ฃผ์„์—์„œ ์ œ์•ˆํ•œ ๋Œ€๋กœ webpack ๋ฌด์‹œ) ๋ฐ raven-js ๋ฅผ ๋ชจ๋‘ ์„ค์น˜ํ•œ ๋‹ค์Œ lib/raven.js ์™€ ๊ฐ™์ด ๋™ํ˜• Raven์„ ์ธ์Šคํ„ด์Šคํ™”ํ•˜๋Š” ๋„์šฐ๋ฏธ๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

import Raven from 'raven-js';

// https://gist.github.com/impressiver/5092952
const clientIgnores = {
  ignoreErrors: [
    'top.GLOBALS',
    'originalCreateNotification',
    'canvas.contentDocument',
    'MyApp_RemoveAllHighlights',
    'http://tt.epicplay.com',
    "Can't find variable: ZiteReader",
    'jigsaw is not defined',
    'ComboSearch is not defined',
    'http://loading.retry.widdit.com/',
    'atomicFindClose',
    'fb_xd_fragment',
    'bmi_SafeAddOnload',
    'EBCallBackMessageReceived',
    'conduitPage',
    'Script error.',
  ],
  ignoreUrls: [
    // Facebook flakiness
    /graph\.facebook\.com/i,
    // Facebook blocked
    /connect\.facebook\.net\/en_US\/all\.js/i,
    // Woopra flakiness
    /eatdifferent\.com\.woopra-ns\.com/i,
    /static\.woopra\.com\/js\/woopra\.js/i,
    // Chrome extensions
    /extensions\//i,
    /^chrome:\/\//i,
    // Other plugins
    /127\.0\.0\.1:4001\/isrunning/i, // Cacaoweb
    /webappstoolbarba\.texthelp\.com\//i,
    /metrics\.itunes\.apple\.com\.edgesuite\.net\//i,
  ],
};

const options = {
  autoBreadcrumbs: true,
  captureUnhandledRejections: true,
};

let IsomorphicRaven = null;

if (process.browser === true) {
  IsomorphicRaven = Raven;
  IsomorphicRaven.config(SENTRY_PUBLIC_DSN, {
    ...clientIgnores,
    ...options,
  }).install();
} else {
  // https://arunoda.me/blog/ssr-and-server-only-modules
  IsomorphicRaven = eval("require('raven')");
  IsomorphicRaven.config(
    SENTRY_DSN,
    options,
  ).install();
}

export default IsomorphicRaven;

๊ทธ๋Ÿฐ ๋‹ค์Œ pages/_error.js ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์„œ๋ฒ„ ์ธก๊ณผ ํด๋ผ์ด์–ธํŠธ ์ธก ๋ชจ๋‘์—์„œ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

import NextError from 'next/error';
import IsomorphicRaven from 'lib/raven';

class MyError extends NextError {
  static getInitialProps = async (context) => {
    if (context.err) {
      IsomorphicRaven.captureException(context.err);
    }
    const errorInitialProps = await NextError.getInitialProps(context);
    return errorInitialProps;
  };
}

export default MyError;

๋‹ค์Œ์€ Next.js๋ฅผ ์ง€์›ํ•˜๋Š” Rollbar ์†Œ์Šค๋งต ์›น๋ฐฑ ํ”Œ๋Ÿฌ๊ทธ์ธ https://github.com/thredup/rollbar-sourcemap-webpack-plugin/pull/56์— ๋Œ€ํ•œ ์ œ PR์ž…๋‹ˆ๋‹ค. :)

@tusgavomelo , ์ŠคํŠธ๋ฆผ์— ์žˆ๋Š” ์˜ค๋ฅ˜๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์„ค๋ช…ํ•ด ์ฃผ์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?
"process.stderr.write = ์˜ค๋ฅ˜ => yourErrorLog(์˜ค๋ฅ˜);"

๋…ธ๋“œ ์ฝ˜์†”์— ์˜ค๋ฅ˜๋ฅผ ๊ธฐ๋กํ•˜๋ ค๋ฉด ์ด ์ฝ”๋“œ ์ค„์„ ์–ด๋””์— ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๊นŒ?

๊ทธ๊ฒƒ์€ ๋‹จ์ง€ ํด๋ผ์ด์–ธํŠธ ์ธก์ž…๋‹ˆ๋‹ค.
Rollbar.error('some error')

https://docs.rollbar.com/docs/javascript

@teekey99 @sentry/browser ๋น„์Šทํ•œ ์†”๋ฃจ์…˜์ด ์žˆ์Šต๋‹ˆ๊นŒ? ์•„๋งˆ๋„ with-sentry ์˜ˆ์ œ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?

@sheerun ์ง€๊ธˆ๊นŒ์ง€ raven ๋ฐ raven-js ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์ด ์ด์ œ @sentry/node ๋ฐ @sentry/browser ์ถ”๊ฐ€๋จ์— ๋”ฐ๋ผ ์ด๋Ÿฌํ•œ ๊ธฐ๋Šฅ์ด ๋” ์ด์ƒ ์‚ฌ์šฉ๋˜์ง€ ์•Š์„ ๊ฒƒ์ž„์„ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฌธ์ œ๋Š” ์•„์ง ๋‚ด ํ”„๋กœ์ ํŠธ์—์„œ ์ด๋Ÿฌํ•œ ์ƒˆ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜์ง€๋งŒ ์‚ดํŽด๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ž‘์—… ์˜ˆ์ œ๊ฐ€ ์žˆ์œผ๋ฉด ๋‹ค์‹œ ๊ฐ€์ ธ์˜ค๊ฒ ์Šต๋‹ˆ๋‹ค.

with-sentry ์˜ˆ์ œ๊ฐ€ ์ตœ๊ทผ์— ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

@timneutkens ๋‚ด๊ฐ€ @sentry/node ์–ด๋”˜๊ฐ€์— ์‚ฌ์šฉํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

@sheerun ์ €๋„ ๊ฐ™์€ ๋ฌธ์ œ๋ฅผ @sentry/node ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ์‰ฌ์šด ์ผ์ด ์•„๋‹™๋‹ˆ๋‹ค. ์ด Sentry ์˜ˆ์ œ์˜ ์ถ”๊ฐ€ ์ •๋ณด๋Š” ๋‚ด๊ฐ€ ์ž‘์—… ์ค‘์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ด๋ฏธ ์žˆ๋Š” ์‚ฌ์šฉ์ž ์ •์˜ ์„œ๋ฒ„ ์‚ฌ์šฉ์„ ์ œ์•ˆํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž ์ง€์ • Express.js ์„œ๋ฒ„์—์„œ ์˜ˆ์™ธ๋ฅผ ์บก์ฒ˜ํ•˜๋ ค๋ฉด Sentry ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ๊ธฐ ๋ฏธ๋“ค์›จ์–ด ์˜ค๋ฅ˜ ๋ฅผ ์ฒซ ๋ฒˆ์งธ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ๋ฏธ๋“ค์›จ์–ด ๋กœ ์‚ฝ์ž…ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ ํ•ธ๋“ค๋Ÿฌ ๋ฐ”๋กœ ๋’ค์— Sentry์˜ ์˜ค๋ฅ˜ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์‚ฝ์ž…ํ•˜๋ฉด ํ•ด๋‹น ์ง€์ ์—์„œ ์ด๋ฏธ ์˜ค๋ฅ˜๋ฅผ ์‚ผ์ผœ ๋ฒ„๋ ธ๊ณ  Sentry๋Š” ์ด๋ฅผ ๋ณผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

getInitialProps ์˜ _error.js getInitialProps @sentry/browser ์—์„œ @sentry/browser ์— ์„œ๋ฒ„ ์ธก ์ง€์›์ด ์žˆ์–ด์•ผ ํ•˜๋Š”์ง€ ๋ชจ๋ฅด๊ฒ ์ง€๋งŒ Sentry์— ์ด๋ฒคํŠธ๋ฅผ ๋ฐ›๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์‚ฌ์šฉ์ž ์ •์˜ Express ์„œ๋ฒ„์˜ ํ•ญ๋ชฉ ํŒŒ์ผ์—์„œ @sentry/node Sentry.init() ๋ฅผ ํ†ตํ•ด

๋‹ค์Œ์€ ๋‚ด๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ์„ค์ •์˜ ์š”์ง€์ž…๋‹ˆ๋‹ค. https://gist.github.com/mcdougal/7bf001417c3dc4b579da224b12776691

ํฅ๋ฏธ๋กœ์šด!

์—ฌ๊ธฐ์—๋Š” ํ™•์‹คํžˆ ์ผ์ข…์˜ ๊ธ€๋กœ๋ฒŒ ์ƒํƒœ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค(์•ฝ๊ฐ„ ๋ฌด์„ญ๊ณ  ์•„๋งˆ๋„ ๋ถ€์„œ์ง€๊ธฐ ์‰ฝ๊ณ  ๋ฐ”๋žŒ์งํ•˜์ง€ ์•Š์Œ). _error.js ๋ณ€๊ฒฝ ์‚ฌํ•ญ ์ ์šฉ:

  • ์ปค์Šคํ…€ ์„œ๋ฒ„์—์„œ Sentry๋ฅผ ๊ตฌ์„ฑํ•˜์ง€ ์•Š๊ณ __:

    • ๊ฐœ๋ฐœ ๋ชจ๋“œ ๋˜๋Š” ํ”„๋กœ๋•์…˜(์˜ˆ: ๋‚ด ์•ฑ ๋นŒ๋“œ)์—์„œ ์‹คํ–‰ํ•  ๋•Œ ์„œ๋ฒ„ ์ธก ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด Sentry์— ์•„๋ฌด ๊ฒƒ๋„ ๋ณด๊ณ ๋˜์ง€ ์•Š์ง€๋งŒ ์„œ๋ฒ„ ๋กœ๊ทธ์— ReferenceError: XMLHttpRequest is not defined ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

  • __๊ตฌ์„ฑ ํ›„__ ์‚ฌ์šฉ์ž ์ง€์ • ์„œ๋ฒ„์˜ Sentry:

    • ๋‚ด ์„œ๋ฒ„ ์ธก ์˜ˆ์™ธ๊ฐ€ ๋ณด๊ณ ๋˜์ง€๋งŒ Sentry์— ์ด์ƒํ•œ Error: Sentry syntheticException ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค.

๊ณต์‹์ ์ธ ํ•ด๊ฒฐ์ฑ…์ด ๋ฌด์—‡์ธ์ง€ ์ดํ•ดํ•˜๋Š” ๊ฒƒ์ด ์ข‹์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. Next์˜ ๋ฌธ์„œ๋Š” ์ง€๊ธˆ ์‚ฌ์šฉ์ž ์ •์˜ <App> ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•˜๋Š” ๊ฒƒ ๊ฐ™์œผ๋ฉฐ ์ด๊ฒƒ์ด "with Sentry" ์˜ˆ์ œ๊ฐ€ ํ•˜๋Š” ์ผ์ด์ง€๋งŒ ์ด๊ฒƒ์€ ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ๋งŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

์˜ค๋ฅ˜ ๋ณด๊ณ ๋Š” ์•ฑ์ด ์ฒ˜์Œ ๋ Œ๋”๋ง๋  ๋•Œ๊นŒ์ง€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์€ ์˜ˆ๋ฅผ ๋“ค์–ด ํŽ˜์ด์ง€๋ฅผ ๋ Œ๋”๋งํ•  ๋•Œ ์ด์ „์— ์‹คํŒจํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•ฑ์ด ์„œ๋ฒ„ ์ธก์—์„œ ์ฒ˜์Œ ๋ Œ๋”๋ง๋œ ํ›„ ์–ด๋–ค ๊ฒฝ์šฐ์—๋Š” ์šฐ์—ฐํžˆ ์ž‘๋™ํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ ์™„์ „ํ•œ ์†”๋ฃจ์…˜์€ ์•„๋‹™๋‹ˆ๋‹ค.

with-sentry ์˜ˆ์ œ๋Š” @timneutkens์—์„œ๋„ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ”„๋กœ์ ํŠธ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ์‹ค์ œ DSN์œผ๋กœ ํ…Œ์ŠคํŠธํ•˜๋ฉด 400 ์‘๋‹ต์ด ๋‚˜์˜ค๊ณ  ์„ผํŠธ๋ฆฌ(api ๋ฒ„์ „ 7)์—๋Š” ์•„๋ฌด ๊ฒƒ๋„ ์ „๋‹ฌ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

{"error":"Bad data reconstructing object (JSONDecodeError, Expecting value: line 1 column 1 (char 0))"}

@mcdougal ์€ ๋‚˜๋ฅผ ์œ„ํ•ด ์ž‘๋™ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์–ด๋–ป๊ฒŒ๋“  ๋ฒ„๋ธ”๋ง๋˜๋Š” ์ „์—ญ ์„œ๋ฒ„ ์ธก ์„ค์ •์„ ๊ฐ–๋Š” ๊ฒƒ์€ ์ด๋ฏธ ๊ทธ๋‹ค์ง€ ์ข‹์ง€ ์•Š์•˜์ง€๋งŒ, ๊ทธ ํ•ดํ‚น์œผ๋กœ๋„ ์„ผํŠธ๋ฆฌ 301 ์‘๋‹ต์„ ๋ฐ›์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์˜ต์…˜์ด ๋งŽ์ง€ ์•Š๊ณ  ์—ฌ๋Ÿฌ ํ”„๋กœ์ ํŠธ์—์„œ ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋‚ด ์„ผํŠธ๋ฆฌ ์ž์ฒด ํ˜ธ์ŠคํŒ… ์„ค์ •์ด ์–ด๋–ป๊ฒŒ ์ž˜๋ชป ๊ตฌ์„ฑ๋  ์ˆ˜ ์žˆ๋Š”์ง€ ์•Œ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

๋‚˜๋Š” with-sentry ์˜ˆ์ œ์—์„œ ๊ถŒ์žฅํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ @sentry/browser ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์œผ๋ฉฐ ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ ๋ชจ๋‘์—์„œ ๋‚ด ์„ผํŠธ๋ฆฌ๋กœ ์˜ค๋ฅ˜๋ฅผ ๋ณด๋‚ด๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ํŠน๋ณ„ํ•œ ๊ตฌ์„ฑ์€ ์—†์œผ๋ฉฐ ์˜ˆ์ œ์™€ ๋™์ผํ•œ ์ฝ”๋“œ๋งŒ ์žˆ์Šต๋‹ˆ๋‹ค.

@Jauny ์„œ๋ฒ„์—์„œ ๋ณด๋‚ด๋Š”

@timrogers ๋„ค, ์ •๋ง ์ œ ์ž˜๋ชป์ž…๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธํ•œ ์˜ค๋ฅ˜๊ฐ€ ์„œ๋ฒ„์—์„œ ์˜จ๋‹ค๊ณ  ๊ฐ€์ •ํ–ˆ์ง€๋งŒ ์‹ค์ œ๋กœ๋Š” ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ˜„์žฌ ์˜ˆ์ œ with-sentry๋Š” getInitialProps์—์„œ throw๋œ ์˜ค๋ฅ˜๋ฅผ catchํ•˜์ง€ ์•Š๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์˜ ๋ Œ๋”๋ง ํŠธ๋ฆฌ์— ์žˆ๋Š” ์˜ค๋ฅ˜์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. ์ œ ๊ฒฝ์šฐ์—๋Š” ๋Œ€๋ถ€๋ถ„์˜ ์˜ค๋ฅ˜๊ฐ€ getInitialProps์—์„œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

@sheerun ์œ„์˜ mcdougal ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• ์„ ์‹œ๋„ํ•ด ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ? ์™„๋ฒฝํ•˜์ง€๋Š” ์•Š์ง€๋งŒ(์ด์ƒํ•œ ํ•ฉ์„ฑ ์„ผํŠธ๋ฆฌ ์˜ค๋ฅ˜) ๋ชจ๋“  ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋Š” ์ธ์ƒ์„ ๋ฐ›๊ณ  ๊ทธ๊ฒƒ์ด ์‚ฌ์‹ค์ด ์•„๋‹Œ์ง€ ์•Œ๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์ด ์‚ฌ์‹ค์ด๋ผ๋ฉด, with-sentry ์˜ˆ์ œ๋Š” ์•„๋งˆ๋„ Next.js๊ฐ€ ๊ทธ๊ฒƒ์„ ๋” ์ž˜ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์กฐ์–ธํ•  ์ˆ˜ ์žˆ์„ ๋•Œ๊นŒ์ง€ ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค(์ด์ƒ์ ์œผ๋กœ๋Š” server.js ์„ผํŠธ๋ฆฌ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ๊ธฐ๋ฅผ ๊ฑด๋„ˆ๋›ฐ์ง€ ์•Š๋„๋ก ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ์ด์ƒ์ ์ž…๋‹ˆ๊นŒ?).

๋‘ ๊ฐ€์ง€ ์ฃผ์š” ๋ฌธ์ œ๋กœ ์ž‘๋™ํ•˜๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. ๋น„๋™๊ธฐ ์„œ๋ฒ„ ์ธก ์ฝ”๋“œ๊ฐ€ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ์žฌ์ƒ์„ฑ ์ฝ”๋“œ๋กœ ์ปดํŒŒ์ผ๋˜๊ธฐ ๋•Œ๋ฌธ์— ํ”„๋กœ๋•์…˜์˜ ์„œ๋ฒ„ ์ธก ์Šคํƒ ์ถ”์ ์€ ์ข…์ข… internal/process/next_tick.js์—์„œ ์ž˜๋ฆฌ๊ณ  ์˜ค๋ฅ˜์˜ ์›์ธ์ด ์ „ํ˜€ ํ‘œ์‹œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  2. ๋‹ค์Œ ์š”์ฒญ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ํ˜ธ์ถœ๋˜๊ธฐ ์ „์— ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ๊ฐ€ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค(์˜ˆ: ์‚ฌ์šฉ์ž ์ •์˜ ์„œ๋ฒ„ ์ฝ”๋“œ์—์„œ)

์‹ค์ œ๋กœ ์‚ฌ์šฉ์ž ์ •์˜ ์˜ค๋ฅ˜์˜ getInitialProps๋ฅผ ์ถ”๊ฐ€๋กœ ํ…Œ์ŠคํŠธํ•œ ํ›„ ์–ด๋–ค ์ด์œ ๋กœ ์ธํ•ด ์‚ฌ์šฉ์ž ์ •์˜ _app์˜ getInitialProps ๋‚ด๋ถ€์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋„ ์‹คํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์˜ˆ, ๋ฉฐ์น  ๋™์•ˆ ์‹œ๋„ํ•œ ํ›„ ํ™•์‹คํžˆ ์ด์ƒํ•œ ํ–‰๋™์ด ๋‚˜ํƒ€๋‚ฌ์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๊ฐ€ ์ง๋ฉดํ•œ ์ฃผ์š” ๋ฌธ์ œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. CSR์˜ ๊ฒฝ์šฐ @sentry/browser ๋ฐ SSR์˜ ๊ฒฝ์šฐ @sentry/node ๊ฐ€์ ธ์˜ค๊ธฐ
  2. CSR๊ณผ SSR ๋ชจ๋‘์— ๋Œ€ํ•ด ํ•ญ์ƒ ํŠธ๋ฆฌ๊ฑฐ๋˜๋Š” ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ๋ฅผ ๋„ฃ์„ ์œ„์น˜ ์ฐพ๊ธฐ

๋‚˜๋Š” #1 ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์ง€์—ฐ ๊ฐ€์ ธ์˜ค๊ธฐ ๋ฐ ์ „์—ญ ์ƒํƒœ๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค.

const sentryInitialized = false;

# Inside some trigger point
const Sentry = ssr ? eval(`require('@sentry/node')`) : eval(`require('@sentry/browser')`);
if (!sentryInitialized) {
  Sentry.init({dsn: SENTRY_DSN});
}
Sentry.captureException(err);

์•„๋งˆ๋„ Next์˜ ๋™์  ๊ฐ€์ ธ์˜ค๊ธฐ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์•„์ง ์ต์ˆ™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ์ฝ”๋“œ๊ฐ€ ํŠธ๋ฆฌ๊ฑฐ๋  ๋•Œ๋งˆ๋‹ค require ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์ด ๋ฏธ์น˜๋Š” ์˜ํ–ฅ๋„ ์ž˜ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด ์‹œ๋„๋ฅผ ํ•˜๋ฉด/๋•Œ ์—…๋ฐ์ดํŠธํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

#2 ๋ฌธ์ œ์— ๊ด€ํ•ด์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

  1. _app.componentDidCatch , SSR์— ๋Œ€ํ•ด ์‹คํ–‰๋˜์ง€ ์•Š์Œ
  2. _error.getInitialProps , ๋งŽ์€ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
  3. ...๋‹ค๋ฅธ ๊ฒƒ? ๋‹ค์Œ ์„œ๋ฒ„ ํ•ธ๋“ค๋Ÿฌ ๊ธฐ๋Šฅ์—์„œ SSR์„ ์œ„ํ•ด ์šฐ๋ฆฌ๊ฐ€ ํ•  ์ˆ˜ ์žˆ๋Š” ์ผ์ด ์žˆ์Šต๋‹ˆ๊นŒ?

๋‚ด ์ง๊ฐ์œผ๋กœ๋Š” ๋‹ค์Œ๋ณด๋‹ค ๋จผ์ € Sentry์˜ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ๊ธฐ๋ฅผ ์ฃผ์ž…ํ•˜์—ฌ ์„œ๋ฒ„์—์„œ ์ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์„ ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜์ง€๋งŒ ์•„์ง ์‹œ๋„ํ•ด๋ณด์ง€๋Š” ์•Š์•˜์Šต๋‹ˆ๋‹ค.

ํ˜„์žฌ Next๋Š” ๊ทธ๋ ‡๊ฒŒ ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋˜๋Š” ํ›„ํฌ๋ฅผ ์ œ๊ณตํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ์ž‘๋™ํ•˜๋Š” ๊ฒƒ์„ ๋ฐœ๊ฒฌํ•˜๋ฉด ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค ๐Ÿ‘Œ

ํ•˜์ง€๋งŒ Next๊ฐ€ ๋„ˆ๋ฌด ์ผ์ฐ ์žก๊ธฐ ๋•Œ๋ฌธ์— ์ด๊ฒƒ์ด ์ž‘๋™ํ•˜์ง€ ์•Š์„ ์œ„ํ—˜์ด ์žˆ์Šต๋‹ˆ๋‹ค.

@mcdougal ์•„ &#!% ๊ท€ํ•˜์˜ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์ด ์šฐ๋ฆฌ๋ฅผ ํ”„๋กœ๋•์…˜์— ํˆฌ์ž…ํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ์„ผํŠธ๋ฆฌ ํด๋ผ์ด์–ธํŠธ/์„œ๋ฒ„ ๋ฒ”์œ„๋ฅผ ํ™•์ธํ•˜๊ธฐ์— ์ถฉ๋ถ„ํ–ˆ์„ ๋•Œ ์ •๋ง ๊ธฐ๋ปค์Šต๋‹ˆ๋‹ค.

๋‚ด ์™„์ „ํ•œ ๋ฌด์ง€๋ฅผ ์šฉ์„œํ•˜์ง€๋งŒ nodejs ๋ณด์ดˆ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ๊ธฐ๊ฐ€ ์ฒซ ๋ฒˆ์งธ๊ฐ€๋˜๋„๋ก ์กฐ๊ฑด๋ถ€๋กœ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ๊ธฐ๋ฅผ ๋น„ํ™œ์„ฑํ™” ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋‹ค์Œ์ด ํ•„์š”ํ•ฉ๋‹ˆ๊นŒ? "disableErrorHandler"๋ผ๋Š” next.config.js์˜ ์ผ๋ถ€ ํ”Œ๋ž˜๊ทธ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ?

@Enalmada ๋ฉ‹์ง„ ์˜ค๋ฅ˜ ํŽ˜์ด์ง€๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค์Œ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ๊ธฐ๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•˜๊ณ  ์‹ถ์ง€ ์•Š๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๊ทธ ์•ž์— ๋‹ค๋ฅธ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์‚ฝ์ž…ํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๋‚˜๋Š” ๊ทธ๊ฒƒ์ด ํšจ๊ณผ๊ฐ€ ์žˆ์„ ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜์ง€๋งŒ ๋‚˜๋Š” ๊ทธ๊ฒƒ์„ ์‹œ๋„ํ•ด์•ผ ํ•œ๋‹ค.

์ด ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋˜์—ˆ์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ํด๋ผ์ด์–ธํŠธ ์ธก ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ๊ฐ€ ๊ธฐ๋Œ€๋งŒํผ ์ž˜ ์ž‘๋™ํ•˜๋Š” ๊ฒƒ ๊ฐ™์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ด ๋ชจ๋“  ๋ฌธ์ œ๋Š” ๋ถ€๋„๋Ÿฌ์šด ์ผ์ด๋ฉฐ ํ”„๋กœ๋•์…˜์—์„œ Next๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์‹คํ–‰ํ•˜๋Š” ๋ฐ ๋ฐฉํ•ด๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

์ฐธ๊ณ ๋กœ ์ €๋Š” @sentry/node ๋ชจ๋“  ๊ณณ์—์„œ ๊ฐ€์ ธ์˜ค๊ณ  ๋‹ค์Œ์„ next.config.js์— ๋„ฃ์Šต๋‹ˆ๋‹ค.

        if (!isServer) {
          config.resolve.alias['@sentry/node$'] = '@sentry/browser'
        }

@mcdougal์˜ eval ๋ณด๋‹ค ๋‚˜์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ์€ ์‚ฌ์šฉ์ž ์ง€์ • _app ๊ตฌ์„ฑ ์š”์†Œ ๋ฐ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ์— ๋Œ€ํ•œ ์ถ”๊ฐ€ ์ฐธ๊ณ  ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค.

  1. _app.js ๋Š” _error.js ๋˜๋Š” 404์™€ ๊ฐ™์€ ํŽ˜์ด์ง€๋ฅผ ํฌํ•จํ•œ ๋ชจ๋“  ํŽ˜์ด์ง€์— ์‚ฌ์šฉ๋˜๋ฏ€๋กœ ctx.err ๊ฐ€ ์ „๋‹ฌ๋  ๋•Œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.
  2. _error.js getInitialProps๊ฐ€ ํ˜ธ์ถœ๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๋ฏ€๋กœ ์•ฑ์˜ getInitialProps์—์„œ Component.getInitialProps๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์„ ์žŠ์ง€ ๋งˆ์‹ญ์‹œ์˜ค( ctx.err ๊ฐ€ ์žˆ๋”๋ผ๋„ ํ˜ธ์ถœ).
class MyApp extends App {
  static async getInitialProps (appContext) {
    const { Component, ctx } = appContext
    if (ctx.err) {
      if (Component.getInitialProps) {
        pageProps = await Component.getInitialProps(ctx)
      }
      return { error: true, pageProps }
    }
    // here code that can throw an error, and then:
    if (Component.getInitialProps) {
      pageProps = await Component.getInitialProps(ctx)
    }
    return { pageProps }
  }
  render() {
    if (this.props.error) return super.render()
    // rest of code that can throw an error
  }
}

์ง€๊ธˆ๊นŒ์ง€ next.js์—์„œ ์˜ค๋ฅ˜ ๋ณด๊ณ ๋ฅผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์€ ๋งค์šฐ ์ทจ์•ฝํ•œ ์ ˆ์ฐจ์ž…๋‹ˆ๋‹ค.

์˜ฌ๋ฐ”๋ฅธ ๋ฐฉํ–ฅ์„ ํ–ฅํ•œ ์ข‹์€ ์ •๊ฑฐ์žฅ์ฒ˜๋Ÿผ ๋ณด์ด๋Š” @sheerun ์—๊ฒŒ ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ๋‚˜๋Š” ๋‹ค์Œ์˜ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ๊ฐ€ ์ง€๊ธˆ ์ตœ์ ์ด ์•„๋‹ˆ๋ผ๋Š” ๋ฐ ๋™์˜ํ•ฉ๋‹ˆ๋‹ค. ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ๋“ฑ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ผ๋ถ€ ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“ˆ/๋ฏธ๋“ค์›จ์–ด๊ฐ€ ์ถ”๊ฐ€๋˜๋Š” ๊ฒƒ์„ ๋ณด๋Š” ๊ฒƒ์ด ์ข‹์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

Sentry์™€ ๊ฐ™์€ ๋™ํ˜• ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๋ถ€์กฑ์€ ๋˜ํ•œ ๋ฌธ์ œ๋ฅผ ๋ณต์žกํ•˜๊ฒŒ ๋งŒ๋“ค๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๊ตฌ์„ฑ ์š”์†Œ์—์„œ ๋‘ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†์Œ์„ ์˜๋ฏธํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋Ÿฐํƒ€์ž„์— ๋™์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•˜์—ฌ ์˜ค๋ฅ˜๊ฐ€ ์„œ๋ฒ„ ์ธก์ด๋‚˜ ๋ธŒ๋ผ์šฐ์ € ์ธก์—์„œ ๋ฐœ์ƒํ•˜๋Š”์ง€ ํ•ญ์ƒ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ด ๋ฌธ์ œ์— ๋Œ€ํ•œ ์—…๋ฐ์ดํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ? ์ง€๊ธˆ๊นŒ์ง€ ์‹œ๋„ํ•œ ๊ฒƒ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ์ถ”์  ์ฝ”๋“œ๋ฅผ _app.js๋กœ ์˜ฎ๊ฒผ์Šต๋‹ˆ๋‹ค.

constructor(args: any) {
        super(args)
        Sentry.init({
            dsn: 'blah',
            environment: 'local',
        })
        Sentry.configureScope(scope => {
            scope.setTag('errorOrigin', isServer ? 'SSR' : 'Client')
        })
    }
    static async getInitialProps({ Component, router, ctx }: any) {
        let pageProps = {}

        try {
            if (Component.getInitialProps) {
                pageProps = await Component.getInitialProps(ctx)
            }
        } catch (error) {
            // console.log('we caught an error')
            console.log(error)
            Sentry.captureException(error)
            throw error
        }

        return { pageProps }
    }

server.js์—์„œ @sheerun์—์„œ next.config.js ์ถ”๊ฐ€ ๋ฐ ์ดˆ๊ธฐํ™” ๋ณด์ดˆ๊ณผ ํ•จ๊ป˜ ๋„ˆ๋ฌด if (!isServer) { config.resolve.alias['@sentry/node$'] = '@sentry/browser' }
์ด๊ฒƒ์€ ํด๋ผ์ด์–ธํŠธ ์ธก์˜ ๋ชจ๋“  ์˜ค๋ฅ˜๋ฅผ ์ถ”์ ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด์ง€๋งŒ ์„œ๋ฒ„ ์ธก์—์„œ๋Š” ์„œ๋ฒ„๋ฅผ ๋‹ค์‹œ ์‹œ์ž‘ํ•œ ํ›„ ๋ฐœ์ƒํ•˜๋Š” ์ฒซ ๋ฒˆ์งธ ์˜ค๋ฅ˜๋งŒ ์ถ”์ ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์„œ๋ฒ„์˜ ์ดํ›„ ์˜ค๋ฅ˜๋Š” ์ถ”์ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด ์ ‘๊ทผ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋ฉด ๋กœ๊ทธ์— SyntheticErrors๊ฐ€ ์—†๊ณ  ์‹ค์ œ ์˜ค๋ฅ˜๋งŒ ์žˆ์Šต๋‹ˆ๋‹ค.

์—ฌ์ „ํžˆ ์ด๊ฒƒ์€ ๋‚˜์—๊ฒŒ ๊ฝค ํ•ด์ปค์ฒ˜๋Ÿผ ๋Š๊ปด์ง€๊ณ  ์„œ๋ฒ„ ์ธก ์ถ”์ ์€ ์ฒ˜์Œ์—๋งŒ ์ž‘๋™ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์—ฌ์ „ํžˆ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ with-Sentry ์˜ˆ์ œ์—์„œ ์ด ๋ถ€๋ถ„์„ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.

    componentDidCatch(error: any, errorInfo: any) {
        // if (process.env.FIAAS_NAMESPACE !== undefined) {
        Sentry.configureScope(scope => {
            Object.keys(errorInfo).forEach(key => {
                scope.setExtra(key, errorInfo[key])
            })
        })
        Sentry.captureException(error)
        console.log('componentDidCatch')

        // This is needed to render errors correctly in development / production
        super.componentDidCatch(error, errorInfo)
        // }
    }

๊ทธ๋Ÿฌ๋‚˜ ์ด๊ฒƒ์ด ํ•„์š”ํ•œ์ง€ ์™„์ „ํžˆ ํ™•์‹คํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ œ ๊ฒฝ์šฐ์—๋Š” ๋ฌธ์ œ ์—†์ด ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ์„ผํŠธ๋ฆฌ๋ฅผ ์ดˆ๊ธฐํ™”ํ•ด์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค.
_app.js ์ƒ์„ฑ์ž์ด์ง€๋งŒ ์™„์ „ํžˆ ์ด ํด๋ž˜์Šค ์™ธ๋ถ€์— ์žˆ์Œ

2018๋…„ 11์›” 21์ผ ์ˆ˜์š”์ผ ์˜คํ›„ 2์‹œ 53๋ถ„์— abraxxas [email protected]์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ผ์Šต๋‹ˆ๋‹ค.

์ด ๋ฌธ์ œ์— ๋Œ€ํ•œ ์—…๋ฐ์ดํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ? ๋‚ด๊ฐ€ ์ง€๊ธˆ๊นŒ์ง€ ์‹œ๋„ํ•œ ๊ฒƒ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.
๋ชจ๋“  ์ถ”์  ์ฝ”๋“œ๋ฅผ _app.js๋กœ ์ด๋™ํ–ˆ์Šต๋‹ˆ๋‹ค.

`
์ƒ์„ฑ์ž(์ธ์ˆ˜: ๋ชจ๋‘) {
์Šˆํผ(์ธ์ˆ˜)
Sentry.init({
dsn: 'ํ•˜ํ•˜',
ํ™˜๊ฒฝ: '๋กœ์ปฌ',
})
Sentry.configureScope(๋ฒ”์œ„ => {
scope.setTag('errorOrigin', isServer ? 'SSR' : 'ํด๋ผ์ด์–ธํŠธ')
})
}

์ •์  ๋น„๋™๊ธฐ getInitialProps({ ๊ตฌ์„ฑ ์š”์†Œ, ๋ผ์šฐํ„ฐ, ctx }: ๋ชจ๋“ ) {
let pageProps = {}

try {
    if (Component.getInitialProps) {
        pageProps = await Component.getInitialProps(ctx)
    }
} catch (error) {
    // console.log('we caught an error')
    console.log(error)
    Sentry.captureException(error)
    throw error
}

return { pageProps }

}

`

@sheerun์˜ next.config.js ์ถ”๊ฐ€์™€ ๊ฒฐํ•ฉ
https://github.com/sheerun ๋ฐ server.js์—์„œ ๋ณด์ดˆ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋Š” ๊ฒฝ์šฐ์—๋„
(!isServer) { config.resolve.alias['@sentry/node$'] = '@sentry/browser' }
์ด๊ฒƒ์€ ํด๋ผ์ด์–ธํŠธ ์ธก์˜ ๋ชจ๋“  ์˜ค๋ฅ˜๋ฅผ ์ถ”์ ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด์ง€๋งŒ ์„œ๋ฒ„ ์ธก์—์„œ๋Š”
๋‹ค์‹œ ์‹œ์ž‘ํ•œ ํ›„์— ๋ฐœ์ƒํ•˜๋Š” ์ฒซ ๋ฒˆ์งธ ์˜ค๋ฅ˜๋งŒ ์ถ”์ ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค.
์„ฌ๊ธฐ๋Š” ์‚ฌ๋žŒ. ๊ทธ๋Ÿฌ๋‚˜ ์„œ๋ฒ„์˜ ์ดํ›„ ์˜ค๋ฅ˜๋Š” ์ถ”์ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์œผ๋กœ
์ ‘๊ทผ ๋ฐฉ์‹ ๋‚˜๋Š” ๋กœ๊ทธ์— SyntheticErrors๊ฐ€ ์—†์ง€๋งŒ ์‹ค์ œ ์˜ค๋ฅ˜๋งŒ ์žˆ์Šต๋‹ˆ๋‹ค.

์—ฌ์ „ํžˆ ์ด๊ฒƒ์€ ๋‚˜์—๊ฒŒ ๊ฝค ํ•ดํ‚คํ•˜๊ฒŒ ๋Š๊ปด์ง€๊ณ  ์„œ๋ฒ„ ์ธก ์ถ”์ ์ด
์ฒ˜์Œ์—๋งŒ ์ž‘๋™ํ•˜์ง€๋งŒ ์—ฌ์ „ํžˆ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

โ€”
๋‹น์‹ ์ด ์–ธ๊ธ‰๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๊ฒƒ์„ ๋ฐ›๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
์ด ์ด๋ฉ”์ผ์— ์ง์ ‘ ๋‹ต์žฅํ•˜๊ณ  GitHub์—์„œ ํ™•์ธํ•˜์„ธ์š”.
https://github.com/zeit/next.js/issues/1852#issuecomment-440668980 ๋˜๋Š” ์Œ์†Œ๊ฑฐ
์Šค๋ ˆ๋“œ
https://github.com/notifications/unsubscribe-auth/AAR2DeIhoOj6PdWRA2VqiEZyrO5Jui8vks5uxVrHgaJpZM4NOQlp
.

๋‚˜๋Š” ์ด๋ฏธ ๊ทธ๊ฒƒ์„ ์˜ฎ๊ธฐ๋ ค๊ณ  ์‹œ๋„ํ–ˆ์ง€๋งŒ ์—ฌ์ „ํžˆ ๊ฐ™์€ ํ–‰๋™์ž…๋‹ˆ๋‹ค. @sheerun ์„ค์ •์— ๋Œ€ํ•œ ์ตœ์†Œํ•œ์˜ ์š”์ ์„ ๊ฒŒ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ? ๋‹น์‹ ์ด ์ œ๊ณตํ•œ ์Šค๋‹ˆํŽซ์œผ๋กœ ์„ค์ •์„ ์‹œ๋„ํ–ˆ์ง€๋งŒ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋‚ด๊ฐ€ ๋‹ค์†Œ ๊ฐ„๋‹จํ•œ ์ž‘์—…์ด๋ผ๊ณ  ์˜ˆ์ƒํ–ˆ๋˜ ๊ฒƒ์— ๋น„ํ•ด ๋ชจ๋“  ๊ฒƒ์ด ๋„ˆ๋ฌด ๋ณต์žกํ•ด ๋ณด์ž…๋‹ˆ๋‹ค.

๊ณต์‹ ์„ผํŠธ๋ฆฌ ์˜ˆ์ œ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ณ  ์‹ถ์ง€๋งŒ "๋„ˆ๋ฌด ๋ณต์žกํ•˜๋‹ค"๋Š” ์ด์œ ๋กœ ๊ฑฐ๋ถ€๋ ๊นŒ ๋‘๋ ต์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜๋„ ์‹œ๊ฐ„์ด ๋‚˜๋ฉด ๊ฒŒ์‹œํ•ด ๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

@sheerun ๊ณต์‹ ์˜ˆ์ œ๋กœ ์—…๋ฐ์ดํŠธํ•˜๋ ค๋Š” ์‹œ๋„์กฐ์ฐจ ํฐ ๊ฐ€์น˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. Sentry ssr์ด SyntheticErrors ์—†์ด ์ž‘๋™ํ•˜๊ฑฐ๋‚˜ ๋ฐœ์ƒํ•œ ์ฒซ ๋ฒˆ์งธ ์„œ๋ฒ„ ์˜ค๋ฅ˜๋งŒ ๊ธฐ๋กํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ์ตœ์†Œ ๋ณต์žก์„ฑ์ด๋ผ๋ฉด ๋ณ‘ํ•ฉ๋  ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ ๊ฑฐ๊ธฐ์—์„œ ๋” ๋‚˜์€ ๋ฐฉ๋ฒ•์œผ๋กœ ๋งŒ๋“ค๊ฑฐ๋‚˜ nextjs ํ•ต์‹ฌ ๊ฐœ์„  ๋˜๋Š” ์„ผํŠธ๋ฆฌ ๋™ํ˜• ์ง€์›์„ ์ถ”์ง„ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด์ œ ํ•„์—ฐ์ ์œผ๋กœ ๋ณต์žกํ•œ ์ž‘์—… ์˜ˆ์ œ๊ฐ€ ์žˆ์œผ๋ฏ€๋กœ ์ƒํ™ฉ์„ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•œ ๋‹ค์Œ ๋‹จ๊ณ„๋Š” ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?

next.js์—์„œ ํ•„์š”ํ•œ ์œ ์ผํ•œ ๊ฒƒ์€ ๋ฒ”์šฉ(๋™ํ˜•) ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ตฌ์„ฑ์„ ์œ„ํ•ด next.config.js์— ์ปค์Šคํ…€ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  next.browser.js์™€ ๊ฐ™์€ ๊ฒƒ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค(next.config.js๋Š” webpack ๊ตฌ์„ฑ์„ ์œ„ํ•ด ๋‹ค๋ฅธ ๊ฒƒ๋“ค ์‚ฌ์ด์—์„œ ์‚ฌ์šฉ๋˜๋ฉฐ, ์ด๋Š” ์ˆœํ™˜ ์ข…์†์„ฑ์„ ์œ ๋ฐœํ•˜๋ฏ€๋กœ ์ด ํŒŒ์ผ์— ์ •์˜๋œ ๋‹ค๋ฅธ ํ•ญ๋ชฉ์„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

next.browser.js์˜ ๊ฒฝ์šฐ next.js๋Š” ์•ฑ ๋˜๋Š” ๋ฌธ์„œ ๊ตฌ์„ฑ ์š”์†Œ์— ๋Œ€ํ•œ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์™€ ๊ฐ™์€ ๊ตฌ์„ฑ์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์„ผํŠธ๋ฆฌ ํ†ตํ•ฉ์„ ์™„์ „ํžˆ ํ”Œ๋Ÿฌ๊ทธ์ธ์œผ๋กœ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํŽธ์ง‘: ์ด์— ๋Œ€ํ•œ ํ‹ฐ์ผ“์ด ์žˆ๋Š”์ง€ ์—ฌ๋ถ€๋Š” ๋ชจ๋ฅด๊ฒ ์ง€๋งŒ @timneutkens ๊ฐ€ ์ด๋ฏธ ๋‹ค์Œ ์„œ๋ฒ„๋ฅผ ๋ณ„๋„์˜ ํŒจํ‚ค์ง€๋กœ ์ถ”์ถœํ•˜๊ณ  ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ์ตœ๊ณ ์˜ ํ”Œ๋Ÿฌ๊ทธ์ธ ์ธํ„ฐํŽ˜์ด์Šค๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

module.exports = {
  server: server => {
    server.use(Sentry.Handlers.errorHandler())
  }
}

#6922์—์„œ ์ด๋Ÿฌํ•œ API์˜ ์ œ์•ˆ์„ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ปจํŠธ๋ž™ํŠธ๊ฐ€ .listen() ์ž๋™์œผ๋กœ ํ˜ธ์ถœํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์œผ๋กœ ๋ณ€๊ฒฝ๋˜์–ด ์‚ฌ์šฉ์ž ์ •์˜ ์„œ๋ฒ„ ์ฝ”๋“œ์— ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ next.js๊ฐ€ ์ธ์Šคํ„ด์Šคํ™”ํ•˜๊ธฐ ์ „์— ๋” ๋ฐ์ฝ”๋ ˆ์ด์…˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

with-sentry ์˜ˆ์ œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐ ๋งŽ์€ ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ํ›จ์”ฌ ๋” ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ๋ฅผ ์œ„ํ•ด PR์„ ์—ด์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์€ ๋ชจ๋“  ์ข…๊ณผ ํœ˜ํŒŒ๋žŒ์„ ๊ฐ€์ง€๊ณ  ์žˆ์ง€ ์•Š์ง€๋งŒ ๊ทธ๊ฒƒ์€ ๋‚˜๋ฅผ ์œ„ํ•ด ์ผํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

https://github.com/zeit/next.js/pull/7119

์‚ฌ์šฉ์ž ์ •์˜ _document.js ์—์„œ ์ด๊ฒƒ์„ ์‹œ๋„ํ•˜์‹ญ์‹œ์˜ค.

import React from "react";
import Document, {
  Html,
  Head,
  Main,
  NextScript,
} from "next/document";
import { NodeClient } from "@sentry/node";

const { default: getConfig } = require("next/config");
const { publicRuntimeConfig: { sentryDSN } } = getConfig();

let sentry = null;
if (sentryDSN) {
  sentry = new NodeClient({ dsn: sentryDSN });
}

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx);
    if (ctx.err && sentry) sentry.captureException(ctx.err);
    return { ...initialProps };
  }

  render() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

๊ทธ๋ฆฌ๊ณ  ์ด๊ฒƒ์€ ์‚ฌ์šฉ์ž ์ •์˜ _app.js :

import * as Sentry from "@sentry/browser";

const { default: getConfig } = require("next/config");
const { publicRuntimeConfig: { sentryDSN } } = getConfig();

if (sentryDSN) {
  Sentry.init({ dsn: sentryDSN });
}

๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.

ํ˜„์žฌ next.js์— ๋Œ€ํ•œ ์˜ค๋ฅ˜๋ฅผ ์žก๋Š” ์˜ฌ๋ฐ”๋ฅธ ์œ„์น˜๊ฐ€ ๋ฌด์—‡์ธ์ง€ ๋งค์šฐ ํ˜ผ๋ž€์Šค๋Ÿฝ์Šต๋‹ˆ๋‹ค. _app.tsx, _error.tsx ๋ฐ _document.tsx๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ผ๋ถ€ ํ•ญ๋ชฉ์€ _error.tsx์—์„œ ํฌ์ฐฉ๋˜์–ด์•ผ ํ•œ๋‹ค๋Š” ์˜๊ฒฌ์ด ์žˆ์Šต๋‹ˆ๋‹ค(์˜ˆ: https://github.com/zeit/next.js/pull/5727/files #r235981700) ๊ทธ๋Ÿฌ๋‚˜ ํ˜„์žฌ ์˜ˆ์ œ์—์„œ๋Š” _app.tsx ๋˜๋Š” _app ๋ฐ _document๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์ง€๊ธˆ ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ๋ชจ๋‘ ์‹œ๋„ํ–ˆ๋Š”๋ฐ ssr ์ค‘์— ๋ฐœ์ƒํ•˜๋Š” ์ผ๋ถ€ ์˜ค๋ฅ˜๊ฐ€ ์ €์—๊ฒŒ ์žกํžˆ์ง€ ์•Š๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์ด์ œ ์–ด๋–ค ์žฅ์†Œ๊ฐ€ ์˜ค๋ฅ˜๋ฅผ ํ™•์ธํ•˜๊ธฐ์— ์˜ฌ๋ฐ”๋ฅธ ์žฅ์†Œ์ž…๋‹ˆ๊นŒ? ๋‚˜์—๊ฒŒ ๊ฐ€์žฅ ๋…ผ๋ฆฌ์ ์ธ ๊ฒƒ์€ _app์˜ componentDidCatch์™€ _error์˜ getInitialProps๋ฅผ ํ™•์ธํ•˜๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์˜ค๋ฅ˜์˜ ๊ฒฝ์šฐ์— ์ œ๊ณต๋˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. _document์˜ ์ „์ฒด process.on ๋ถ€๋ถ„์— ๋Œ€ํ•ด ์•ฝ๊ฐ„ ํ˜ผ๋ž€์Šค๋Ÿฝ์Šต๋‹ˆ๋‹ค.

๋” ๊นŠ์ด ์žˆ๋Š” ์ง€์‹์„ ๊ฐ€์ง„ ์‚ฌ๋žŒ์ด ์ด ์งˆ๋ฌธ์„ ํ•ด๊ฒฐํ•ด ์ฃผ์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?

@abraxxas process.on in _document.js ์€ ์„œ๋ฒ„์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ค๋ฅ˜๋ฅผ ํฌ์ฐฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์š”์•ฝํ•˜์ž๋ฉด,

  • _document.js ๋Š” _์„œ๋ฒ„์ธก ์ „์šฉ_์ด๋ฉฐ ์ดˆ๊ธฐ ์„œ๋ฒ„์ธก ๋ Œ๋”๋ง ๋ฌธ์„œ ๋งˆํฌ์—…์„ ๋ณ€๊ฒฝํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • _app.js ๋Š” _ํด๋ผ์ด์–ธํŠธ ์ธก ์ „์šฉ_์ด๋ฉฐ ํŽ˜์ด์ง€๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ์„œ๋ฒ„์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด process.on ๋ฅผ ํ†ตํ•ด Sentry์— ์˜ค๋ฅ˜๋ฅผ ๋˜์ง€๊ณ  ํด๋ผ์ด์–ธํŠธ๋Š” ๊ธฐ๋ณธ ์˜ค๋ฅ˜ ํŽ˜์ด์ง€ ๋˜๋Š” _error.js ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.

์ด๊ฒƒ์ด ๋„์›€์ด ๋˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค: https://leerob.io/blog/configuring-sentry-for-nextjs-apps/

_app.js๋Š” ํด๋ผ์ด์–ธํŠธ ์ธก ์ „์šฉ์ด ์•„๋‹ˆ์ง€๋งŒ componentDidCatch ๋Š”

@timneutkens ์ข‹์•„, ๊ทธ๊ฒƒ์€ ๋‚ด ๋‘๋‡Œ๋ฅผ ์•ฝ๊ฐ„ ์ฃผ๋ฆ„์ง€๊ฒŒํ•ฉ๋‹ˆ๋‹ค. ๋ฌธ์„œ๋Š” ๋‚ด๊ฐ€ ๋งˆ์ง€๋ง‰์œผ๋กœ ๋ณธ ์ดํ›„ ๋กœ ์—…๋ฐ์ดํŠธ๋œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค . _app.js ๊ฐ€ ํด๋ผ์ด์–ธํŠธ ์ธก์ด ์•„๋‹Œ ๋ฐฉ๋ฒ•์„ ์ข€ ๋” ์ž์„ธํžˆ ์„ค๋ช…ํ•ด ์ฃผ์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?

@timneutkens ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ์œ„์น˜์™€ ์ด์œ ๋ฅผ ์„ค๋ช…ํ•ด ์ฃผ์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? ์˜๊ฒฌ์ด ๋„ˆ๋ฌด ๋งŽ์€ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

@timneutkens ์ข‹์•„, ๊ทธ๊ฒƒ์€ ๋‚ด ๋‘๋‡Œ๋ฅผ ์•ฝ๊ฐ„ ์ฃผ๋ฆ„์ง€๊ฒŒํ•ฉ๋‹ˆ๋‹ค. ๋ฌธ์„œ๋Š” ๋‚ด๊ฐ€ ๋งˆ์ง€๋ง‰์œผ๋กœ ๋ณธ ์ดํ›„ ๋กœ ์—…๋ฐ์ดํŠธ๋œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค . _app.js ๊ฐ€ ํด๋ผ์ด์–ธํŠธ ์ธก์ด ์•„๋‹Œ ๋ฐฉ๋ฒ•์„ ์ข€ ๋” ์ž์„ธํžˆ ์„ค๋ช…ํ•ด ์ฃผ์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?

์•ˆ๋…•ํ•˜์„ธ์š” ๋ฆฌ๋กญ์ž…๋‹ˆ๋‹ค!
๋ฐฉ๊ธˆ ๊ท€ํ•˜์˜ ์˜ˆ์— ๋Œ€ํ•œ ๋ณ‘ํ•ฉ ์š”์ฒญ์— ๋Œ€ํ•œ ์งˆ๋ฌธ์„ ๊ฒŒ์‹œํ–ˆ์Šต๋‹ˆ๋‹ค.
https://github.com/zeit/next.js/pull/7360#issuecomment -514318899

๊ทธ ์งˆ๋ฌธ์— ์ถ”๊ฐ€ํ•˜์—ฌ ... ๋‚˜๋Š” ๋˜ํ•œ ์„œ๋ฒ„ ์ธก์˜ render()์—์„œ ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋ ค๊ณ  ์‹œ๋„ํ–ˆ์Šต๋‹ˆ๋‹ค(์ €๋Š” raiseErrorInRender: true๋กœ ์ƒํƒœ๋ฅผ ์‹œ์ž‘ํ–ˆ์œผ๋ฏ€๋กœ ์„œ๋ฒ„ ์ธก์ธ ์ฒซ ๋ฒˆ์งธ ๋ Œ๋”๋ง์€ ์ด๋ฏธ ์˜ค๋ฅ˜๋ฅผ ๋˜์งˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค). ๊ทธ ์˜ค๋ฅ˜๋„ Sentry์— ์˜ํ•ด ํฌ์ฐฉ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ด๋ฅผ ํ…Œ์ŠคํŠธํ–ˆ์œผ๋ฉฐ ์„œ๋ฒ„ ์ธก ์˜ค๋ฅ˜๊ฐ€ ์‹ค์ œ๋กœ ์บก์ฒ˜๋˜๊ณ  ์žˆ์Šต๋‹ˆ๊นŒ? ๋„ฅ์ŠคํŠธ9 ์‚ฌ์šฉ์ค‘์ž…๋‹ˆ๋‹ค.

๊ทธ๊ฒƒ์ด ์‚ฌ์‹ค์ด๊ณ  ํด๋ผ์ด์–ธํŠธ ์ธก ์˜ค๋ฅ˜๋งŒ ์บก์ฒ˜๋˜๋Š” ๊ฒฝ์šฐ _document.js ํŒŒ์ผ์„ ์žฌ์ •์˜ํ•  ์ด์œ ๋„ ์—†์Šต๋‹ˆ๋‹ค.

๋„์›€์„ ์ฃผ์…”์„œ ๋ฏธ๋ฆฌ ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค!

@timneutkens ์ข‹์•„, ๊ทธ๊ฒƒ์€ ๋‚ด ๋‘๋‡Œ๋ฅผ ์•ฝ๊ฐ„ ์ฃผ๋ฆ„์ง€๊ฒŒํ•ฉ๋‹ˆ๋‹ค. ๋ฌธ์„œ๋Š” ๋‚ด๊ฐ€ ๋งˆ์ง€๋ง‰์œผ๋กœ ๋ณธ ์ดํ›„ ๋กœ ์—…๋ฐ์ดํŠธ๋œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค . _app.js ๊ฐ€ ํด๋ผ์ด์–ธํŠธ ์ธก์ด ์•„๋‹Œ ๋ฐฉ๋ฒ•์„ ์ข€ ๋” ์ž์„ธํžˆ ์„ค๋ช…ํ•ด ์ฃผ์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?

๊ท€ํ•˜์˜ ์งˆ๋ฌธ์— ๋‹ตํ•˜๋ฉด _app์€ ํŽ˜์ด์ง€๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๊ธฐ ์œ„ํ•ด ์•ฑ ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ์ •์˜๋œ ๊ณณ์ž…๋‹ˆ๋‹ค. ํŽ˜์ด์ง€ ๋ณ€๊ฒฝ ๊ฐ„์— ๋ ˆ์ด์•„์›ƒ์„ ์œ ์ง€ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ(๋ชจ๋“  ํŽ˜์ด์ง€์—์„œ ๋™์ผํ•œ _app ์‚ฌ์šฉ) ๋˜๋Š” redux๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์™€ ๊ฐ™์€ ๊ฒฝ์šฐ ์ด๋ฅผ ์žฌ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์„œ๋ฒ„ ์ธก์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์ฒซ ๋ฒˆ์งธ ๋ Œ๋”๋ง์ด๋ผ๋„ _app์˜ ์ฝ˜ํ…์ธ ๋ฅผ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค(ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ๋งŒ์ด ์•„๋‹˜).

๊ทธ ์งˆ๋ฌธ์— ์ถ”๊ฐ€ํ•˜์—ฌ ... ๋‚˜๋Š” ๋˜ํ•œ ์„œ๋ฒ„ ์ธก์˜ render()์—์„œ ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋ ค๊ณ  ์‹œ๋„ํ–ˆ์Šต๋‹ˆ๋‹ค(์ €๋Š” raiseErrorInRender: true๋กœ ์ƒํƒœ๋ฅผ ์‹œ์ž‘ํ–ˆ์œผ๋ฏ€๋กœ ์„œ๋ฒ„ ์ธก์ธ ์ฒซ ๋ฒˆ์งธ ๋ Œ๋”๋ง์€ ์ด๋ฏธ ์˜ค๋ฅ˜๋ฅผ ๋˜์งˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค). ๊ทธ ์˜ค๋ฅ˜๋„ Sentry์— ์˜ํ•ด ํฌ์ฐฉ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

ํ•ด๋‹น ์˜ค๋ฅ˜๊ฐ€ ์„ผํŠธ๋ฆฌ์— ํ‘œ์‹œ๋˜๋„๋ก ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ ๊ด€๋ฆฌํ–ˆ์Šต๋‹ˆ๊นŒ? _error์˜ getInitialProps์—์„œ ์บก์ฒ˜๋ฅผ ์‹œ๋„ํ–ˆ์ง€๋งŒ ์„ผํŠธ๋ฆฌ์—๋Š” ์•„๋ฌด ๊ฒƒ๋„ ํ‘œ์‹œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ ์ €๋Š” ์„œ๋ฒ„์—์„œ ์˜ค๋ฅ˜๋ฅผ ์บก์ฒ˜ํ•˜๋Š” ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ์ฐพ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ ์ค‘ ์ผ๋ถ€(๋Œ€๋ถ€๋ถ„ API ์˜ค๋ฅ˜์— ์—ฐ๊ฒฐ๋œ ๊ฒฝ์šฐ)๊ฐ€ ํ‘œ์‹œ๋˜์ง€๋งŒ errortestpage์—์„œ ๊ฐ„๋‹จํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜์—ฌ ์„ผํŠธ๋ฆฌ์— ํ‘œ์‹œ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

๊ทธ ์งˆ๋ฌธ์— ์ถ”๊ฐ€ํ•˜์—ฌ ... ๋‚˜๋Š” ๋˜ํ•œ ์„œ๋ฒ„ ์ธก์˜ render()์—์„œ ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋ ค๊ณ  ์‹œ๋„ํ–ˆ์Šต๋‹ˆ๋‹ค(์ €๋Š” raiseErrorInRender: true๋กœ ์ƒํƒœ๋ฅผ ์‹œ์ž‘ํ–ˆ์œผ๋ฏ€๋กœ ์„œ๋ฒ„ ์ธก์ธ ์ฒซ ๋ฒˆ์งธ ๋ Œ๋”๋ง์€ ์ด๋ฏธ ์˜ค๋ฅ˜๋ฅผ ๋˜์งˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค). ๊ทธ ์˜ค๋ฅ˜๋„ Sentry์— ์˜ํ•ด ํฌ์ฐฉ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

ํ•ด๋‹น ์˜ค๋ฅ˜๊ฐ€ ์„ผํŠธ๋ฆฌ์— ํ‘œ์‹œ๋˜๋„๋ก ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ ๊ด€๋ฆฌํ–ˆ์Šต๋‹ˆ๊นŒ? _error์˜ getInitialProps์—์„œ ์บก์ฒ˜๋ฅผ ์‹œ๋„ํ–ˆ์ง€๋งŒ ์„ผํŠธ๋ฆฌ์—๋Š” ์•„๋ฌด ๊ฒƒ๋„ ํ‘œ์‹œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ ์ €๋Š” ์„œ๋ฒ„์—์„œ ์˜ค๋ฅ˜๋ฅผ ์บก์ฒ˜ํ•˜๋Š” ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ์ฐพ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ ์ค‘ ์ผ๋ถ€(๋Œ€๋ถ€๋ถ„ API ์˜ค๋ฅ˜์— ์—ฐ๊ฒฐ๋œ ๊ฒฝ์šฐ)๊ฐ€ ํ‘œ์‹œ๋˜์ง€๋งŒ errortestpage์—์„œ ๊ฐ„๋‹จํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜์—ฌ ์„ผํŠธ๋ฆฌ์— ํ‘œ์‹œ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

๋ถˆํ–‰ํžˆ๋„. ๋‚ด๊ฐ€ ๊ตฌํ˜„ํ•  ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๋Š” ์†”๋ฃจ์…˜์€ ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ค๋ฅ˜๋ฅผ ์บก์ฒ˜ํ•˜๋Š” ํ…œํ”Œ๋ฆฟ์œผ๋กœ ์˜ˆ์ œ(์žฌ์ •์˜๋œ _document.js ํŒŒ์ผ ์ œ์™ธ)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ๋‚˜์—๊ฒŒ ๋ณด๊ณ ํ•˜์ง€ ์•Š๋Š” ํ•œ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์„œ๋ฒ„ ์ธก์—์„œ๋Š” ๋ชจ๋“  ๋กœ๊ทธ ๋ผ์ธ์„ ๋กœ๊ทธ ํŒŒ์ผ๋กœ ์ง์ ‘ ๋ฆฌ๋””๋ ‰์…˜ํ•  ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๋™์ผํ•œ ์œ„์น˜์— ๋ชจ๋“  ์˜ค๋ฅ˜๊ฐ€ ์žˆ๊ธฐ๋ฅผ ์›ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ทธ๊ฒƒ์€ ํ™•์‹คํžˆ ์ตœ์„ ์˜ ํ•ด๊ฒฐ์ฑ…์€ ์•„๋‹ˆ์ง€๋งŒ ๊ทธ๋ ‡๊ฒŒ ํ•˜๋ฉด ์ตœ์†Œํ•œ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์˜ค๋ฅ˜์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๊ฐ–๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๊ทธ๊ฒƒ์— ๋Œ€ํ•ด ์–ด๋–ป๊ฒŒ ์ƒ๊ฐํ•˜์„ธ์š”? ๊ฐ์‚ฌ ํ•ด์š”!

๊ทธ ์งˆ๋ฌธ์— ์ถ”๊ฐ€ํ•˜์—ฌ ... ๋‚˜๋Š” ๋˜ํ•œ ์„œ๋ฒ„ ์ธก์˜ render()์—์„œ ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋ ค๊ณ  ์‹œ๋„ํ–ˆ์Šต๋‹ˆ๋‹ค(์ €๋Š” raiseErrorInRender: true๋กœ ์ƒํƒœ๋ฅผ ์‹œ์ž‘ํ–ˆ์œผ๋ฏ€๋กœ ์„œ๋ฒ„ ์ธก์ธ ์ฒซ ๋ฒˆ์งธ ๋ Œ๋”๋ง์€ ์ด๋ฏธ ์˜ค๋ฅ˜๋ฅผ ๋˜์งˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค). ๊ทธ ์˜ค๋ฅ˜๋„ Sentry์— ์˜ํ•ด ํฌ์ฐฉ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

ํ•ด๋‹น ์˜ค๋ฅ˜๊ฐ€ ์„ผํŠธ๋ฆฌ์— ํ‘œ์‹œ๋˜๋„๋ก ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ ๊ด€๋ฆฌํ–ˆ์Šต๋‹ˆ๊นŒ? _error์˜ getInitialProps์—์„œ ์บก์ฒ˜๋ฅผ ์‹œ๋„ํ–ˆ์ง€๋งŒ ์„ผํŠธ๋ฆฌ์—๋Š” ์•„๋ฌด ๊ฒƒ๋„ ํ‘œ์‹œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ ์ €๋Š” ์„œ๋ฒ„์—์„œ ์˜ค๋ฅ˜๋ฅผ ์บก์ฒ˜ํ•˜๋Š” ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ์ฐพ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ ์ค‘ ์ผ๋ถ€(๋Œ€๋ถ€๋ถ„ API ์˜ค๋ฅ˜์— ์—ฐ๊ฒฐ๋œ ๊ฒฝ์šฐ)๊ฐ€ ํ‘œ์‹œ๋˜์ง€๋งŒ errortestpage์—์„œ ๊ฐ„๋‹จํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜์—ฌ ์„ผํŠธ๋ฆฌ์— ํ‘œ์‹œ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

๋ถˆํ–‰ํžˆ๋„. ๋‚ด๊ฐ€ ๊ตฌํ˜„ํ•  ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๋Š” ์†”๋ฃจ์…˜์€ ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ค๋ฅ˜๋ฅผ ์บก์ฒ˜ํ•˜๋Š” ํ…œํ”Œ๋ฆฟ์œผ๋กœ ์˜ˆ์ œ(์žฌ์ •์˜๋œ _document.js ํŒŒ์ผ ์ œ์™ธ)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ๋‚˜์—๊ฒŒ ๋ณด๊ณ ํ•˜์ง€ ์•Š๋Š” ํ•œ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์„œ๋ฒ„ ์ธก์—์„œ๋Š” ๋ชจ๋“  ๋กœ๊ทธ ๋ผ์ธ์„ ๋กœ๊ทธ ํŒŒ์ผ๋กœ ์ง์ ‘ ๋ฆฌ๋””๋ ‰์…˜ํ•  ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๋™์ผํ•œ ์œ„์น˜์— ๋ชจ๋“  ์˜ค๋ฅ˜๊ฐ€ ์žˆ๊ธฐ๋ฅผ ์›ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ทธ๊ฒƒ์€ ํ™•์‹คํžˆ ์ตœ์„ ์˜ ํ•ด๊ฒฐ์ฑ…์€ ์•„๋‹ˆ์ง€๋งŒ ๊ทธ๋ ‡๊ฒŒ ํ•˜๋ฉด ์ตœ์†Œํ•œ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์˜ค๋ฅ˜์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๊ฐ–๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๊ทธ๊ฒƒ์— ๋Œ€ํ•ด ์–ด๋–ป๊ฒŒ ์ƒ๊ฐํ•˜์„ธ์š”? ๊ฐ์‚ฌ ํ•ด์š”!

๊ทธ๊ฒƒ์ด ๋ฐ”๋กœ ์šฐ๋ฆฌ๊ฐ€ ์ง€๊ธˆ ํ•˜๊ณ  ์žˆ๋Š” ์ผ์ž…๋‹ˆ๋‹ค. ์•ฝ๊ฐ„ ํˆฌ๋ฐ•ํ•˜์ง€๋งŒ ์šฐ๋ฆฌ๊ฐ€ ํ•  ์ˆ˜ ์žˆ๋Š” ์ตœ์„ ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ผ๋ถ€ ์„œ๋ฒ„ ์ธก ์˜ค๋ฅ˜๊ฐ€ ์šฐ๋ฆฌ๋ฅผ ์œ„ํ•ด sentry์— ํ‘œ์‹œ๋˜๊ธฐ ๋•Œ๋ฌธ์— sentry ๋˜๋Š” next.js์—์„œ ๋ญ”๊ฐ€ ์ง„ํ–‰ ์ค‘์ธ ๊ฒƒ์ด ํ‹€๋ฆผ์—†๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๋‚˜๋Š” ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ์™€ ๋” ๋ณต์žกํ•œ ์˜ˆ์ œ๋ฅผ ๋ชจ๋‘ ์‹œ๋„ํ–ˆ๊ณ  ๋‘˜ ๋‹ค ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋ฏ€๋กœ ์ด ๋™์ž‘์ด ์šฐ๋ฆฌ์˜ ์„ค์ •๊ณผ ๊ด€๋ จ์ด ์—†๋‹ค๊ณ  ์ ์–ด๋„ ์–ด๋Š ์ •๋„ ํ™•์‹ ํ•ฉ๋‹ˆ๋‹ค.

์ด ์Šค๋ ˆ๋“œ์˜ ์‚ฌ๋žŒ๋“ค์€ https://github.com/zeit/next.js/pull/8684 ๋ฐ ๊ด€๋ จ ๋ฒ„๊ทธ์— ๊ด€์‹ฌ์ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Next.js๊ฐ€ ์ฒ˜๋ฆฌํ•˜๋Š” ์˜ˆ์™ธ์™€ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š๋Š” ์˜ˆ์™ธ๋ฅผ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ ์˜ˆ์™ธ์— ๋Œ€ํ•œ 12๊ฐ€์ง€ ๋‹ค๋ฅธ ํ…Œ์ŠคํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๋ฌธ์ œ์— ๋Œ€ํ•œ ์†Œ์‹์ด ์žˆ์Šต๋‹ˆ๊นŒ?

๋‚ด ์†”๋ฃจ์…˜์€ redux ์‚ฌ์šฉํ•˜์—ฌ state ์— ๋…ธ๋“œ ๊ฐ€์ ธ์˜ค๊ธฐ ๋ฐ์ดํ„ฐ ์˜ค๋ฅ˜๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ใ€‚๊ทธ๋Ÿฐ ๋‹ค์Œ componentDidMount ์˜ _app.js ์กฐ์น˜๋ฅผ ์ทจํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค(์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ๊ฒฝ๊ณ  ๋˜๋Š” ์˜ค๋ฅ˜ ์š”์ฒญ ์š”์ฒญ ๋ฐฑ์—”๋“œ๋กœ).
์ฝ”๋“œ๋Š” next-antd-scaffold_server-error ์— ์žˆ์Šต๋‹ˆ๋‹ค.

========> ์ƒํƒœ

import {
  SERVER_ERROR,
  CLEAR_SERVER_ERROR
} from '../../constants/ActionTypes';

const initialState = {
  errorType: []
};

const serverError = (state = initialState, { type, payload }) => {
  switch (type) {
    case SERVER_ERROR: {
      const { errorType } = state;
      errorType.includes(payload) ? null : errorType.push(payload);
      return {
        ...state,
        errorType
      };
    }
    case CLEAR_SERVER_ERROR: {
      return initialState;
    }
    default:
      return state;
  }
};

export default serverError;

=======> ์•ก์…˜

import {
  SERVER_ERROR
} from '../../constants/ActionTypes';

export default () => next => action => {
  if (!process.browser && action.type.includes('FAIL')) {
    next({
      type: SERVER_ERROR,
      payload: action.type 
    });
  }
  return next(action);
};

=======> _app.js

...
componentDidMount() {
    const { store: { getState, dispatch } } = this.props;
    const { errorType } = getState().serverError;
    if (errorType.length > 0) {
      Promise.all(
        errorType.map(type => message.error(`Node Error, Code๏ผš${type}`))
      );
      dispatch(clearServerError());
    }
  }
...

_unortodox_ ์†”๋ฃจ์…˜์„ ์‚ฌ๋ž‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

function installErrorHandler(app) {
  const _renderErrorToHTML = app.renderErrorToHTML.bind(app)
  const errorHandler = rollbar.errorHandler()

  app.renderErrorToHTML = (err, req, res, pathname, query) => {
    if (err) {
      errorHandler(err, req, res, () => {})
    }

    return _renderErrorToHTML(err, req, res, pathname, query)
  }

  return app
}
// ยฏ\_(ใƒ„)_/ยฏ

404 ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด err ์ธ์ˆ˜๊ฐ€ null์ด ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ด ๋ฐฉ๋ฒ•์„ 404 ์˜ค๋ฅ˜์— ์ ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

Elastic APM ๋…ธ๋“œ ํด๋ผ์ด์–ธํŠธ(https://www.npmjs.com/package/elastic-apm-node)์— ๋Œ€ํ•ด ์ด๊ฒƒ์„ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ด€์‹ฌ ์žˆ๋Š” ์‚ฌ๋žŒ:

next.config.js :

webpack: (config, { isServer, webpack }) => {
      if (!isServer) {
        config.node = {
          dgram: 'empty',
          fs: 'empty',
          net: 'empty',
          tls: 'empty',
          child_process: 'empty',
        };

        // ignore apm (might use in nextjs code but dont want it in client bundles)
        config.plugins.push(
          new webpack.IgnorePlugin(/^(elastic-apm-node)$/),
        );
      }

      return config;
} 

๊ทธ๋Ÿฐ ๋‹ค์Œ _error.js render func์—์„œ captureError๋ฅผ ์ˆ˜๋™์œผ๋กœ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

function Error({ statusCode, message, err }) {

const serverSide = typeof window === 'undefined';

  // only run this on server side as APM only works on server
  if (serverSide) {
    // my apm instance (imports elastic-apm-node and returns  captureError)
    const { captureError } = require('../src/apm'); 

    if (err) {
      captureError(err);
    } else {
      captureError(`Message: ${message}, Status Code: ${statusCode}`);
    }
  }

}

๋ชจ๋‘ ์•ˆ๋…•! Express ์„œ๋ฒ„๋ฅผ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š๊ณ  Next.js์—์„œ Express ์Šคํƒ€์ผ ์•„ํ‚คํ…์ฒ˜์šฉ NPM ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค. ์„œ๋ฒ„ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ๋ฌธ์ œ์— ๋„์›€์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค! ๊ด€์‹ฌ์ด ์žˆ๋‹ค๋ฉด ํ™•์ธํ•˜์‹ญ์‹œ์˜ค. https://github.com/oslabs-beta/connext-js

๋ˆ„๊ตฌ๋‚˜ getServerSideProps ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ํฌ์ฐฉ(๋ฐ ์ฒ˜๋ฆฌ)ํ–ˆ์Šต๋‹ˆ๊นŒ?

๋ˆ„๊ตฌ๋‚˜ getServerSideProps ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ํฌ์ฐฉ(๋ฐ ์ฒ˜๋ฆฌ)ํ–ˆ์Šต๋‹ˆ๊นŒ?

๋ถˆ๋ช…ํ™•ํ•ฉ๋‹ˆ๋‹ค. ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ๋ชจ๋‘์—์„œ ์˜ค๋ฅ˜๋ฅผ ๊ธฐ๋กํ•˜๋Š” ๊ด€์šฉ์ ์ธ ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•  ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค ๐Ÿคทโ€โ™‚๏ธ

๋ˆ„๊ตฌ๋‚˜ getServerSideProps ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ํฌ์ฐฉ(๋ฐ ์ฒ˜๋ฆฌ)ํ–ˆ์Šต๋‹ˆ๊นŒ?

@stephankaag

์˜ˆ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒƒ์ด ์ €์—๊ฒŒ ํšจ๊ณผ์ ์ด์—ˆ์Šต๋‹ˆ๋‹ค.

๋จผ์ € ์ถฉ๋Œ ๋ณด๊ณ ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. Sentry ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์€ ์ž‘๋™ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— CrashReporter ํด๋ž˜์Šค ์•ˆ์— Sentry ๋ž˜ํ•‘ํ–ˆ์Šต๋‹ˆ๋‹ค(์˜ˆ: req.getCrashReporter = () => Sentry ).

// crash-reporter.js

const Sentry = require("@sentry/node");

class CrashReporter {
  constructor(){
    Sentry.init({ dsn: process.env.SENTRY_DSN });
  }
  captureException(ex){
    return Sentry.captureException(ex);
  }
}

function crashReporterMiddleware(req, res, next) {
  req.getCrashReporter = () => new CrashReporter();
  next();
}

module.exports = crashReporterMiddleware;

๋‹ค์Œ์€ ๋ฌผ๋ก  Next.js ์š”์ฒญ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์„ค์ •๋˜๊ธฐ ์ „์— ์•ฑ์— ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค.

// server.js

const crashReporterMiddleware = require("./middleware/crash-reporter")

...

app.use(crashReporterMiddleware);

...

setHandler((req, res) => {
  return handle(req, res);
});

getServerSideProps ๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ๋งˆ๋‹ค :

// _error.js

export async function getServerSideProps({req, res, err}) {
  const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
  const crashReporter = req.getCrashReporter();
  const eventId = crashReporter.captureException(err);
  req.session.eventId = eventId;
  return {
    props: { statusCode, eventId }
  }
}
์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰