Skip to content

Commit 493e16f

Browse files
authored
Refactor Sentry + fix Sentry usage in API endpoints (#375)
* Refactor Sentry, split code between files depending on usage (browser/server/universal) * Always flush Sentry in API endpoints * Always flush Sentry in _error handling
1 parent 95be082 commit 493e16f

21 files changed

+227
-184
lines changed

src/app/components/BrowserPageBootstrap.tsx

+2-4
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,8 @@ import {
1414
getClientNetworkConnectionType,
1515
getClientNetworkInformationSpeed,
1616
} from '@/modules/core/networkInformation/networkInformation';
17-
import {
18-
configureSentryBrowserMetadata,
19-
configureSentryUserMetadata,
20-
} from '@/modules/core/sentry/sentry';
17+
import { configureSentryBrowserMetadata } from '@/modules/core/sentry/browser';
18+
import { configureSentryUserMetadata } from '@/modules/core/sentry/universal';
2119
import { cypressContext } from '@/modules/core/testing/contexts/cypressContext';
2220
import {
2321
CYPRESS_WINDOW_NS,

src/app/components/MultiversalAppBootstrap.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
stopPreviewMode,
2121
} from '@/modules/core/previewMode/previewMode';
2222
import quickPreviewContext from '@/modules/core/quickPreview/contexts/quickPreviewContext';
23-
import { configureSentryI18n } from '@/modules/core/sentry/sentry';
23+
import { configureSentryI18n } from '@/modules/core/sentry/universal';
2424
import deserializeSafe from '@/modules/core/serializeSafe/deserializeSafe';
2525
import { detectCypress } from '@/modules/core/testing/cypress';
2626
import { initCustomerTheme } from '@/modules/core/theming/theme';

src/app/components/ServerPageBootstrap.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { MultiversalPageProps } from '@/layouts/core/types/MultiversalPageProps';
22
import { OnlyServerPageProps } from '@/layouts/core/types/OnlyServerPageProps';
33
import { createLogger } from '@/modules/core/logging/logger';
4-
import { configureSentryUserMetadata } from '@/modules/core/sentry/sentry';
4+
import { configureSentryUserMetadata } from '@/modules/core/sentry/universal';
55
import { userSessionContext } from '@/modules/core/userSession/userSessionContext';
66
import * as Sentry from '@sentry/node';
77
import React from 'react';

src/layouts/core/components/CoreLayout.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { GenericObject } from '@/modules/core/data/types/GenericObject';
33
import DefaultErrorLayout from '@/modules/core/errorHandling/DefaultErrorLayout';
44
import { createLogger } from '@/modules/core/logging/logger';
55
import PreviewModeBanner from '@/modules/core/previewMode/components/PreviewModeBanner';
6-
import Sentry from '@/modules/core/sentry/sentry';
6+
import Sentry from '@/modules/core/sentry/init';
77
import ErrorPage from '@/pages/_error';
88
import {
99
Amplitude,

src/layouts/demo/components/DemoLayout.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { GenericObject } from '@/modules/core/data/types/GenericObject';
33
import DefaultErrorLayout from '@/modules/core/errorHandling/DefaultErrorLayout';
44
import { createLogger } from '@/modules/core/logging/logger';
55
import PreviewModeBanner from '@/modules/core/previewMode/components/PreviewModeBanner';
6-
import Sentry from '@/modules/core/sentry/sentry';
6+
import Sentry from '@/modules/core/sentry/init';
77
import ErrorPage from '@/pages/_error';
88
import {
99
Amplitude,

src/layouts/quickPreview/components/QuickPreviewLayout.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Head, { HeadProps } from '@/layouts/core/components/CoreHead';
22
import { SoftPageProps } from '@/layouts/core/types/SoftPageProps';
33
import { GenericObject } from '@/modules/core/data/types/GenericObject';
44
import { createLogger } from '@/modules/core/logging/logger';
5-
import Sentry from '@/modules/core/sentry/sentry';
5+
import Sentry from '@/modules/core/sentry/init';
66
import {
77
Amplitude,
88
LogOnMount,

src/modules/core/githubActions/dispatchWorkflow.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createLogger } from '@/modules/core/logging/logger';
2-
import Sentry, { ALERT_TYPES } from '../sentry/sentry';
2+
import { ALERT_TYPES } from '@/modules/core/sentry/config';
3+
import Sentry from '../sentry/init';
34
import { WorkflowsAPIResponse } from './types/WorkflowsAPIResponse';
45

56
const fileLabel = 'modules/core/githubActions/dispatchWorkflow';

src/modules/core/githubActions/dispatchWorkflowByPath.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
GITHUB_REPO_NAME,
55
} from '@/app/constants';
66
import { createLogger } from '@/modules/core/logging/logger';
7-
import Sentry from '../sentry/sentry';
7+
import Sentry from '../sentry/init';
88
import dispatchWorkflow from './dispatchWorkflow';
99
import { WorkflowsAPIResponse } from './types/WorkflowsAPIResponse';
1010

src/modules/core/sentry/browser.ts

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import {
2+
ClientNetworkConnectionType,
3+
ClientNetworkInformationSpeed,
4+
} from '@/modules/core/networkInformation/networkInformation';
5+
import * as Sentry from '@sentry/node';
6+
7+
/**
8+
* Configure Sentry tags related to the browser metadata.
9+
*
10+
* @param networkSpeed
11+
* @param networkConnectionType
12+
* @param isInIframe
13+
* @param iframeReferrer
14+
*/
15+
export const configureSentryBrowserMetadata = (networkSpeed: ClientNetworkInformationSpeed, networkConnectionType: ClientNetworkConnectionType, isInIframe: boolean, iframeReferrer: string): void => {
16+
if (process.env.SENTRY_DSN) {
17+
Sentry.configureScope((scope) => {
18+
scope.setTag('networkSpeed', networkSpeed);
19+
scope.setTag('networkConnectionType', networkConnectionType);
20+
scope.setTag('iframe', `${isInIframe}`);
21+
scope.setExtra('iframe', isInIframe);
22+
scope.setExtra('iframeReferrer', iframeReferrer);
23+
});
24+
}
25+
};

src/modules/core/sentry/config.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* Alert types, meant to be assigned to "alertType" tag when reporting a message/exception/event to Sentry.
3+
*
4+
* Then, you can configure your own Sentry Alerts using the "alertType" tag and perform specific data processing.
5+
* @example If the event's tags match "alertType equals 'vercel-deployment-invoked'", then send it to a dedicated Slack channel.
6+
*
7+
* @see https://sentry.io/organizations/unly/alerts/next-right-now/new/
8+
*/
9+
export const ALERT_TYPES = {
10+
VERCEL_DEPLOYMENT_INVOKED: 'vercel-deployment-invoked',
11+
VERCEL_DEPLOYMENT_TRIGGER_ATTEMPT: 'vercel-deployment-trigger-attempt',
12+
VERCEL_DEPLOYMENT_TRIGGER_ATTEMPT_FAILED: 'vercel-deployment-trigger-attempt-failed',
13+
VERCEL_DEPLOYMENT_TRIGGER_ATTEMPT_SUCCEEDED: 'vercel-deployment-trigger-attempt-succeeded',
14+
VERCEL_DEPLOYMENT_COMPLETED: 'vercel-deployment-completed',
15+
};
16+
17+
/**
18+
* Maximum time in ms the Sentry client (browser or server) should wait.
19+
*
20+
* @see https://github.com/vercel/next.js/blob/canary/examples/with-sentry/pages/_error.js#L45
21+
* @see https://vercel.com/docs/platform/limits#streaming-responses
22+
*/
23+
export const FLUSH_TIMEOUT = 2000;

src/modules/core/sentry/init.ts

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import * as Sentry from '@sentry/node';
2+
import { isBrowser } from '@unly/utils';
3+
4+
/**
5+
* Initialize Sentry and export it.
6+
*
7+
* Helper to avoid duplicating the init() call in every /pages/api file.
8+
* Also used in pages/_app for the client side, which automatically applies it for all frontend pages.
9+
*
10+
* Doesn't initialise Sentry if SENTRY_DSN isn't defined
11+
*
12+
* @see https://www.npmjs.com/package/@sentry/node
13+
*/
14+
if (process.env.SENTRY_DSN) {
15+
Sentry.init({
16+
dsn: process.env.SENTRY_DSN,
17+
enabled: process.env.NODE_ENV !== 'test',
18+
environment: process.env.NEXT_PUBLIC_APP_STAGE,
19+
release: process.env.NEXT_PUBLIC_APP_VERSION_RELEASE,
20+
});
21+
22+
if (!process.env.SENTRY_DSN && process.env.NODE_ENV !== 'test') {
23+
// eslint-disable-next-line no-console
24+
console.error('Sentry DSN not defined');
25+
}
26+
27+
// Scope configured by default, subsequent calls to "configureScope" will add additional data
28+
Sentry.configureScope((scope) => { // See https://www.npmjs.com/package/@sentry/node
29+
scope.setTag('customerRef', process.env.NEXT_PUBLIC_CUSTOMER_REF);
30+
scope.setTag('appStage', process.env.NEXT_PUBLIC_APP_STAGE);
31+
scope.setTag('appName', process.env.NEXT_PUBLIC_APP_NAME);
32+
scope.setTag('appBaseUrl', process.env.NEXT_PUBLIC_APP_BASE_URL);
33+
scope.setTag('appVersion', process.env.NEXT_PUBLIC_APP_VERSION);
34+
scope.setTag('appNameVersion', process.env.NEXT_PUBLIC_APP_NAME_VERSION);
35+
scope.setTag('appBuildTime', process.env.NEXT_PUBLIC_APP_BUILD_TIME);
36+
scope.setTag('buildTimeISO', (new Date(process.env.NEXT_PUBLIC_APP_BUILD_TIME || null)).toISOString());
37+
scope.setTag('appBuildId', process.env.NEXT_PUBLIC_APP_BUILD_ID);
38+
scope.setTag('nodejs', process.version);
39+
scope.setTag('nodejsAWS', process.env.AWS_EXECUTION_ENV || null); // Optional - Available on production environment only
40+
scope.setTag('memory', process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE || null); // Optional - Available on production environment only
41+
scope.setTag('runtimeEngine', isBrowser() ? 'browser' : 'server');
42+
});
43+
}
44+
45+
export default Sentry;

src/modules/core/sentry/sentry.ts

-162
This file was deleted.

src/modules/core/sentry/server.ts

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { convertRequestBodyToJSObject } from '@/modules/core/api/convertRequestBodyToJSObject';
2+
import { GenericObject } from '@/modules/core/data/types/GenericObject';
3+
import * as Sentry from '@sentry/node';
4+
import map from 'lodash.map';
5+
import { NextApiRequest } from 'next';
6+
7+
/**
8+
* Configure the Sentry scope by extracting useful tags and context from the given request.
9+
*
10+
* @param req
11+
* @param tags
12+
* @param contexts
13+
* @see https://www.npmjs.com/package/@sentry/node
14+
*/
15+
export const configureReq = (req: NextApiRequest, tags?: { [key: string]: string }, contexts?: { [key: string]: any }): void => {
16+
let parsedBody: GenericObject = {};
17+
try {
18+
parsedBody = convertRequestBodyToJSObject(req);
19+
} catch (e) {
20+
// eslint-disable-next-line no-console
21+
// console.error(e);
22+
} // Do nothing, as "body" is not necessarily supposed to contain valid stringified JSON
23+
24+
Sentry.configureScope((scope) => {
25+
scope.setTag('host', req?.headers?.host);
26+
scope.setTag('url', req?.url);
27+
scope.setTag('method', req?.method);
28+
scope.setExtra('query', req?.query);
29+
scope.setExtra('body', req?.body);
30+
scope.setExtra('cookies', req?.cookies);
31+
scope.setContext('headers', req?.headers);
32+
scope.setContext('parsedBody', parsedBody);
33+
34+
map(tags, (value: string, tag: string) => {
35+
scope.setTag(tag, value);
36+
});
37+
38+
map(contexts, (value: { [key: string]: any; }, context: string) => {
39+
scope.setContext(context, value);
40+
});
41+
});
42+
};

0 commit comments

Comments
 (0)