Skip to content

Commit 7ac57fe

Browse files
mydeaAbhiPrasad
authored andcommitted
ref(node-experimental): Rename errorHandler to expressErrorHandler (#10746)
Also provide a new `setupExpressErrorHandler(app)` utility.
1 parent b4f3366 commit 7ac57fe

File tree

5 files changed

+94
-85
lines changed

5 files changed

+94
-85
lines changed

dev-packages/node-integration-tests/suites/express/tracing-experimental/server.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,6 @@ app.get(['/test/arr/:id', /\/test\/arr[0-9]*\/required(path)?(\/optionalPath)?\/
3434
res.send({ response: 'response 4' });
3535
});
3636

37-
app.use(Sentry.errorHandler());
37+
Sentry.setupExpressErrorHandler(app);
3838

3939
startExpressServerAndSendPortToRunner(app);

packages/node-experimental/src/index.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
export { errorHandler } from './sdk/handlers/errorHandler';
2-
31
export { httpIntegration } from './integrations/http';
42
export { nativeNodeFetchIntegration } from './integrations/node-fetch';
53

@@ -11,7 +9,7 @@ export { modulesIntegration } from './integrations/modules';
119
export { onUncaughtExceptionIntegration } from './integrations/onuncaughtexception';
1210
export { onUnhandledRejectionIntegration } from './integrations/onunhandledrejection';
1311

14-
export { expressIntegration } from './integrations/tracing/express';
12+
export { expressIntegration, expressErrorHandler, setupExpressErrorHandler } from './integrations/tracing/express';
1513
export { fastifyIntegration } from './integrations/tracing/fastify';
1614
export { graphqlIntegration } from './integrations/tracing/graphql';
1715
export { mongoIntegration } from './integrations/tracing/mongo';

packages/node-experimental/src/integrations/tracing/express.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import type * as http from 'http';
12
import { registerInstrumentations } from '@opentelemetry/instrumentation';
23
import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express';
34
import { defineIntegration } from '@sentry/core';
5+
import { captureException, getClient, getIsolationScope } from '@sentry/core';
46
import type { IntegrationFn } from '@sentry/types';
57

8+
import type { NodeClient } from '../../sdk/client';
69
import { addOriginToSpan } from '../../utils/addOriginToSpan';
710

811
const _expressIntegration = (() => {
@@ -26,5 +29,89 @@ const _expressIntegration = (() => {
2629
* Express integration
2730
*
2831
* Capture tracing data for express.
32+
* In order to capture exceptions, you have to call `setupExpressErrorHandler(app)` before any other middleware and after all controllers.
2933
*/
3034
export const expressIntegration = defineIntegration(_expressIntegration);
35+
36+
interface MiddlewareError extends Error {
37+
status?: number | string;
38+
statusCode?: number | string;
39+
status_code?: number | string;
40+
output?: {
41+
statusCode?: number | string;
42+
};
43+
}
44+
45+
type ExpressMiddleware = (
46+
error: MiddlewareError,
47+
req: http.IncomingMessage,
48+
res: http.ServerResponse,
49+
next: (error: MiddlewareError) => void,
50+
) => void;
51+
52+
/**
53+
* An Express-compatible error handler.
54+
*/
55+
export function expressErrorHandler(options?: {
56+
/**
57+
* Callback method deciding whether error should be captured and sent to Sentry
58+
* @param error Captured middleware error
59+
*/
60+
shouldHandleError?(this: void, error: MiddlewareError): boolean;
61+
}): ExpressMiddleware {
62+
return function sentryErrorMiddleware(
63+
error: MiddlewareError,
64+
_req: http.IncomingMessage,
65+
res: http.ServerResponse,
66+
next: (error: MiddlewareError) => void,
67+
): void {
68+
const shouldHandleError = options?.shouldHandleError || defaultShouldHandleError;
69+
70+
if (shouldHandleError(error)) {
71+
const client = getClient<NodeClient>();
72+
if (client && client.getOptions().autoSessionTracking) {
73+
// Check if the `SessionFlusher` is instantiated on the client to go into this branch that marks the
74+
// `requestSession.status` as `Crashed`, and this check is necessary because the `SessionFlusher` is only
75+
// instantiated when the the`requestHandler` middleware is initialised, which indicates that we should be
76+
// running in SessionAggregates mode
77+
const isSessionAggregatesMode = client['_sessionFlusher'] !== undefined;
78+
if (isSessionAggregatesMode) {
79+
const requestSession = getIsolationScope().getRequestSession();
80+
// If an error bubbles to the `errorHandler`, then this is an unhandled error, and should be reported as a
81+
// Crashed session. The `_requestSession.status` is checked to ensure that this error is happening within
82+
// the bounds of a request, and if so the status is updated
83+
if (requestSession && requestSession.status !== undefined) {
84+
requestSession.status = 'crashed';
85+
}
86+
}
87+
}
88+
89+
const eventId = captureException(error, { mechanism: { type: 'middleware', handled: false } });
90+
(res as { sentry?: string }).sentry = eventId;
91+
next(error);
92+
93+
return;
94+
}
95+
96+
next(error);
97+
};
98+
}
99+
100+
/**
101+
* Setup an error handler for Express.
102+
* The error handler must be before any other middleware and after all controllers.
103+
*/
104+
export function setupExpressErrorHandler(app: { use: (middleware: ExpressMiddleware) => unknown }): void {
105+
app.use(expressErrorHandler());
106+
}
107+
108+
function getStatusCodeFromResponse(error: MiddlewareError): number {
109+
const statusCode = error.status || error.statusCode || error.status_code || (error.output && error.output.statusCode);
110+
return statusCode ? parseInt(statusCode as string, 10) : 500;
111+
}
112+
113+
/** Returns true if response code is internal server error */
114+
function defaultShouldHandleError(error: MiddlewareError): boolean {
115+
const status = getStatusCodeFromResponse(error);
116+
return status >= 500;
117+
}

packages/node-experimental/src/sdk/handlers/errorHandler.ts

Lines changed: 0 additions & 76 deletions
This file was deleted.

packages/node-experimental/test/sdk/handlers/errorHandler.test.ts renamed to packages/node-experimental/test/integrations/express.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import * as http from 'http';
22
import { getCurrentScope, getIsolationScope, setAsyncContextStrategy, setCurrentClient, withScope } from '@sentry/core';
33
import type { Scope } from '@sentry/types';
4-
import { NodeClient } from '../../../src/sdk/client';
5-
import { errorHandler } from '../../../src/sdk/handlers/errorHandler';
6-
import { getDefaultNodeClientOptions } from '../../helpers/getDefaultNodeClientOptions';
4+
import { expressErrorHandler } from '../../src/integrations/tracing/express';
5+
import { NodeClient } from '../../src/sdk/client';
6+
import { getDefaultNodeClientOptions } from '../helpers/getDefaultNodeClientOptions';
77

8-
describe('errorHandler()', () => {
8+
describe('expressErrorHandler()', () => {
99
beforeEach(() => {
1010
getCurrentScope().clear();
1111
getIsolationScope().clear();
@@ -20,7 +20,7 @@ describe('errorHandler()', () => {
2020
const path = '/by/the/trees/';
2121
const queryString = 'chase=me&please=thankyou';
2222

23-
const sentryErrorMiddleware = errorHandler();
23+
const sentryErrorMiddleware = expressErrorHandler();
2424

2525
let req: http.IncomingMessage, res: http.ServerResponse, next: () => undefined;
2626
let client: NodeClient;

0 commit comments

Comments
 (0)