From 134bf84c1a4e4f50c48638100c2b22fc56f9e4f2 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Tue, 24 Jun 2025 11:20:48 +0100 Subject: [PATCH 1/3] feat(node): Use diagnostics channel for Fastify v5 error handling --- .../node-fastify-5/src/app.ts | 2 - .../src/integrations/tracing/fastify/index.ts | 38 +++++++++++++++---- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-5/src/app.ts b/dev-packages/e2e-tests/test-applications/node-fastify-5/src/app.ts index 73ffafcfd04d..db2e9bf9cc5f 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-5/src/app.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify-5/src/app.ts @@ -34,8 +34,6 @@ const app = fastify(); const port = 3030; const port2 = 3040; -Sentry.setupFastifyErrorHandler(app); - app.get('/test-success', function (_req, res) { res.send({ version: 'v1' }); }); diff --git a/packages/node/src/integrations/tracing/fastify/index.ts b/packages/node/src/integrations/tracing/fastify/index.ts index 13805e18d575..9a286deb938a 100644 --- a/packages/node/src/integrations/tracing/fastify/index.ts +++ b/packages/node/src/integrations/tracing/fastify/index.ts @@ -60,6 +60,8 @@ export const instrumentFastifyV3 = generateInstrumentOnce(INTEGRATION_NAME_V3, ( export const instrumentFastify = generateInstrumentOnce(INTEGRATION_NAME, () => { const fastifyOtelInstrumentationInstance = new FastifyOtelInstrumentation(); const plugin = fastifyOtelInstrumentationInstance.plugin(); + const options = fastifyOtelInstrumentationInstance.getConfig(); + const shouldHandleError = (options as FastifyHandlerOptions)?.shouldHandleError || defaultShouldHandleError; // This message handler works for Fastify versions 3, 4 and 5 diagnosticsChannel.subscribe('fastify.initialization', message => { @@ -78,8 +80,22 @@ export const instrumentFastify = generateInstrumentOnce(INTEGRATION_NAME, () => }); }); + // This diagnostics channel only works on Fastify version 5 + // For versions 3 and 4, we use `setupFastifyErrorHandler` instead + diagnosticsChannel.subscribe('tracing:fastify.request.handler:error', message => { + const { error, request, reply } = message as { + error: Error; + request: FastifyRequest & { opentelemetry?: () => { span?: Span } }; + reply: FastifyReply; + }; + + if (shouldHandleError(error, request, reply)) { + captureException(error); + } + }); + // Returning this as unknown not to deal with the internal types of the FastifyOtelInstrumentation - return fastifyOtelInstrumentationInstance as Instrumentation; + return fastifyOtelInstrumentationInstance as Instrumentation; }); const _fastifyIntegration = (() => { @@ -143,15 +159,21 @@ function defaultShouldHandleError(_error: Error, _request: FastifyRequest, reply */ export function setupFastifyErrorHandler(fastify: FastifyInstance, options?: Partial): void { const shouldHandleError = options?.shouldHandleError || defaultShouldHandleError; - const plugin = Object.assign( function (fastify: FastifyInstance, _options: unknown, done: () => void): void { - fastify.addHook('onError', async (request, reply, error) => { - if (shouldHandleError(error, request, reply)) { - captureException(error); - } - }); - + if (fastify.version?.startsWith('5.')) { + // Fastify 5.x uses diagnostics channel for error handling + DEBUG_BUILD && + logger.warn( + 'Fastify 5.x detected, using diagnostics channel for error handling.\nYou can safely remove `setupFastifyErrorHandler` call.', + ); + } else { + fastify.addHook('onError', async (request, reply, error) => { + if (shouldHandleError(error, request, reply)) { + captureException(error); + } + }); + } done(); }, { From 74c80235bacc25b8b10c70f6898feac8b50a9623 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Wed, 25 Jun 2025 17:53:13 +0100 Subject: [PATCH 2/3] Remove Fastify version check --- .../src/integrations/tracing/fastify/index.ts | 49 +++++++++++++------ 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/packages/node/src/integrations/tracing/fastify/index.ts b/packages/node/src/integrations/tracing/fastify/index.ts index 9a286deb938a..8b59978f7363 100644 --- a/packages/node/src/integrations/tracing/fastify/index.ts +++ b/packages/node/src/integrations/tracing/fastify/index.ts @@ -57,6 +57,35 @@ const INTEGRATION_NAME_V3 = 'Fastify-V3'; export const instrumentFastifyV3 = generateInstrumentOnce(INTEGRATION_NAME_V3, () => new FastifyInstrumentationV3()); +function handleFastifyError( + this: any, + error: Error, + request: FastifyRequest & { opentelemetry?: () => { span?: Span } }, + reply: FastifyReply, + shouldHandleError: (error: Error, request: FastifyRequest, reply: FastifyReply) => boolean, + handlerOrigin: 'diagnostics-channel' | 'onError-hook', +): void { + // Diagnostics channel runs before the onError hook, so we can use it to check if the handler was already registered + if (handlerOrigin === 'diagnostics-channel') { + this.diagnosticsChannelExists = true; + } + + if (this.diagnosticsChannelExists && handlerOrigin === 'onError-hook') { + DEBUG_BUILD && + logger.warn( + 'Fastify error handler was already registered via diagnostics channel.', + 'You can safely remove `setupFastifyErrorHandler` call.', + ); + + // If the diagnostics channel already exists, we don't need to handle the error again + return; + } + + if (shouldHandleError(error, request, reply)) { + captureException(error); + } +} + export const instrumentFastify = generateInstrumentOnce(INTEGRATION_NAME, () => { const fastifyOtelInstrumentationInstance = new FastifyOtelInstrumentation(); const plugin = fastifyOtelInstrumentationInstance.plugin(); @@ -89,9 +118,7 @@ export const instrumentFastify = generateInstrumentOnce(INTEGRATION_NAME, () => reply: FastifyReply; }; - if (shouldHandleError(error, request, reply)) { - captureException(error); - } + handleFastifyError(error, request, reply, shouldHandleError, 'diagnostics-channel'); }); // Returning this as unknown not to deal with the internal types of the FastifyOtelInstrumentation @@ -161,19 +188,9 @@ export function setupFastifyErrorHandler(fastify: FastifyInstance, options?: Par const shouldHandleError = options?.shouldHandleError || defaultShouldHandleError; const plugin = Object.assign( function (fastify: FastifyInstance, _options: unknown, done: () => void): void { - if (fastify.version?.startsWith('5.')) { - // Fastify 5.x uses diagnostics channel for error handling - DEBUG_BUILD && - logger.warn( - 'Fastify 5.x detected, using diagnostics channel for error handling.\nYou can safely remove `setupFastifyErrorHandler` call.', - ); - } else { - fastify.addHook('onError', async (request, reply, error) => { - if (shouldHandleError(error, request, reply)) { - captureException(error); - } - }); - } + fastify.addHook('onError', async (request, reply, error) => { + handleFastifyError(error, request, reply, shouldHandleError, 'onError-hook'); + }); done(); }, { From 6b019acd87094e0e9d246251dc2ccb7aebffd6bc Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Wed, 25 Jun 2025 18:29:24 +0100 Subject: [PATCH 3/3] Pass self context --- packages/node/src/integrations/tracing/fastify/index.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/node/src/integrations/tracing/fastify/index.ts b/packages/node/src/integrations/tracing/fastify/index.ts index 8b59978f7363..b514cb80d32e 100644 --- a/packages/node/src/integrations/tracing/fastify/index.ts +++ b/packages/node/src/integrations/tracing/fastify/index.ts @@ -58,7 +58,9 @@ const INTEGRATION_NAME_V3 = 'Fastify-V3'; export const instrumentFastifyV3 = generateInstrumentOnce(INTEGRATION_NAME_V3, () => new FastifyInstrumentationV3()); function handleFastifyError( - this: any, + this: { + diagnosticsChannelExists?: boolean; + }, error: Error, request: FastifyRequest & { opentelemetry?: () => { span?: Span } }, reply: FastifyReply, @@ -118,7 +120,7 @@ export const instrumentFastify = generateInstrumentOnce(INTEGRATION_NAME, () => reply: FastifyReply; }; - handleFastifyError(error, request, reply, shouldHandleError, 'diagnostics-channel'); + handleFastifyError.call(handleFastifyError, error, request, reply, shouldHandleError, 'diagnostics-channel'); }); // Returning this as unknown not to deal with the internal types of the FastifyOtelInstrumentation @@ -189,7 +191,7 @@ export function setupFastifyErrorHandler(fastify: FastifyInstance, options?: Par const plugin = Object.assign( function (fastify: FastifyInstance, _options: unknown, done: () => void): void { fastify.addHook('onError', async (request, reply, error) => { - handleFastifyError(error, request, reply, shouldHandleError, 'onError-hook'); + handleFastifyError.call(handleFastifyError, error, request, reply, shouldHandleError, 'onError-hook'); }); done(); },