ããã«ã¡ã¯ã
ãµãŒããŒåŽãšã¯ã©ã€ã¢ã³ãåŽã®äž¡æ¹ã§ãSentryããŒã«ã«ãšã©ãŒãéä¿¡ãããç¶æ³ã«ãããŸãã
ãã®ã¢ããªã¯ãExpressãã«ã¹ã¿ã ãµãŒããŒãšããŠäœ¿çšããŸãã åºæ¬çã«ã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ã«ãã£ãŠãã£ãããããŸãããããåŠçããå¯äžã®æ¹æ³ã¯ã _error.js
ããŒãžãã¡ã€ã«ããªãŒããŒã©ã€ãããããšã§ãã
ãã®_error.js
ãã¡ã€ã«å
ã§ãSentryã«ãšã©ãŒãå ±åããããã®æ®éçãªæ¹æ³ãå¿
èŠã§ãã çŸåšã2ã€ã®ã©ã€ãã©ãªããããŸãïŒããŒãçšã®raven
ãšjavascriptçšã®raven-js
ïŒã åé¡ã¯ã raven
ãSSRã§æ©èœããããwebpackããã³ãã«ããã«ããããšãã«å€±æããXMLHTTPRequestã®äŸåé¢ä¿ã®ããã«raven-js
倱æãããããäž¡æ¹ãã€ã³ããŒãã§ããªãããšã§ãã
ãµãŒããŒåŽã§next.jsãšã©ãŒãéç¥ããæ¹æ³ã¯ãããŸããïŒ
ã¯ã©ã€ã¢ã³ãåŽã§ãšã©ãŒããã°ã«èšé²ããããã«ã次ã®ããšãè¡ã£ãŠããŸãã
https://gist.github.com/jgautheron/044b88307d934d486f59ae87c5a5a5a0
åºæ¬çã«ãµãŒããŒã«ãšã©ãŒãéä¿¡ããŸãããšã©ãŒã¯æçµçã«stdout
ã«åºåããã Docker
ãã®ã³ã°ãã©ã€ããŒã«ãã£ãŠãã£ãããããŸãã
react-guard
ã§SSRãšã©ãŒãæ£åžžã«æ€åºããã³ã¢Nextãã¡ã€ã«ããªãŒããŒã©ã€ãããå¿
èŠã¯ãããŸããã
ç§ããã®åé¡ãæ±ããŠããŸãã ç§ã¯çåã«æããŸãïŒæ¬¡ã®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ç§ãããäžã§ææ¡ããå€æŽã¯
ãšã©ãŒãåã¹ããŒããŠããããè©ŠããŠã¿ãããšã«åæããŸãã
ãŸãã 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ããŒããæ§æããŸã
2-ã¯ã©ã€ã¢ã³ãåŽã§ravenjsãæ§æããŸãïŒ _document
ã
ãã®2ã€ã®æé ã§ãã¯ã©ã€ã¢ã³ããšãµãŒããŒã®äž¡æ¹ã§æªåŠçã®äŸå€ããã£ããããŸãã
3- _error
ããŒãžãäœæããŸãã nextjsããªã¯ãšã¹ããåŠçãããšïŒã¯ã©ã€ã¢ã³ãåŽããµãŒããŒåŽãã«é¢ä¿ãªãïŒããã®ããŒãžãã¬ã³ããªã³ã°ããããšãšã©ãŒãçºçããŸãã _errorããŒãžã®getInitialProps
ã¡ãœããã§ãæ©åšã«ãšã©ãŒãå ±åããŸãã
ã¯ã©ã€ã¢ã³ãconst Raven = require('raven-js');
ãŸãã¯ãµãŒããŒåŽconst Raven = require('raven');
ã©ã¡ãã«ãããã«å¿ããŠãåçã«ã€ã³ããŒãããŠraven-nodeãŸãã¯ravenjsã解決ããããã©ãããããŒãããæ¹æ³ã決å®ããæ¹æ³ã
raven
ã¢ãžã¥ãŒã«ïŒãµãŒããŒåŽã®ã¢ãžã¥ãŒã«ïŒããã³ãã«ããªãããã«webpackãæ§æããããšã«æ³šæããŠãã ãããããã«ããã next.config.js
ã次ã®ããã«æŽæ°ãããŸãã
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;
},
};
@acanimal
2-ã¯ã©ã€ã¢ã³ãåŽã§ravenjsãæ§æããŸãïŒããã¯_documentã§è¡ããŸããã
_document.js
raven-jsãã©ã®ããã«èšå®ãããæããŠããã ããŸããïŒ ãšã©ãŒãçºçããŠãæ©åšã¯äœãèµ·ãããªãã®ã§ãããŸããããŸããã
_error.js
ããŒãžã§ãã¹ãŠã®ãšã©ãŒãæåã§æ©åšã«éä¿¡ããå¿
èŠããããŸããïŒ
// _document constructor
constructor(props) {
super(props);
Raven
.config('...')
.install();
}
Next.jsã¯ã quietã«èšå®ããªãéãããµãŒããŒäžã®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
ãæçš¿ã§ããŸããïŒ
_error.js
ã§RavenInstance.captureException(err)
ãããªããšãããŠããŸãããçºçãããšã©ãŒã®çš®é¡ãšå ŽæãããããŸããã
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
}
// ¯\_(ã)_/¯
ã«ã¹ã¿ã Webpackæ§æãªãã§ããŒããšãã©ãŠã¶ãŒã®æ£ãã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
äž¡æ¹ãã€ã³ã¹ããŒã«ããŠãããå圢ã®Ravenãã€ã³ã¹ã¿ã³ã¹åãããã«ããŒãäœæããŸãïŒäŸïŒ lib/raven.js
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 sourcemapwepbackãã©ã°ã€ã³https://github.com/thredup/rollbar-sourcemap-webpack-plugin/pull/56ã®PRã§ã:)
@tusgavomelo ãã¹ããªãŒã ã«ååšãããšã©ãŒãå©çšããæ¹æ³ã«ã€ããŠè©³ãã説æããŠããã ããŸããïŒ
"process.stderr.write = error => yourErrorLogïŒerrorïŒ;"
ãšã©ãŒãããŒãã³ã³ãœãŒã«ã«èšé²ããããã«ããã®ã³ãŒãè¡ãã©ãã«æžãå¿ èŠããããŸããïŒ
ããã¯ã¯ã©ã€ã¢ã³ãåŽã ãã§ãã
Rollbar.error('some error')
@ teekey99 @sentry/browser
ã«ã€ããŠåæ§ã®è§£æ±ºçã¯ãããŸããïŒ ãã¶ã-sentryã®äŸã§æŽæ°ããŸããïŒ
@sheerunãããŸã§raven
ãšraven-js
䜿çšããŠããŸããã ãã¹ãŠã®æ°æ©èœã@sentry/node
ãš@sentry/browser
è¿œå ãããããããããã¯ããããéæšå¥šã«ãªãããšãèªèããŠããŸãã ãããžã§ã¯ãã§ãããã®æ°ããã©ã€ãã©ãªããŸã 䜿çšããŠããŸãããã調ã¹ãŠã¿ãŸãã å®çšçãªäŸãããã°ããããåãæ»ããŸãã
with-sentryã®äŸã¯æè¿æŽæ°ãããŸããã
@timneutkensããããŸãããããµãŒããŒãšã©ãŒããã£ããããã«ã¯é
ãããã¢ããªå
ã§SentryãåæåãããããããµãŒããŒåŽãšã©ãŒã¯ãµããŒããããŠããŸããã é©åãªè§£æ±ºçã¯ããããã©ããã§@sentry/node
䜿çšããã§ããã
@sheerunåãåé¡ãçºçããŠããŸãã @sentry/node
ã䜿çšããã®ã¯ç°¡åã§ã¯ãããŸããã ãã®Sentryã®äŸã®readmeã¯ãç§ãäœæ¥ããŠããã¢ããªã±ãŒã·ã§ã³ã«ãã§ã«ããã«ã¹ã¿ã ãµãŒããŒã®äœ¿çšãææ¡ããŠããŸãã ã«ã¹ã¿ã Express.jsãµãŒããŒã§äŸå€ããã£ããã£ããã«ã¯ãããã«ãŠã§ã¢ãåŠçããæåã®ãšã©ãŒãšããŠSentryãšã©ãŒãã³ãã©ããã«ãŠã§ã¢ãšã©ãŒãæ¿å
¥ããå¿
èŠããããŸãã 次ã®ãã³ãã©ãŒã®çŽåŸã«Sentryã®ãšã©ãŒãã³ãã©ãŒãæ¿å
¥ãããšããšã©ãŒã¯ãã®æç¹ã§ãã§ã«é£²ã¿èŸŒãŸããŠãããSentryã¯ãããèªèããŸããã
@sentry/browser
ã®getInitialProps
ã§_error.js
ã䜿çšããŠããŸãããã¯ã©ã€ã¢ã³ãåŽãšãµãŒããŒåŽã®äž¡æ¹ã§æ©èœããŠããããã§ãã @sentry/browser
ããµãŒããŒåŽã§ãµããŒããããããšã«ãªã£ãŠããã®ãã©ããã¯ããããŸããããSentryã«ã€ãã³ããåä¿¡ããŠââããŸãã
ã«ã¹ã¿ã ExpressãµãŒããŒã®ãšã³ããªãã¡ã€ã«ã§@sentry/node
Sentry.init()
ãä»ããŠ
ãããç§ã䜿çšããŠããã»ããã¢ããã®èŠç¹ã§ãïŒ https ïŒ
é¢çœãïŒ
ããã§ã¯ç¢ºãã«ããçš®ã®ã°ããŒãã«ãªç¶æ
ãèµ·ãã£ãŠããŸãïŒããã¯å°ãæããããããããããŠæãŸãããããŸããïŒã _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ãµãŒããŒããéä¿¡ããŠãããããã§ããïŒ ã©ã®ããã«ããããã¹ãããŸãããïŒ äŸã®readmeã¯ããµãŒããŒã§ã¯æ©èœããªãããšã瀺åããŠããããã§ãã
@timrogersã¯ãã確ãã«ç§ã®æªãã§ãããã¹ãããããšã©ãŒã¯ãµãŒããŒããæ¥ãŠãããšæããŸããããå®éã«ã¯ã¯ã©ã€ã¢ã³ãããããªã¬ãŒãããŸãã:(
çŸåšã®äŸwith-sentryã¯ãgetInitialPropsã§ã¹ããŒããããšã©ãŒããã£ãããããã¢ããªã±ãŒã·ã§ã³ã®ã¬ã³ããªã³ã°ããªãŒã«ãããšã©ãŒããã£ããããŠããããã§ããç§ã®å Žåããšã©ãŒã®ã»ãšãã©ã¯getInitialPropsããã®ãã®ã§ãã
@sheerunäžèšã®mcdougalåé¿çãè©Šãããšãã§ããŸããïŒ å®ç§ã§ã¯ãããŸãããïŒå¥åŠãªåææ©åšãšã©ãŒïŒããã¹ãŠã®ãšã©ãŒãçºçãããšããå°è±¡ãåããŠããããããæ£ãããªããã©ãããç¥ããããšæããŸãã trueã®å Žåãwith-sentryã®äŸã¯ãNext.jsããããæ¹åããæ¹æ³ãã¢ããã€ã¹ã§ããããã«ãªããŸã§ãããããããã§æŽæ°ããå¿ èŠããããŸãïŒçæ³çã«ã¯ãserver.jsã®æ©åšãšã©ãŒãã³ãã©ãŒãã¹ããããããªãããã«ããŸããïŒïŒã
ããã¯2ã€ã®äž»èŠãªåé¡ã§ã¡ãã£ãšããŸãããããã§ãïŒ
å®éã«ã¯ãã«ã¹ã¿ã ãšã©ãŒã®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
ãããã«ã¯å€ãã®åé¡ããããŸãç§ã®çŽæã§ã¯ãNextã®åã«Sentryã®ãšã©ãŒãã³ãã©ãŒãæ¿å ¥ããããšã§ãµãŒããŒäžã§ãããè¡ãæ¹æ³ããããšæããŸããããŸã è©ŠããŠã¿ãŸããã
çŸæç¹ã§ã¯ãNextã¯ãããè¡ãã®ã«åœ¹ç«ã€ããã¯ãæäŸããŠããŸããã ãããæ©èœããããšãããã£ãå Žåã¯ãããããè¿œå ã§ããŸãð
ãã ããNextã®ãã£ãããæ©ãããããããããæ©èœããªããªã¹ã¯ããããŸãã
@mcdougal AhïŒïŒïŒïŒ åé¿çãã
ç§ã®å®å šãªç¡ç¥ãèš±ããŠãã ããããããã
@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ã«æè¬ããŸãã 次ã®ãšã©ãŒåŠçã¯çŸæç¹ã§ã¯æé©ã§ã¯ãªãããšã«åæããŸãããšã©ãŒåŠçãªã©ãè¿œå ã§ããããã«ãæ¡åŒµå¯èœãªã¢ãžã¥ãŒã«/ããã«ãŠã§ã¢ãè¿œå ãããŠããã®ãèŠãã®ã¯çŽ æŽãããããšã§ãã
æ©åšãªã©ã®å圢ã©ã€ãã©ãªããªããããäºæ ã¯è€éã«ãªã£ãŠããŸããã€ãŸããã³ã³ããŒãã³ãã«ã©ã¡ãã®ã©ã€ãã©ãªãåçŽã«ã€ã³ããŒãã§ããªããããå®è¡æã«åçã«ã€ã³ããŒãããŠããµãŒããŒåŽãŸãã¯ãã©ãŠã¶åŽã§ãšã©ãŒãçºçãããã©ãããåžžã«ç¢ºèªããå¿ èŠããããŸãã
ãã®åé¡ã®æŽæ°ã¯ãããŸããïŒ ãããŸã§ã«è©Šããããšã¯æ¬¡ã®ãšããã§ãããã¹ãŠã®ãã©ããã³ã°ã³ãŒãã_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 }
}
@sheerunããã®next.config.jsã®è¿œå ãšãserver.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ã³ã³ã¹ãã©ã¯ã¿ãŒã§ãããå®å
šã«ãã®ã¯ã©ã¹ã®å€ã«ãããŸã
2æ53åPMã§æ°Žææ¥ã2018幎11æ21æ¥ã«ã¯abraxxas [email protected]æžããŸããïŒ
ãã®åé¡ã®æŽæ°ã¯ãããŸããïŒ ç§ããããŸã§ã«è©Šããããšã¯æ¬¡ã®ãšããã§ãã
ãã¹ãŠã®è¿œè·¡ã³ãŒãã_app.jsã«ç§»åããŸãã`
ã³ã³ã¹ãã©ã¯ã¿ãŒïŒåŒæ°ïŒä»»æïŒ{
superïŒargsïŒ
Sentry.initïŒ{
dsnïŒ 'äœãšã'ã
ç°å¢ïŒ 'ããŒã«ã«'ã
}ïŒ
Sentry.configureScopeïŒscope => {
scope.setTagïŒ 'errorOrigin'ãisServerïŒ 'SSR'ïŒ 'ã¯ã©ã€ã¢ã³ã'ïŒ
}ïŒ
}static async getInitialPropsïŒ{Componentãrouterãctx}ïŒanyïŒ{
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 / browser'}
ããã¯ã¯ã©ã€ã¢ã³ãåŽã§ã¯ãã¹ãŠã®ãšã©ãŒã远跡ããŠããããã§ããããµãŒããŒåŽã§ã¯
åèµ·ååŸã«çºçããæåã®ãšã©ãŒã®ã¿ã远跡ããŠããããã§ãã
ãµãŒãã ãã ãããµãŒããŒäžã®ãã以éã®ãšã©ãŒã¯è¿œè·¡ãããŸããã ãããšãšãã«
ã¢ãããŒããã°ã«SyntheticErrorsã¯ãããŸããããå®éã®ãšã©ãŒã®ã¿ããããŸããããã§ããããã¯ç§ã«ã¯ããªãããããŒã ãšæããŸãããµãŒããŒåŽã®è¿œè·¡ã¯
åããŠåäœããå Žåã®ã¿ããŸã 䜿çšã§ããŸãããâ
ããªããèšåãããã®ã§ãããªãã¯ãããåãåã£ãŠããŸãã
ãã®ã¡ãŒã«ã«çŽæ¥è¿ä¿¡ããGitHubã§è¡šç€ºããŠãã ãã
https://github.com/zeit/next.js/issues/1852#issuecomment-440668980 ããŸãã¯ãã¥ãŒã
ã¹ã¬ãã
https://github.com/notifications/unsubscribe-auth/AAR2DeIhoOj6PdWRA2VqiEZyrO5Jui8vks5uxVrHgaJpZM4NOQlp
ã
ç§ã¯ãã§ã«ããã移åããããšããŸããããããã§ãåãåäœã§ãã @sheerunã»ããã¢ããã®æå°éã®èŠç¹ãæçš¿ã§ããŸããïŒ æäŸãããã¹ããããã䜿çšããŠèšå®ããããšããŸããããæ©èœããŸããã å šäœãéåžžã«è€éã«èŠããã®ã§ãç§ã¯ããªãåçŽãªã¿ã¹ã¯ã§ãããšæåŸ ããŠããŸãã:(ãµãŒããŒã§æ©åšãåæåããŠããŸããããããšãã¯ã©ã¹å€ã®_app.jsã§ã®ã¿åæåããŠããŸããïŒ
å ¬åŒã®æ©åšã®äŸãæŽæ°ããŸããããè€éãããããšããŠæåŠãããã®ã§ã¯ãªãããšæããŸãããæéãããã°æçš¿ãè©Šã¿ãããšãã§ããŸãã
@sheerunå ¬åŒã®äŸã«æŽæ°ããããšããŠããéåžžã«äŸ¡å€ããããŸãã SyntheticErrorsãªãã§sentryssrãåäœãããããããŸãã¯çºçããæåã®ãµãŒããŒãšã©ãŒã®ã¿ãèšé²ããããã«å¿ èŠãªæå°éã®è€éãã§ããã°ãããŒãžããããšæããŸãã 次ã«ãããããããããæ¹åããæ¹æ³ããŸãã¯nextjsã³ã¢ã®æ¹åãŸãã¯æ©åšååãµããŒããæšé²ããæ¹æ³ãèŠã€ããããšãã§ããŸãã
ããã«è¡ããŸã..httpsïŒ//github.com/zeit/next.js/pull/5727
å¿ ç¶çã«è€éãªå®äŸãã§ããã®ã§ãç¶æ³ãæ¹åããããã®æ¬¡ã®ã¹ãããã¯äœã§ããã
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())
}
}
ãã®ãããªAPIã®ææ¡ãïŒ6922ã§å®è£ ããŸãã
ã³ã³ãã©ã¯ãã.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 });
}
ããã¯ããããå©ããã
Tbhç§ã¯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
ã§_document.js
ããµãŒããŒäžã§èµ·ãããšã©ãŒããã£ãããããŸããã èŠçŽãããšã
_document.js
ã¯_ãµãŒããŒåŽã®ã¿_ã§ããããµãŒããŒåŽã§ã¬ã³ããªã³ã°ãããæåã®ããã¥ã¡ã³ãããŒã¯ã¢ãããå€æŽããããã«äœ¿çšãããŸãã_app.js
ã¯_ã¯ã©ã€ã¢ã³ãåŽã®ã¿_ã§ãããããŒãžã®åæåã«äœ¿çšãããŸãããããã£ãŠããµãŒããŒã§ãšã©ãŒãçºçãããšã process.on
ãä»ããŠSentryã«ãšã©ãŒãã¹ããŒãããã¯ã©ã€ã¢ã³ãã¯ããã©ã«ãã®ãšã©ãŒããŒãžãŸãã¯_error.js
ãã¬ã³ããªã³ã°ããŸãã
ããŸãããã°ãããã圹ç«ã€ã§ãããïŒ https ïŒ
_app.jsã¯ã¯ã©ã€ã¢ã³ãåŽã®ã¿ã§ã¯ãããŸãããã componentDidCatch
ã¯
@timneutkensããŠãããã¯ç§ã®è³ãå°ãããã«ããŸãã ååèŠãæããããã¥ã¡ã³ããæŽæ°ãããŠ_app.js
ãã¯ã©ã€ã¢ã³ããµã€ãã§ã¯ãªãããšã詳ãã説æããŠããã ããŸããïŒ
@timneutkensãããããã©ãã§ããªããšã©ãŒããã£ããããã®ã説æã§ããŸããïŒ ããããã®æèŠãããããã§ãã
@timneutkensããŠãããã¯ç§ã®è³ãå°ãããã«ããŸãã ååèŠãæããããã¥ã¡ã³ããæŽæ°ãããŠ
_app.js
ãã¯ã©ã€ã¢ã³ããµã€ãã§ã¯ãªãããšã詳ãã説æããŠããã ããŸããïŒ
ããã«ã¡ã¯ãªãŒããïŒ
ããªãã®äŸã®ããŒãžãªã¯ãšã¹ãã«ã€ããŠè³ªåãæçš¿ããŸããïŒ
https://github.com/zeit/next.js/pull/7360#issuecomment -514318899
ãã®è³ªåã«å ããŠ...ç§ã¯ãµãŒããŒåŽã®renderïŒïŒã§ãšã©ãŒãã¹ããŒããããšããŸããïŒraiseErrorInRenderïŒtrueã§ç¶æ ãéå§ããã®ã§ããµãŒããŒåŽã®æåã®ã¬ã³ããªã³ã°ã¯ãã§ã«ãšã©ãŒãã¹ããŒããŸãïŒããããŠãã®ãšã©ãŒãSentryã«ãã£ãŠãã£ããã£ãããŸããã§ããã
ã¢ããªã±ãŒã·ã§ã³ã§ããããã¹ãããŸãããïŒãããã®ãµãŒããŒåŽã®ãšã©ãŒã¯å®éã«ãã£ããã£ãããŠããŸããïŒ Next9ã䜿çšããŠããŸãã
ãããå®éã«åœãŠã¯ãŸããã¯ã©ã€ã¢ã³ãåŽã®ãšã©ãŒã®ã¿ããã£ããã£ãããŠããå Žåã¯ã_document.jsãã¡ã€ã«ããªãŒããŒã©ã€ãããçç±ããããŸããã
å©ããŠãããŠããããšãïŒ
@timneutkensããŠãããã¯ç§ã®è³ãå°ãããã«ããŸãã ååèŠãæããããã¥ã¡ã³ããæŽæ°ãããŠ
_app.js
ãã¯ã©ã€ã¢ã³ããµã€ãã§ã¯ãªãããšã詳ãã説æããŠããã ããŸããïŒ
質åã«çãããšã_appã¯ãããŒãžãåæåããããã«Appã³ã³ããŒãã³ããå®çŸ©ãããŠããå Žæã§ãã ããŒãžå€æŽã®éã«æ°žç¶çãªã¬ã€ã¢ãŠããå¿ èŠãªå ŽåïŒãã¹ãŠã®ããŒãžãåã_appãå©çšããïŒããŸãã¯reduxãå©çšããå Žåãªã©ãããããªãŒããŒã©ã€ãããŸãã ãããã£ãŠããµãŒããŒåŽã§çºçããæåã®ã¬ã³ããªã³ã°ã§ããã_appã®ã³ã³ãã³ããã¬ã³ããªã³ã°ããŸãïŒã¯ã©ã€ã¢ã³ãåŽã®ã¿ã§ã¯ãããŸããïŒã
ãã®è³ªåã«å ããŠ...ç§ã¯ãµãŒããŒåŽã®renderïŒïŒã§ãšã©ãŒãã¹ããŒããããšããŸããïŒraiseErrorInRenderïŒtrueã§ç¶æ ãéå§ããã®ã§ããµãŒããŒåŽã®æåã®ã¬ã³ããªã³ã°ã¯ãã§ã«ãšã©ãŒãã¹ããŒããŸãïŒããããŠãã®ãšã©ãŒãSentryã«ãã£ãŠãã£ããã£ãããŸããã§ããã
ãã®ãšã©ãŒãæ©åšã«è¡šç€ºãããããã«ä»ã®æ¹æ³ã§ç®¡çããŸãããïŒ _errorã®getInitialPropsã§ãã£ããã£ããããšããŸããããæ©åšã§ã¯äœã衚瀺ãããŸããã çŸåšããµãŒããŒäžã®ãšã©ãŒããã£ããã£ããä¿¡é Œã§ããæ¹æ³ã1ã€èŠã€ããŠããŸããããã®ãã¡ã®ããã€ãã¯ïŒã»ãšãã©ã®å ŽåãAPIé害ã«æ¥ç¶ãããŠããå ŽåïŒè¡šç€ºãããŸããããšã©ãŒãã¹ãããŒãžããåçŽãªãšã©ãŒãååŸããŠæ©åšã«è¡šç€ºããããšã¯ã§ããŸããã§ããã
ãã®è³ªåã«å ããŠ...ç§ã¯ãµãŒããŒåŽã®renderïŒïŒã§ãšã©ãŒãã¹ããŒããããšããŸããïŒraiseErrorInRenderïŒtrueã§ç¶æ ãéå§ããã®ã§ããµãŒããŒåŽã®æåã®ã¬ã³ããªã³ã°ã¯ãã§ã«ãšã©ãŒãã¹ããŒããŸãïŒããããŠãã®ãšã©ãŒãSentryã«ãã£ãŠãã£ããã£ãããŸããã§ããã
ãã®ãšã©ãŒãæ©åšã«è¡šç€ºãããããã«ä»ã®æ¹æ³ã§ç®¡çããŸãããïŒ _errorã®getInitialPropsã§ãã£ããã£ããããšããŸããããæ©åšã§ã¯äœã衚瀺ãããŸããã çŸåšããµãŒããŒäžã®ãšã©ãŒããã£ããã£ããä¿¡é Œã§ããæ¹æ³ã1ã€èŠã€ããŠããŸããããã®ãã¡ã®ããã€ãã¯ïŒã»ãšãã©ã®å ŽåãAPIé害ã«æ¥ç¶ãããŠããå ŽåïŒè¡šç€ºãããŸããããšã©ãŒãã¹ãããŒãžããåçŽãªãšã©ãŒãååŸããŠæ©åšã«è¡šç€ºããããšã¯ã§ããŸããã§ããã
æ®å¿µãªããéããŸãã ç§ãå®è£ ããããšããŠãã解決çã¯ããã³ãã¬ãŒããšããŠäŸïŒãªãŒããŒã©ã€ãããã_document.jsãã¡ã€ã«ãé€ãïŒã䜿çšããŠãã¯ã©ã€ã¢ã³ãåŽã§çºçãããšã©ãŒããã£ããã£ããããšã§ãããŠãŒã¶ãŒãç§ã«å ±åããªãéããç¥ã£ãŠããŸãã ãµãŒããŒåŽã§ã¯ããã°è¡ããã°ãã¡ã€ã«ã«çŽæ¥ãªãã€ã¬ã¯ãããã ãã ãšæããŸãã ãã¹ãŠã®ãšã©ãŒãåãå Žæã«çœ®ãããã£ãã®ã§ãããã¯ééããªãæåã®è§£æ±ºçã§ã¯ãããŸããããããããããšã§ãå°ãªããšãã¢ããªã±ãŒã·ã§ã³ã§çºçããå¯èœæ§ã®ãããšã©ãŒã«é¢ããæ å ±ãåŸãããšãã§ããŸãã
ããªãã¯ãããã©ãæããŸããïŒ ããããšãïŒ
ãã®è³ªåã«å ããŠ...ç§ã¯ãµãŒããŒåŽã®renderïŒïŒã§ãšã©ãŒãã¹ããŒããããšããŸããïŒraiseErrorInRenderïŒtrueã§ç¶æ ãéå§ããã®ã§ããµãŒããŒåŽã®æåã®ã¬ã³ããªã³ã°ã¯ãã§ã«ãšã©ãŒãã¹ããŒããŸãïŒããããŠãã®ãšã©ãŒãSentryã«ãã£ãŠãã£ããã£ãããŸããã§ããã
ãã®ãšã©ãŒãæ©åšã«è¡šç€ºãããããã«ä»ã®æ¹æ³ã§ç®¡çããŸãããïŒ _errorã®getInitialPropsã§ãã£ããã£ããããšããŸããããæ©åšã§ã¯äœã衚瀺ãããŸããã çŸåšããµãŒããŒäžã®ãšã©ãŒããã£ããã£ããä¿¡é Œã§ããæ¹æ³ã1ã€èŠã€ããŠããŸããããã®ãã¡ã®ããã€ãã¯ïŒã»ãšãã©ã®å ŽåãAPIé害ã«æ¥ç¶ãããŠããå ŽåïŒè¡šç€ºãããŸããããšã©ãŒãã¹ãããŒãžããåçŽãªãšã©ãŒãååŸããŠæ©åšã«è¡šç€ºããããšã¯ã§ããŸããã§ããã
æ®å¿µãªããéããŸãã ç§ãå®è£ ããããšããŠãã解決çã¯ããã³ãã¬ãŒããšããŠäŸïŒãªãŒããŒã©ã€ãããã_document.jsãã¡ã€ã«ãé€ãïŒã䜿çšããŠãã¯ã©ã€ã¢ã³ãåŽã§çºçãããšã©ãŒããã£ããã£ããããšã§ãããŠãŒã¶ãŒãç§ã«å ±åããªãéããç¥ã£ãŠããŸãã ãµãŒããŒåŽã§ã¯ããã°è¡ããã°ãã¡ã€ã«ã«çŽæ¥ãªãã€ã¬ã¯ãããã ãã ãšæããŸãã ãã¹ãŠã®ãšã©ãŒãåãå Žæã«çœ®ãããã£ãã®ã§ãããã¯ééããªãæåã®è§£æ±ºçã§ã¯ãããŸããããããããããšã§ãå°ãªããšãã¢ããªã±ãŒã·ã§ã³ã§çºçããå¯èœæ§ã®ãããšã©ãŒã«é¢ããæ å ±ãåŸãããšãã§ããŸãã
ããªãã¯ãããã©ãæããŸããïŒ ããããšãïŒ
ããã¯ãŸãã«ç§ãã¡ãä»ããŠããããšã§ããããã¯å°ãäžæ Œå¥œã§ãããç§ãã¡ãæãã€ãããšãã§ããæé«ã®ãã®ã§ãã ãããããµãŒããŒåŽã®ãšã©ãŒã®äžéšãæ©åšã«è¡šç€ºããããããæ©åšãŸãã¯next.jsã®ããããã§äœããèµ·ãã£ãŠããã«éããªããšæããŸãã åçŽãªäŸãšããè€éãªäŸã®äž¡æ¹ãè©ŠããŠã¿ãŸããããã©ã¡ããåãããã«åäœããããããã®åäœã¯ã»ããã¢ãããšã¯é¢ä¿ããªãããšãå°ãªããšãããçšåºŠç¢ºä¿¡ããŠããŸãã
ãã®ã¹ã¬ããã®äººã ã¯ã httpsïŒ//github.com/zeit/next.js/pull/8684ããã³é¢é£ãããã°ã«èå³ããããããããŸããã æªåŠçã®äŸå€ã®12ã®ç°ãªããã¹ãããããNext.jsãåŠçããäŸå€ãšåŠçããªãäŸå€ãç解ããããã«ãããŸããŸãªãã¹ããè¡ãããšãã§ããŸãã
ãã®åé¡ã«é¢ãããã¥ãŒã¹ã¯ãããŸããïŒ
ç§ã®è§£æ±ºçã¯ã redux
ã䜿çšããŠãããŒããã§ããããŒã¿ãšã©ãŒãstate
redux
ä¿åããããšã§ãã次ã«ã _app.js
ã®componentDidMount
ã§ãäœããå®è¡ããŸãïŒãŠãŒã¶ãŒãžã®ã¢ã©ãŒããŸãã¯ãšã©ãŒåŸã®èŠæ±ããã¯ãšã³ããžïŒã
ã³ãŒãã¯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
ã¬ã³ããªã³ã°é¢æ°ã§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
ãè¿ãã ãã§ã¯æ©èœããªãããã Sentry
ãCrashReporter
ã¯ã©ã¹ã«ã©ããããŸããïŒã€ãŸãã 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_ãœãªã¥ãŒã·ã§ã³ã倧奜ãã§ã