Skip to content

Commit

Permalink
Refactor Sentry + fix Sentry usage in API endpoints (#375)
Browse files Browse the repository at this point in the history
* Refactor Sentry, split code between files depending on usage (browser/server/universal)

* Always flush Sentry in API endpoints

* Always flush Sentry in _error handling
  • Loading branch information
Vadorequest authored Jun 24, 2021
1 parent 95be082 commit 493e16f
Show file tree
Hide file tree
Showing 21 changed files with 227 additions and 184 deletions.
6 changes: 2 additions & 4 deletions src/app/components/BrowserPageBootstrap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@ import {
getClientNetworkConnectionType,
getClientNetworkInformationSpeed,
} from '@/modules/core/networkInformation/networkInformation';
import {
configureSentryBrowserMetadata,
configureSentryUserMetadata,
} from '@/modules/core/sentry/sentry';
import { configureSentryBrowserMetadata } from '@/modules/core/sentry/browser';
import { configureSentryUserMetadata } from '@/modules/core/sentry/universal';
import { cypressContext } from '@/modules/core/testing/contexts/cypressContext';
import {
CYPRESS_WINDOW_NS,
Expand Down
2 changes: 1 addition & 1 deletion src/app/components/MultiversalAppBootstrap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
stopPreviewMode,
} from '@/modules/core/previewMode/previewMode';
import quickPreviewContext from '@/modules/core/quickPreview/contexts/quickPreviewContext';
import { configureSentryI18n } from '@/modules/core/sentry/sentry';
import { configureSentryI18n } from '@/modules/core/sentry/universal';
import deserializeSafe from '@/modules/core/serializeSafe/deserializeSafe';
import { detectCypress } from '@/modules/core/testing/cypress';
import { initCustomerTheme } from '@/modules/core/theming/theme';
Expand Down
2 changes: 1 addition & 1 deletion src/app/components/ServerPageBootstrap.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { MultiversalPageProps } from '@/layouts/core/types/MultiversalPageProps';
import { OnlyServerPageProps } from '@/layouts/core/types/OnlyServerPageProps';
import { createLogger } from '@/modules/core/logging/logger';
import { configureSentryUserMetadata } from '@/modules/core/sentry/sentry';
import { configureSentryUserMetadata } from '@/modules/core/sentry/universal';
import { userSessionContext } from '@/modules/core/userSession/userSessionContext';
import * as Sentry from '@sentry/node';
import React from 'react';
Expand Down
2 changes: 1 addition & 1 deletion src/layouts/core/components/CoreLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { GenericObject } from '@/modules/core/data/types/GenericObject';
import DefaultErrorLayout from '@/modules/core/errorHandling/DefaultErrorLayout';
import { createLogger } from '@/modules/core/logging/logger';
import PreviewModeBanner from '@/modules/core/previewMode/components/PreviewModeBanner';
import Sentry from '@/modules/core/sentry/sentry';
import Sentry from '@/modules/core/sentry/init';
import ErrorPage from '@/pages/_error';
import {
Amplitude,
Expand Down
2 changes: 1 addition & 1 deletion src/layouts/demo/components/DemoLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { GenericObject } from '@/modules/core/data/types/GenericObject';
import DefaultErrorLayout from '@/modules/core/errorHandling/DefaultErrorLayout';
import { createLogger } from '@/modules/core/logging/logger';
import PreviewModeBanner from '@/modules/core/previewMode/components/PreviewModeBanner';
import Sentry from '@/modules/core/sentry/sentry';
import Sentry from '@/modules/core/sentry/init';
import ErrorPage from '@/pages/_error';
import {
Amplitude,
Expand Down
2 changes: 1 addition & 1 deletion src/layouts/quickPreview/components/QuickPreviewLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Head, { HeadProps } from '@/layouts/core/components/CoreHead';
import { SoftPageProps } from '@/layouts/core/types/SoftPageProps';
import { GenericObject } from '@/modules/core/data/types/GenericObject';
import { createLogger } from '@/modules/core/logging/logger';
import Sentry from '@/modules/core/sentry/sentry';
import Sentry from '@/modules/core/sentry/init';
import {
Amplitude,
LogOnMount,
Expand Down
3 changes: 2 additions & 1 deletion src/modules/core/githubActions/dispatchWorkflow.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createLogger } from '@/modules/core/logging/logger';
import Sentry, { ALERT_TYPES } from '../sentry/sentry';
import { ALERT_TYPES } from '@/modules/core/sentry/config';
import Sentry from '../sentry/init';
import { WorkflowsAPIResponse } from './types/WorkflowsAPIResponse';

const fileLabel = 'modules/core/githubActions/dispatchWorkflow';
Expand Down
2 changes: 1 addition & 1 deletion src/modules/core/githubActions/dispatchWorkflowByPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
GITHUB_REPO_NAME,
} from '@/app/constants';
import { createLogger } from '@/modules/core/logging/logger';
import Sentry from '../sentry/sentry';
import Sentry from '../sentry/init';
import dispatchWorkflow from './dispatchWorkflow';
import { WorkflowsAPIResponse } from './types/WorkflowsAPIResponse';

Expand Down
25 changes: 25 additions & 0 deletions src/modules/core/sentry/browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {
ClientNetworkConnectionType,
ClientNetworkInformationSpeed,
} from '@/modules/core/networkInformation/networkInformation';
import * as Sentry from '@sentry/node';

/**
* Configure Sentry tags related to the browser metadata.
*
* @param networkSpeed
* @param networkConnectionType
* @param isInIframe
* @param iframeReferrer
*/
export const configureSentryBrowserMetadata = (networkSpeed: ClientNetworkInformationSpeed, networkConnectionType: ClientNetworkConnectionType, isInIframe: boolean, iframeReferrer: string): void => {
if (process.env.SENTRY_DSN) {
Sentry.configureScope((scope) => {
scope.setTag('networkSpeed', networkSpeed);
scope.setTag('networkConnectionType', networkConnectionType);
scope.setTag('iframe', `${isInIframe}`);
scope.setExtra('iframe', isInIframe);
scope.setExtra('iframeReferrer', iframeReferrer);
});
}
};
23 changes: 23 additions & 0 deletions src/modules/core/sentry/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Alert types, meant to be assigned to "alertType" tag when reporting a message/exception/event to Sentry.
*
* Then, you can configure your own Sentry Alerts using the "alertType" tag and perform specific data processing.
* @example If the event's tags match "alertType equals 'vercel-deployment-invoked'", then send it to a dedicated Slack channel.
*
* @see https://sentry.io/organizations/unly/alerts/next-right-now/new/
*/
export const ALERT_TYPES = {
VERCEL_DEPLOYMENT_INVOKED: 'vercel-deployment-invoked',
VERCEL_DEPLOYMENT_TRIGGER_ATTEMPT: 'vercel-deployment-trigger-attempt',
VERCEL_DEPLOYMENT_TRIGGER_ATTEMPT_FAILED: 'vercel-deployment-trigger-attempt-failed',
VERCEL_DEPLOYMENT_TRIGGER_ATTEMPT_SUCCEEDED: 'vercel-deployment-trigger-attempt-succeeded',
VERCEL_DEPLOYMENT_COMPLETED: 'vercel-deployment-completed',
};

/**
* Maximum time in ms the Sentry client (browser or server) should wait.
*
* @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 = 2000;
45 changes: 45 additions & 0 deletions src/modules/core/sentry/init.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import * as Sentry from '@sentry/node';
import { isBrowser } from '@unly/utils';

/**
* Initialize Sentry and export 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.
*
* Doesn't initialise Sentry if SENTRY_DSN isn't defined
*
* @see https://www.npmjs.com/package/@sentry/node
*/
if (process.env.SENTRY_DSN) {
Sentry.init({
dsn: process.env.SENTRY_DSN,
enabled: process.env.NODE_ENV !== 'test',
environment: process.env.NEXT_PUBLIC_APP_STAGE,
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
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);
scope.setTag('appBaseUrl', process.env.NEXT_PUBLIC_APP_BASE_URL);
scope.setTag('appVersion', process.env.NEXT_PUBLIC_APP_VERSION);
scope.setTag('appNameVersion', process.env.NEXT_PUBLIC_APP_NAME_VERSION);
scope.setTag('appBuildTime', process.env.NEXT_PUBLIC_APP_BUILD_TIME);
scope.setTag('buildTimeISO', (new Date(process.env.NEXT_PUBLIC_APP_BUILD_TIME || null)).toISOString());
scope.setTag('appBuildId', process.env.NEXT_PUBLIC_APP_BUILD_ID);
scope.setTag('nodejs', process.version);
scope.setTag('nodejsAWS', process.env.AWS_EXECUTION_ENV || null); // Optional - Available on production environment only
scope.setTag('memory', process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE || null); // Optional - Available on production environment only
scope.setTag('runtimeEngine', isBrowser() ? 'browser' : 'server');
});
}

export default Sentry;
162 changes: 0 additions & 162 deletions src/modules/core/sentry/sentry.ts

This file was deleted.

42 changes: 42 additions & 0 deletions src/modules/core/sentry/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { convertRequestBodyToJSObject } from '@/modules/core/api/convertRequestBodyToJSObject';
import { GenericObject } from '@/modules/core/data/types/GenericObject';
import * as Sentry from '@sentry/node';
import map from 'lodash.map';
import { NextApiRequest } from 'next';

/**
* Configure the Sentry scope by extracting useful tags and context from the given request.
*
* @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 => {
let parsedBody: GenericObject = {};
try {
parsedBody = convertRequestBodyToJSObject(req);
} catch (e) {
// eslint-disable-next-line no-console
// console.error(e);
} // Do nothing, as "body" is not necessarily supposed to contain valid stringified JSON

Sentry.configureScope((scope) => {
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);

map(tags, (value: string, tag: string) => {
scope.setTag(tag, value);
});

map(contexts, (value: { [key: string]: any; }, context: string) => {
scope.setContext(context, value);
});
});
};
Loading

0 comments on commit 493e16f

Please sign in to comment.