@@ -12,19 +12,13 @@ import type { IntegrationFn, Span } from '@sentry/core';
1212import { generateInstrumentOnce } from '../../otel/instrument' ;
1313import { ensureIsWrapped } from '../../utils/ensureIsWrapped' ;
1414
15- // We inline the types we care about here
16- interface Fastify {
17- // eslint-disable-next-line @typescript-eslint/no-explicit-any
18- register : ( plugin : any ) => void ;
19- // eslint-disable-next-line @typescript-eslint/no-explicit-any
20- addHook : ( hook : string , handler : ( request : any , reply : any , error : Error ) => void ) => void ;
21- }
22-
2315/**
2416 * Minimal request type containing properties around route information.
2517 * Works for Fastify 3, 4 and presumably 5.
18+ *
19+ * Based on https://github.com/fastify/fastify/blob/ce3811f5f718be278bbcd4392c615d64230065a6/types/request.d.ts
2620 */
27- interface FastifyRequestRouteInfo {
21+ interface MinimalFastifyRequest {
2822 method ?: string ;
29233024 routeOptions ?: {
@@ -33,6 +27,64 @@ interface FastifyRequestRouteInfo {
3327 routerPath ?: string ;
3428}
3529
30+ /**
31+ * Minimal reply type containing properties needed for error handling.
32+ *
33+ * Based on https://github.com/fastify/fastify/blob/ce3811f5f718be278bbcd4392c615d64230065a6/types/reply.d.ts
34+ */
35+ interface MinimalFastifyReply {
36+ statusCode : number ;
37+ }
38+
39+ // We inline the types we care about here
40+ interface Fastify {
41+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
42+ register : ( plugin : any ) => void ;
43+ addHook (
44+ hook : 'onError' ,
45+ handler : ( request : MinimalFastifyRequest , reply : MinimalFastifyReply , error : Error ) => void ,
46+ ) : void ;
47+ addHook ( hook : 'onRequest' , handler : ( request : MinimalFastifyRequest , reply : MinimalFastifyReply ) => void ) : void ;
48+ }
49+
50+ interface FastifyHandlerOptions {
51+ /**
52+ * Callback method deciding whether error should be captured and sent to Sentry
53+ *
54+ * @param error Captured Fastify error
55+ * @param request Fastify request
56+ * @param reply Fastify reply
57+ *
58+ * @example
59+ *
60+ * ```javascript
61+ * setupFastifyErrorHandler(app, {
62+ * shouldHandleError(_error, _request, reply) {
63+ * return reply.statusCode >= 400;
64+ * },
65+ * });
66+ * ```
67+ *
68+ * If using TypeScript, pass in the `FastifyRequest` and `FastifyReply` types to get type safety.
69+ *
70+ * ```typescript
71+ * import type { FastifyRequest, FastifyReply } from 'fastify';
72+ *
73+ * setupFastifyErrorHandler(app, {
74+ * shouldHandleError<FastifyRequest, FastifyReply>(error, request, reply) {
75+ * return reply.statusCode >= 500;
76+ * },
77+ * });
78+ * ```
79+ */
80+ shouldHandleError < FastifyRequest extends MinimalFastifyRequest , FastifyReply extends MinimalFastifyReply > (
81+ this : void ,
82+ error : Error ,
83+ request : FastifyRequest ,
84+ reply : FastifyReply ,
85+ ) : boolean ;
86+ }
87+
3688const INTEGRATION_NAME = 'Fastify' ;
3789
3890export const instrumentFastify = generateInstrumentOnce (
@@ -73,10 +125,20 @@ const _fastifyIntegration = (() => {
73125 */
74126export const fastifyIntegration = defineIntegration ( _fastifyIntegration ) ;
75127
128+ /**
129+ * Default function to determine if an error should be sent to Sentry
130+ * Only sends 5xx errors by default
131+ */
132+ function defaultShouldHandleError ( _error : Error , _request : MinimalFastifyRequest , reply : MinimalFastifyReply ) : boolean {
133+ const statusCode = reply . statusCode ;
134+ return statusCode >= 500 ;
135+ }
136+
76137/**
77138 * Add an Fastify error handler to capture errors to Sentry.
78139 *
79140 * @param fastify The Fastify instance to which to add the error handler
141+ * @param options Configuration options for the handler
80142 *
81143 * @example
82144 * ```javascript
@@ -92,23 +154,25 @@ export const fastifyIntegration = defineIntegration(_fastifyIntegration);
92154 * app.listen({ port: 3000 });
93155 * ```
94156 */
95- export function setupFastifyErrorHandler ( fastify : Fastify ) : void {
157+ export function setupFastifyErrorHandler ( fastify : Fastify , options ?: Partial < FastifyHandlerOptions > ) : void {
158+ const shouldHandleError = options ?. shouldHandleError || defaultShouldHandleError ;
159+
96160 const plugin = Object . assign (
97161 function ( fastify : Fastify , _options : unknown , done : ( ) => void ) : void {
98- fastify . addHook ( 'onError' , async ( _request , _reply , error ) => {
99- captureException ( error ) ;
162+ fastify . addHook ( 'onError' , async ( request , reply , error ) => {
163+ if ( shouldHandleError ( error , request , reply ) ) {
164+ captureException ( error ) ;
165+ }
100166 } ) ;
101167
102168 // registering `onRequest` hook here instead of using Otel `onRequest` callback b/c `onRequest` hook
103169 // is ironically called in the fastify `preHandler` hook which is called later in the lifecycle:
104170 // https://fastify.dev/docs/latest/Reference/Lifecycle/
105171 fastify . addHook ( 'onRequest' , async ( request , _reply ) => {
106- const reqWithRouteInfo = request as FastifyRequestRouteInfo ;
107-
108172 // Taken from Otel Fastify instrumentation:
109173 // https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/plugins/node/opentelemetry-instrumentation-fastify/src/instrumentation.ts#L94-L96
110- const routeName = reqWithRouteInfo . routeOptions ?. url || reqWithRouteInfo . routerPath ;
111- const method = reqWithRouteInfo . method || 'GET' ;
174+ const routeName = request . routeOptions ?. url || request . routerPath ;
175+ const method = request . method || 'GET' ;
112176
113177 getIsolationScope ( ) . setTransactionName ( `${ method } ${ routeName } ` ) ;
114178 } ) ;
@@ -133,6 +197,7 @@ export function setupFastifyErrorHandler(fastify: Fastify): void {
133197 } ) ;
134198 }
135199
200+ // eslint-disable-next-line @typescript-eslint/unbound-method
136201 ensureIsWrapped ( fastify . addHook , 'fastify' ) ;
137202}
138203
0 commit comments