Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Sentry config on the server side #377

Merged
merged 6 commits into from
Jun 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@
"@fortawesome/free-regular-svg-icons": "5.15.3",
"@fortawesome/free-solid-svg-icons": "5.15.3",
"@fortawesome/react-fontawesome": "0.1.14",
"@sentry/browser": "6.3.6",
"@sentry/node": "6.3.6",
"@sentry/browser": "6.7.2",
"@sentry/node": "6.7.2",
"@types/lodash.isequal": "4.5.5",
"@unly/simple-logger": "1.0.0",
"@unly/universal-language-detector": "2.0.3",
Expand Down
6 changes: 6 additions & 0 deletions src/app/components/MultiversalAppBootstrap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,12 @@ const MultiversalAppBootstrap: React.FunctionComponent<Props> = (props): JSX.Ele
Sentry.captureException(props.err);
});
}
} else {
// XXX Opinionated: Record an exception in Sentry for 404, if you don't want this then uncomment the below code
const err = new Error(`Page not found (404) for "${router?.asPath}"`);

logger.warn(err);
Sentry.captureException(err);
}

const i18nextInstance: i18n = i18nextLocize(lang, i18nTranslations); // Apply i18next configuration with Locize backend
Expand Down
14 changes: 14 additions & 0 deletions src/app/isNextApiRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { IncomingMessage } from 'http';
import { NextApiRequest } from 'next';

/**
* TS type guard resolving whether "req" matches a "NextApiRequest" object.
*
* @param req
*
* @see https://www.typescripttutorial.net/typescript-tutorial/typescript-type-guards/
* @see https://www.logicbig.com/tutorials/misc/typescript/type-guards.html
*/
export const isNextApiRequest = (req: NextApiRequest | IncomingMessage): req is NextApiRequest => {
return (req as NextApiRequest).body !== undefined;
};
2 changes: 1 addition & 1 deletion src/modules/core/sentry/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ export const ALERT_TYPES = {
* @see https://github.com/vercel/next.js/blob/canary/examples/with-sentry/pages/_error.js#L45
* @see https://vercel.com/docs/platform/limits#streaming-responses
*/
export const FLUSH_TIMEOUT = 5000;
export const FLUSH_TIMEOUT = 2000;
34 changes: 23 additions & 11 deletions src/modules/core/sentry/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,23 @@ import * as Sentry from '@sentry/node';
import { isBrowser } from '@unly/utils';

/**
* Initialize Sentry and export it.
* Initializes Sentry and exports it.
*
* Helper to avoid duplicating the init() call in every /pages/api file.
* Also used in pages/_app for the client side, which automatically applies it for all frontend pages.
* Helper to avoid duplicating the Sentry initialization in:
* - The "/pages/api" files, for the server side.
* - The "pages/_app" file, for the client side, which in turns automatically applies it for all frontend pages.
*
* Doesn't initialise Sentry if SENTRY_DSN isn't defined
* Also configures the default scope, subsequent calls to "configureScope" will enrich the scope.
* Must only contain tags/contexts/extras that are universal (not server or browser specific).
*
* The Sentry scope will be enriched by:
* - BrowserPageBootstrap, for browser-specific metadata.
* - ServerPageBootstrap, for server-specific metadata.
* - API endpoints, for per-API additional metadata.
* - React components, for per-component additional metadata.
*
* Doesn't initialize Sentry if SENTRY_DSN isn't defined.
* Re-exports the Sentry object to make it simpler to consume by developers (DX).
*
* @see https://www.npmjs.com/package/@sentry/node
*/
Expand All @@ -19,13 +30,7 @@ if (process.env.SENTRY_DSN) {
release: process.env.NEXT_PUBLIC_APP_VERSION_RELEASE,
});

if (!process.env.SENTRY_DSN && process.env.NODE_ENV !== 'test') {
// eslint-disable-next-line no-console
console.error('Sentry DSN not defined');
}

