์๋
,
์ ๋ ์๋ฒ ์ธก๊ณผ ํด๋ผ์ด์ธํธ ์ธก ๋ชจ๋์์ 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 ์ค๋ฅ๋ฅผ ์๋ฆด ์ ์๋ ๋ฐฉ๋ฒ์ด ์์ต๋๊น?
ํด๋ผ์ด์ธํธ ์ธก์์ ์ค๋ฅ๋ฅผ ๊ธฐ๋กํ๊ธฐ ์ํด ๋ค์์ ์ํํ์ต๋๋ค.
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 ์์ฑ์ ๋ฐ ์ ์ง ๊ด๋ฆฌ์์๊ฒ, ๋ ๋๋ง๋๋ ๋ด์ฉ์ ์ ์ดํ๊ธฐ ์ํด ๋ค์ ์์ด๋์ด ์ค ์ด๋ค ๊ฒ์ด ํ์ฉ/๊ฐ๋ฅํ๋ค๊ณ ์๊ฐํ์ญ๋๊น?
catch
๋ธ๋ก์ ํํฌ๋ฅผ ๋์
ํ์๊ฒ ์ต๋๊น?์ด๋ฐ ์ข ๋ฅ์ ์ค๋ฅ๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํด 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')
@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
๋ณ๊ฒฝ ์ฌํญ ์ ์ฉ:
ReferenceError: XMLHttpRequest is not defined
์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.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 ์ผํธ๋ฆฌ ์ค๋ฅ ์ฒ๋ฆฌ๊ธฐ๋ฅผ ๊ฑด๋๋ฐ์ง ์๋๋ก ๋ง๋๋ ๊ฒ์ด ์ด์์ ์ ๋๊น?).
๋ ๊ฐ์ง ์ฃผ์ ๋ฌธ์ ๋ก ์๋ํ๋ ๊ฒ ๊ฐ์ต๋๋ค.
์ค์ ๋ก ์ฌ์ฉ์ ์ ์ ์ค๋ฅ์ getInitialProps๋ฅผ ์ถ๊ฐ๋ก ํ ์คํธํ ํ ์ด๋ค ์ด์ ๋ก ์ธํด ์ฌ์ฉ์ ์ ์ _app์ getInitialProps ๋ด๋ถ์์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด ํ๋ก๋์ ํ๊ฒฝ์์๋ ์คํ๋์ง ์์ต๋๋ค.
์, ๋ฉฐ์น ๋์ ์๋ํ ํ ํ์คํ ์ด์ํ ํ๋์ด ๋ํ๋ฌ์ต๋๋ค. ์ฐ๋ฆฌ๊ฐ ์ง๋ฉดํ ์ฃผ์ ๋ฌธ์ ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
@sentry/browser
๋ฐ SSR์ ๊ฒฝ์ฐ @sentry/node
๊ฐ์ ธ์ค๊ธฐ๋๋ #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
๋ฌธ์ ์ ๊ดํด์๋ ๋ค์๊ณผ ๊ฐ์ ๊ฐ๋ฅ์ฑ์ด ์์ต๋๋ค.
_app.componentDidCatch
, SSR์ ๋ํด ์คํ๋์ง ์์_error.getInitialProps
, ๋ง์ ๋ฌธ์ ๊ฐ ์์ต๋๋ค.๋ด ์ง๊ฐ์ผ๋ก๋ ๋ค์๋ณด๋ค ๋จผ์ 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 ๊ตฌ์ฑ ์์ ๋ฐ ์ค๋ฅ ์ฒ๋ฆฌ์ ๋ํ ์ถ๊ฐ ์ฐธ๊ณ ์ฌํญ์ ๋๋ค.
_app.js
๋ _error.js
๋๋ 404์ ๊ฐ์ ํ์ด์ง๋ฅผ ํฌํจํ ๋ชจ๋ ํ์ด์ง์ ์ฌ์ฉ๋๋ฏ๋ก ctx.err
๊ฐ ์ ๋ฌ๋ ๋ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ง ์๋์ง ํ์ธํ๊ณ ์ถ์ต๋๋ค._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์ ์ด์์ต๋๋ค. ๊ทธ๊ฒ์ ๋ชจ๋ ์ข
๊ณผ ํํ๋์ ๊ฐ์ง๊ณ ์์ง ์์ง๋ง ๊ทธ๊ฒ์ ๋๋ฅผ ์ํด ์ผํ๊ณ ์์ต๋๋ค.
์ฌ์ฉ์ ์ ์ _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());
}
}
...
https://github.com/zeit/next.js/issues/1852#issuecomment -353671222 ๋๋ถ์ ์๋ฒ ์ธก ๋กค๋ฐ ์ค๋ฅ ๋ณด๊ณ ์์ ์์ ๋ฅผ ์ป์์ต๋๋ค.
_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 }
}
}
๊ฐ์ฅ ์ ์ฉํ ๋๊ธ
_unortodox_ ์๋ฃจ์ ์ ์ฌ๋ํด์ผ ํฉ๋๋ค.