Skip to content

Commit 687e7ae

Browse files
committed
feat(node): Add shouldHandleError option to fastifyIntegration
1 parent 49061a0 commit 687e7ae

File tree

3 files changed

+98
-38
lines changed

3 files changed

+98
-38
lines changed

dev-packages/e2e-tests/test-applications/node-fastify-5/src/app.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,18 @@ console.warn = new Proxy(console.warn, {
1717
Sentry.init({
1818
environment: 'qa', // dynamic sampling bias to keep transactions
1919
dsn: process.env.E2E_TEST_DSN,
20-
integrations: [],
20+
integrations: [
21+
Sentry.fastifyIntegration({
22+
shouldHandleError: (error, _request, _reply) => {
23+
if (_request.routeOptions?.url?.includes('/test-error-not-captured')) {
24+
// Errors from this path will not be captured by Sentry
25+
return false;
26+
}
27+
28+
return true;
29+
},
30+
}),
31+
],
2132
tracesSampleRate: 1,
2233
tunnel: 'http://localhost:3031/', // proxy server
2334
tracePropagationTargets: ['http://localhost:3030', '/external-allowed'],
@@ -79,6 +90,11 @@ app.get('/test-error', async function (req, res) {
7990
res.send({ exceptionId });
8091
});
8192

93+
app.get('/test-error-not-captured', async function () {
94+
// This error will not be captured by Sentry
95+
throw new Error('This is an error that will not be captured');
96+
});
97+
8298
app.get<{ Params: { id: string } }>('/test-exception/:id', async function (req, res) {
8399
throw new Error(`This is an exception with id ${req.params.id}`);
84100
});

dev-packages/e2e-tests/test-applications/node-fastify-5/tests/errors.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,16 @@ test('Sends correct error event', async ({ baseURL }) => {
2828
parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
2929
});
3030
});
31+
32+
test('Does not send error when shouldHandleError returns false', async ({ baseURL }) => {
33+
const errorEventPromise = waitForError('node-fastify-5', event => {
34+
return !event.type && event.exception?.values?.[0]?.value === 'This is an error that will not be captured';
35+
});
36+
37+
await fetch(`${baseURL}/test-error-not-captured`);
38+
39+
// Wait for a short time to ensure no error is sent
40+
await new Promise(resolve => setTimeout(resolve, 1000));
41+
42+
expect(errorEventPromise).rejects.toBeDefined();
43+
});

packages/node/src/integrations/tracing/fastify/index.ts

Lines changed: 68 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ interface FastifyHandlerOptions {
2727
*
2828
* @example
2929
*
30+
*
3031
* ```javascript
3132
* setupFastifyErrorHandler(app, {
3233
* shouldHandleError(_error, _request, reply) {
@@ -35,6 +36,20 @@ interface FastifyHandlerOptions {
3536
* });
3637
* ```
3738
*
39+
* or if you use Fastify v5 you can set options in the Sentry.init call:
40+
*
41+
* ```javascript
42+
* Sentry.init({
43+
* integrations: [
44+
* Sentry.fastifyIntegration({
45+
* shouldHandleError(_error, _request, reply) {
46+
* return reply.statusCode >= 500;
47+
* },
48+
* });
49+
* },
50+
* });
51+
* ```
52+
*
3853
* If using TypeScript, you can cast the request and reply to get full type safety.
3954
*
4055
* ```typescript
@@ -88,51 +103,65 @@ function handleFastifyError(
88103
}
89104
}
90105

91-
export const instrumentFastify = generateInstrumentOnce(INTEGRATION_NAME, () => {
92-
const fastifyOtelInstrumentationInstance = new FastifyOtelInstrumentation();
93-
const plugin = fastifyOtelInstrumentationInstance.plugin();
94-
const options = fastifyOtelInstrumentationInstance.getConfig();
95-
const shouldHandleError = (options as FastifyHandlerOptions)?.shouldHandleError || defaultShouldHandleError;
96-
97-
// This message handler works for Fastify versions 3, 4 and 5
98-
diagnosticsChannel.subscribe('fastify.initialization', message => {
99-
const fastifyInstance = (message as { fastify?: FastifyInstance }).fastify;
100-
101-
fastifyInstance?.register(plugin).after(err => {
102-
if (err) {
103-
DEBUG_BUILD && debug.error('Failed to setup Fastify instrumentation', err);
104-
} else {
105-
instrumentClient();
106-
107-
if (fastifyInstance) {
108-
instrumentOnRequest(fastifyInstance);
106+
export const instrumentFastify = generateInstrumentOnce(
107+
INTEGRATION_NAME,
108+
(
109+
options: Partial<FastifyHandlerOptions> = {
110+
shouldHandleError: defaultShouldHandleError,
111+
},
112+
) => {
113+
const fastifyOtelInstrumentationInstance = new FastifyOtelInstrumentation();
114+
const plugin = fastifyOtelInstrumentationInstance.plugin();
115+
116+
// This message handler works for Fastify versions 3, 4 and 5
117+
diagnosticsChannel.subscribe('fastify.initialization', message => {
118+
const fastifyInstance = (message as { fastify?: FastifyInstance }).fastify;
119+
120+
fastifyInstance?.register(plugin).after(err => {
121+
if (err) {
122+
DEBUG_BUILD && debug.error('Failed to setup Fastify instrumentation', err);
123+
} else {
124+
instrumentClient();
125+
126+
if (fastifyInstance) {
127+
instrumentOnRequest(fastifyInstance);
128+
}
109129
}
110-
}
130+
});
111131
});
112-
});
113-
114-
// This diagnostics channel only works on Fastify version 5
115-
// For versions 3 and 4, we use `setupFastifyErrorHandler` instead
116-
diagnosticsChannel.subscribe('tracing:fastify.request.handler:error', message => {
117-
const { error, request, reply } = message as {
118-
error: Error;
119-
request: FastifyRequest & { opentelemetry?: () => { span?: Span } };
120-
reply: FastifyReply;
121-
};
122132

123-
handleFastifyError.call(handleFastifyError, error, request, reply, shouldHandleError, 'diagnostics-channel');
124-
});
133+
// This diagnostics channel only works on Fastify version 5
134+
// For versions 3 and 4, we use `setupFastifyErrorHandler` instead
135+
diagnosticsChannel.subscribe('tracing:fastify.request.handler:error', message => {
136+
const { error, request, reply } = message as {
137+
error: Error;
138+
request: FastifyRequest & { opentelemetry?: () => { span?: Span } };
139+
reply: FastifyReply;
140+
};
141+
142+
handleFastifyError.call(
143+
handleFastifyError,
144+
error,
145+
request,
146+
reply,
147+
options.shouldHandleError,
148+
'diagnostics-channel',
149+
);
150+
});
125151

126-
// Returning this as unknown not to deal with the internal types of the FastifyOtelInstrumentation
127-
return fastifyOtelInstrumentationInstance as Instrumentation<InstrumentationConfig & FastifyHandlerOptions>;
128-
});
152+
// Returning this as unknown not to deal with the internal types of the FastifyOtelInstrumentation
153+
return fastifyOtelInstrumentationInstance as Instrumentation<InstrumentationConfig & FastifyHandlerOptions>;
154+
},
155+
);
129156

130-
const _fastifyIntegration = (() => {
157+
const _fastifyIntegration = (({ shouldHandleError }) => {
131158
return {
132159
name: INTEGRATION_NAME,
133160
setupOnce() {
134161
instrumentFastifyV3();
135-
instrumentFastify();
162+
instrumentFastify({
163+
shouldHandleError,
164+
});
136165
},
137166
};
138167
}) satisfies IntegrationFn;
@@ -153,7 +182,9 @@ const _fastifyIntegration = (() => {
153182
* })
154183
* ```
155184
*/
156-
export const fastifyIntegration = defineIntegration(_fastifyIntegration);
185+
export const fastifyIntegration = defineIntegration((options: Partial<FastifyHandlerOptions> = {}) =>
186+
_fastifyIntegration(options),
187+
);
157188

158189
/**
159190
* Default function to determine if an error should be sent to Sentry

0 commit comments

Comments
 (0)