// Scope configured by default, subsequent calls to "configureScope" will add additional data
Sentry.configureScope((scope) => { // See https://www.npmjs.com/package/@sentry/node
Sentry.configureScope((scope) => {
scope.setTag('customerRef', process.env.NEXT_PUBLIC_CUSTOMER_REF);
scope.setTag('appStage', process.env.NEXT_PUBLIC_APP_STAGE);
scope.setTag('appName', process.env.NEXT_PUBLIC_APP_NAME);
Expand All @@ -40,4 +45,11 @@ if (process.env.SENTRY_DSN) {
scope.setTag('memory', process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE || null); // Optional - Available on production environment only
scope.setTag('runtimeEngine', isBrowser() ? 'browser' : 'server');
});
} else {
if (process.env.NODE_ENV !== 'test') {
// eslint-disable-next-line no-console
console.error(`Sentry DSN not defined, events (exceptions, messages, etc.) won't be sent to Sentry.`);
}
}

export default Sentry;
23 changes: 16 additions & 7 deletions src/modules/core/sentry/server.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
import { isNextApiRequest } from '@/app/isNextApiRequest';
import { convertRequestBodyToJSObject } from '@/modules/core/api/convertRequestBodyToJSObject';
import { GenericObject } from '@/modules/core/data/types/GenericObject';
import * as Sentry from '@sentry/node';
import Sentry from '@/modules/core/sentry/init';
import { IncomingMessage } from 'http'; // Automatically inits Sentry during import
import map from 'lodash.map';
import { NextApiRequest } from 'next';

/**
* Configure the Sentry scope by extracting useful tags and context from the given request.
* Configures the Sentry scope by extracting useful tags and context from the given request.
*
* XXX Because it imports Sentry from "@/modules/core/sentry/init", it automatically initializes Sentry as well
*
* @param req
* @param tags
* @param contexts
* @see https://www.npmjs.com/package/@sentry/node
*/
export const configureReq = (req: NextApiRequest, tags?: { [key: string]: string }, contexts?: { [key: string]: any }): void => {
export const configureReq = (req: NextApiRequest | IncomingMessage, tags?: { [key: string]: string }, contexts?: { [key: string]: any }): void => {
let parsedBody: GenericObject = {};
try {
parsedBody = convertRequestBodyToJSObject(req);
if (isNextApiRequest(req)) {
parsedBody = convertRequestBodyToJSObject(req);
}
} catch (e) {
// eslint-disable-next-line no-console
// console.error(e);
Expand All @@ -25,12 +31,15 @@ export const configureReq = (req: NextApiRequest, tags?: { [key: string]: string
scope.setTag('host', req?.headers?.host);
scope.setTag('url', req?.url);
scope.setTag('method', req?.method);
scope.setExtra('query', req?.query);
scope.setExtra('body', req?.body);
scope.setExtra('cookies', req?.cookies);
scope.setContext('headers', req?.headers);
scope.setContext('parsedBody', parsedBody);

if (isNextApiRequest(req)) {
scope.setExtra('query', req?.query);
scope.setExtra('body', req?.body);
scope.setExtra('cookies', req?.cookies);
}

map(tags, (value: string, tag: string) => {
scope.setTag(tag, value);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ const ErrorsHandlingPage: NextPage<Props> = (props): JSX.Element => {
<h2>404 - Using CSR</h2>

<Alert color={'info'}>
This page doesn't exist and should display a 404 page.
This page doesn't exist and should display a 404 page. The error will be reported to Sentry.
</Alert>

<p>
Expand All @@ -98,7 +98,7 @@ const ErrorsHandlingPage: NextPage<Props> = (props): JSX.Element => {
<h2>404 - Using full page reload</h2>

<Alert color={'info'}>
This page doesn't exist and should display a 404 page.
This page doesn't exist and should display a 404 page. The error will be reported to Sentry.
</Alert>

<p>
Expand All @@ -123,6 +123,7 @@ const ErrorsHandlingPage: NextPage<Props> = (props): JSX.Element => {

<Alert color={'info'}>
This page throws an error right from the Page component and should display a 500 page error without anything else (no footer/header).
The error will be reported to Sentry.
</Alert>

<Code
Expand Down Expand Up @@ -162,7 +163,7 @@ const ErrorsHandlingPage: NextPage<Props> = (props): JSX.Element => {

<hr />

<h2>Interactive error (simulating User interaction)</h2>
<h2>Interactive errors (simulating User interaction)</h2>

<Btn mode={'primary-outline'}>
<I18nLink href={'/demo/built-in-utilities/interactive-error'}>Go to interactive error page</I18nLink><br />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ const InteractiveErrorPage: NextPage<Props> = (props): JSX.Element => {
<Button
onClick={(): void => {
setIsClicked(true);
throw new Error('Page 500 error example');
throw new Error('Page 500 error example (handled)');
}}
>
Will it crash the whole app?
Expand All @@ -99,7 +99,7 @@ const InteractiveErrorPage: NextPage<Props> = (props): JSX.Element => {
<Button
onClick={(): void => {
setIsClicked(true);
throw new Error('Page 500 error example');
throw new Error('Page 500 error example (handled)');
}}
>
Will it crash the whole app?
Expand Down
19 changes: 9 additions & 10 deletions src/pages/_error.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,19 @@ const ErrorPage = (props: ErrorPageProps): JSX.Element => {
};

/**
* TODO Doc - Is this only called when the error happens server-side?
* What's the point of getInitialProps when using SSG or hybrid apps?
* Might be called from the server and the client side.
*
* Won't be called for 404 errors (those are caught in MultiversalPageBootstrap).
*
* XXX Question: What's the point of getInitialProps when using SSG or hybrid apps? Is it being used? In what cases?
*
* @param props
*
* @see https://github.com/vercel/next.js/blob/canary/examples/with-sentry/pages/_error.js
*/
ErrorPage.getInitialProps = async (props: NextPageContext): Promise<ErrorProps> => {
const {
req,
res,
err,
asPath,
Expand All @@ -115,13 +119,9 @@ ErrorPage.getInitialProps = async (props: NextPageContext): Promise<ErrorProps>
if (res) {
// Running on the server, the response object is available.
//
// Next.js will pass an err on the server if a page's `getInitialProps`
// threw or returned a Promise that rejected

// XXX Opinionated: Record an exception in Sentry for 404, if you don't want this then uncomment the below code
// if (res.statusCode === 404) {
// return { statusCode: 404, isReadyToRender: true };
// }
// Next.js will pass an err on the server if a page's `getInitialProps` threw or returned a Promise that rejected
const configureReq = (await import('@/modules/core/sentry/server')).configureReq;
configureReq(req);

if (err) {
Sentry.captureException(err);
Expand All @@ -134,7 +134,6 @@ ErrorPage.getInitialProps = async (props: NextPageContext): Promise<ErrorProps>
// Running on the client (browser).
//
// Next.js will provide an err if:
//
// - a page's `getInitialProps` threw or returned a Promise that rejected
// - an exception was thrown somewhere in the React lifecycle (render,
// componentDidMount, etc) that was caught by Next.js's React Error
Expand Down
108 changes: 54 additions & 54 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2611,82 +2611,82 @@
dependencies:
any-observable "^0.3.0"

"@sentry/browser@6.3.6":
version "6.3.6"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.3.6.tgz#bba07033efded6c844de88dcc47f99548a29afed"
integrity sha512-l4323jxuBOArki6Wf+EHes39IEyJ2Zj/CIUaTY7GWh7CntpfHQAfFmZWQw3Ozq+ka1u8lVp25RPhb4Wng3azNA==
dependencies:
"@sentry/core" "6.3.6"
"@sentry/types" "6.3.6"
"@sentry/utils" "6.3.6"
"@sentry/browser@6.7.2":
version "6.7.2"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.7.2.tgz#cfbe060de5a9694617f175a6bde469e5e266792e"
integrity sha512-Lv0Ne1QcesyGAhVcQDfQa3hDPR/MhPSDTMg3xFi+LxqztchVc4w/ynzR0wCZFb8KIHpTj5SpJHfxpDhXYMtS9g==
dependencies:
"@sentry/core" "6.7.2"
"@sentry/types" "6.7.2"
"@sentry/utils" "6.7.2"
tslib "^1.9.3"

"@sentry/core@6.3.6":
version "6.3.6"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.3.6.tgz#e2ec6ae7e456e61f28000bab2d8ce85f58c59c66"
integrity sha512-w6BRizAqh7BaiM9oeKzO6aACXwRijUPacYaVLX/OfhqCSueF9uDxpMRT7+4D/eCeDVqgJYhBJ4Vsu2NSstkk4A==
"@sentry/core@6.7.2":
version "6.7.2"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.7.2.tgz#1d294fac6e62744bce3b9dfbcd90b14e93620480"
integrity sha512-NTZqwN5nR94yrXmSfekoPs1mIFuKvf8esdIW/DadwSKWAdLJwQTJY9xK/8PQv+SEzd7wiitPAx+mCw2By1xiNQ==
dependencies:
"@sentry/hub" "6.3.6"
"@sentry/minimal" "6.3.6"
"@sentry/types" "6.3.6"
"@sentry/utils" "6.3.6"
"@sentry/hub" "6.7.2"
"@sentry/minimal" "6.7.2"
"@sentry/types" "6.7.2"
"@sentry/utils" "6.7.2"
tslib "^1.9.3"

"@sentry/hub@6.3.6":
version "6.3.6"
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.3.6.tgz#e7bc6960e30d8731e23c6e77f31af0bfb1d5af3c"
integrity sha512-foBZ3ilMnm9Gf9OolrAxYHK8jrA6IF72faDdJ3Al+1H27qcpnBaMdrdEp2/jzwu/dgmwuLmbBaMjEPXaGH/0JQ==
"@sentry/hub@6.7.2":
version "6.7.2"
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.7.2.tgz#31b250e74aa303877620dfa500aa89e4411e2dec"
integrity sha512-05qVW6ymChJsXag4+fYCQokW3AcABIgcqrVYZUBf6GMU/Gbz5SJqpV7y1+njwWvnPZydMncP9LaDVpMKbE7UYQ==
dependencies:
"@sentry/types" "6.3.6"
"@sentry/utils" "6.3.6"
"@sentry/types" "6.7.2"
"@sentry/utils" "6.7.2"
tslib "^1.9.3"

"@sentry/minimal@6.3.6":
version "6.3.6"
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.3.6.tgz#aebcebd2ee9007b0ec505b9fcefd10f10fc5d43d"
integrity sha512-uM2/dH0a6zfvI5f+vg+/mST+uTBdN6Jgpm585ipH84ckCYQwIIDRg6daqsen4S1sy/xgg1P1YyC3zdEC4G6b1Q==
"@sentry/minimal@6.7.2":
version "6.7.2"
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.7.2.tgz#9e6c0c587daea64a9042041694a4ad5d559d16cd"
integrity sha512-jkpwFv2GFHoVl5vnK+9/Q+Ea8eVdbJ3hn3/Dqq9MOLFnVK7ED6MhdHKLT79puGSFj+85OuhM5m2Q44mIhyS5mw==
dependencies:
"@sentry/hub" "6.3.6"
"@sentry/types" "6.3.6"
"@sentry/hub" "6.7.2"
"@sentry/types" "6.7.2"
tslib "^1.9.3"

"@sentry/node@6.3.6":
version "6.3.6"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-6.3.6.tgz#e584500a10a9162d47fc8b55c9bf2ac3fec8d9f9"
integrity sha512-QVWakREgVUV/rocm4uMq+RkC0/g9d/z2BYic+2b0ZZMZD2aXF5RulrUQlAO2MzoXcO+bqpkXQsqdhMccqB4Zeg==
"@sentry/node@6.7.2":
version "6.7.2"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-6.7.2.tgz#ef2b865af2c37d83966db7fbd031179aa8c82cc0"
integrity sha512-vfNTmxBbHthAKPDBo0gVk/aNHdgUfXLzmaK7FgWO7cISiI2soCfvKEIP61XqIFZru06teqcRuDsYlR4wSeyWpg==
dependencies:
"@sentry/core" "6.3.6"
"@sentry/hub" "6.3.6"
"@sentry/tracing" "6.3.6"
"@sentry/types" "6.3.6"
"@sentry/utils" "6.3.6"
"@sentry/core" "6.7.2"
"@sentry/hub" "6.7.2"
"@sentry/tracing" "6.7.2"
"@sentry/types" "6.7.2"
"@sentry/utils" "6.7.2"
cookie "^0.4.1"
https-proxy-agent "^5.0.0"
lru_map "^0.3.3"
tslib "^1.9.3"

"@sentry/tracing@6.3.6":
version "6.3.6"
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-6.3.6.tgz#dc2aced01cdc401f97d6027113f6313503ee9c91"
integrity sha512-dfyYY2eESJGt5Qbigmfmb2U9ntqbwPhLNAOcjKaVg9WQRV5q2RkHCVctPoYk7TEAvfNeNRXCD8SnuFOZhttt8g==
"@sentry/tracing@6.7.2":
version "6.7.2"
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-6.7.2.tgz#78a6934837143ae5e200b49bd256bc8a917477bc"
integrity sha512-juKlI7FICKONWJFJxDxerj0A+8mNRhmtrdR+OXFqOkqSAy/QXlSFZcA/j//O19k2CfwK1BrvoMcQ/4gnffUOVg==
dependencies:
"@sentry/hub" "6.3.6"
"@sentry/minimal" "6.3.6"
"@sentry/types" "6.3.6"
"@sentry/utils" "6.3.6"
"@sentry/hub" "6.7.2"
"@sentry/minimal" "6.7.2"
"@sentry/types" "6.7.2"
"@sentry/utils" "6.7.2"
tslib "^1.9.3"

"@sentry/types@6.3.6":
version "6.3.6"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.3.6.tgz#aa3687051af1dc04ebc4eaf7f9562872da67aa5c"
integrity sha512-93cFJdJkWyCfyZeWFARSU11qnoHVOS/R2h5WIsEf+jbQmkqG2C+TXVz/19s6nHVsfDrwpvYpwALPv4/nrxfU7g==
"@sentry/types@6.7.2":
version "6.7.2"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.7.2.tgz#8108272c98ad7784ddf9ddda0b7bdc6880ed6e50"
integrity sha512-h21Go/PfstUN+ZV6SbwRSZVg9GXRJWdLfHoO5PSVb3TVEMckuxk8tAE57/u+UZDwX8wu+Xyon2TgsKpiWKxqUg==

"@sentry/utils@6.3.6":
version "6.3.6"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.3.6.tgz#6f619a525f2a94fa6b160500f63f4bd5bd171055"
integrity sha512-HnYlDBf8Dq8MEv7AulH7B6R1D/2LAooVclGdjg48tSrr9g+31kmtj+SAj2WWVHP9+bp29BWaC7i5nkfKrOibWw==
"@sentry/utils@6.7.2":
version "6.7.2"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.7.2.tgz#c7f957ebe16de3e701a0c5477ac2dba04e7b4b68"
integrity sha512-9COL7aaBbe61Hp5BlArtXZ1o/cxli1NGONLPrVT4fMyeQFmLonhUiy77NdsW19XnvhvaA+2IoV5dg3dnFiF/og==
dependencies:
"@sentry/types" "6.3.6"
"@sentry/types" "6.7.2"
tslib "^1.9.3"

"@sindresorhus/is@^0.14.0":
Expand Down