66 InstrumentationNodeModuleFile ,
77 isWrapped ,
88 safeExecuteInTheMiddle ,
9- safeExecuteInTheMiddleAsync ,
109} from '@opentelemetry/instrumentation' ;
1110import type { SpanAttributes } from '@sentry/core' ;
1211import type {
@@ -28,7 +27,7 @@ import type {
2827 onObjectMetadataUpdated ,
2928} from 'firebase-functions/storage' ;
3029import type { FirebaseInstrumentation } from '../firebaseInstrumentation' ;
31- import type { FirebaseInstrumentationConfig , FunctionsSpanCreationHook } from '../types' ;
30+ import type { FirebaseInstrumentationConfig , RequestHook , ResponseHook } from '../types' ;
3231
3332/**
3433 * Patches Firebase Functions v2 to add OpenTelemetry instrumentation
@@ -45,15 +44,30 @@ export function patchFunctions(
4544 unwrap : InstrumentationBase [ '_unwrap' ] ,
4645 config : FirebaseInstrumentationConfig ,
4746) : InstrumentationNodeModuleDefinition {
48- const defaultFunctionsSpanCreationHook : FunctionsSpanCreationHook = ( ) => { } ;
47+ let requestHook : RequestHook = ( ) => { } ;
48+ let responseHook : ResponseHook = ( ) => { } ;
49+ const errorHook = config . functions ?. errorHook ;
50+ const configRequestHook = config . functions ?. requestHook ;
51+ const configResponseHook = config . functions ?. responseHook ;
4952
50- let functionsSpanCreationHook : FunctionsSpanCreationHook = defaultFunctionsSpanCreationHook ;
51- const configFunctionsSpanCreationHook = config . functionsSpanCreationHook ;
52-
53- if ( typeof configFunctionsSpanCreationHook === 'function' ) {
54- functionsSpanCreationHook = ( span : Span ) => {
53+ if ( typeof configResponseHook === 'function' ) {
54+ responseHook = ( span : Span , err : unknown ) => {
5555 safeExecuteInTheMiddle (
56- ( ) => configFunctionsSpanCreationHook ( span ) ,
56+ ( ) => configResponseHook ( span , err ) ,
57+ error => {
58+ if ( ! error ) {
59+ return ;
60+ }
61+ diag . error ( error ?. message ) ;
62+ } ,
63+ true ,
64+ ) ;
65+ } ;
66+ }
67+ if ( typeof configRequestHook === 'function' ) {
68+ requestHook = ( span : Span ) => {
69+ safeExecuteInTheMiddle (
70+ ( ) => configRequestHook ( span ) ,
5771 error => {
5872 if ( ! error ) {
5973 return ;
@@ -79,7 +93,14 @@ export function patchFunctions(
7993 name ,
8094 functionsSupportedVersions ,
8195 moduleExports =>
82- wrapCommonFunctions ( moduleExports , wrap , unwrap , tracer , functionsSpanCreationHook , triggerType ) ,
96+ wrapCommonFunctions (
97+ moduleExports ,
98+ wrap ,
99+ unwrap ,
100+ tracer ,
101+ { requestHook, responseHook, errorHook } ,
102+ triggerType ,
103+ ) ,
83104 moduleExports => unwrapCommonFunctions ( moduleExports , unwrap ) ,
84105 ) ,
85106 ) ;
@@ -123,17 +144,44 @@ type AvailableFirebaseFunctions = {
123144
124145type FirebaseFunctions = AvailableFirebaseFunctions [ keyof AvailableFirebaseFunctions ] ;
125146
147+ /**
148+ * Async function to execute patched function and being able to catch errors
149+ * @param execute - function to be executed
150+ * @param onFinish - callback to run when execute finishes
151+ */
152+ export async function safeExecuteInTheMiddleAsync < T > (
153+ execute : ( ) => T ,
154+ onFinish : ( e : Error | undefined , result : T | undefined ) => Promise < void > | void ,
155+ preventThrowingError ?: boolean ,
156+ ) : Promise < T > {
157+ let error : Error | undefined ;
158+ let result : T | undefined ;
159+ try {
160+ result = await execute ( ) ;
161+ } catch ( e ) {
162+ error = e as Error ;
163+ } finally {
164+ await onFinish ?.( error , result ) ;
165+ if ( error && ! preventThrowingError ) {
166+ // eslint-disable-next-line no-unsafe-finally
167+ throw error ;
168+ }
169+ // eslint-disable-next-line no-unsafe-finally
170+ return result as T ;
171+ }
172+ }
173+
126174/**
127175 * Patches Cloud Functions for Firebase (v2) to add OpenTelemetry instrumentation
128176 *
129177 * @param tracer - Opentelemetry Tracer
130- * @param functionsSpanCreationHook - Function to create a span for the function
178+ * @param functionsConfig - Firebase instrumentation config
131179 * @param triggerType - Type of trigger
132180 * @returns A function that patches the function
133181 */
134182export function patchV2Functions < T extends FirebaseFunctions = FirebaseFunctions > (
135183 tracer : Tracer ,
136- functionsSpanCreationHook : FunctionsSpanCreationHook ,
184+ functionsConfig : FirebaseInstrumentationConfig [ 'functions' ] ,
137185 triggerType : string ,
138186) : ( original : T ) => ( ...args : OverloadedParameters < T > ) => ReturnType < T > {
139187 return function v2FunctionsWrapper ( original : T ) : ( ...args : OverloadedParameters < T > ) => ReturnType < T > {
@@ -166,20 +214,35 @@ export function patchV2Functions<T extends FirebaseFunctions = FirebaseFunctions
166214 }
167215
168216 span . setAttributes ( attributes ) ;
169- functionsSpanCreationHook ( span ) ;
170-
171- return context . with ( trace . setSpan ( context . active ( ) , span ) , ( ) =>
172- safeExecuteInTheMiddleAsync (
173- ( ) => handler . apply ( this , handlerArgs ) ,
174- err => {
175- if ( err ) {
176- span . recordException ( err ) ;
177- }
178-
179- span . end ( ) ;
180- } ,
181- ) ,
182- ) ;
217+ functionsConfig ?. requestHook ?.( span ) ;
218+
219+ // Can be changed to safeExecuteInTheMiddleAsync once following is merged and released
220+ // https://github.com/open-telemetry/opentelemetry-js/pull/6032
221+ return context . with ( trace . setSpan ( context . active ( ) , span ) , async ( ) => {
222+ let error : Error | undefined ;
223+ let result : T | undefined ;
224+
225+ try {
226+ result = await handler . apply ( this , handlerArgs ) ;
227+ } catch ( e ) {
228+ error = e as Error ;
229+ }
230+
231+ functionsConfig ?. responseHook ?.( span , error ) ;
232+
233+ if ( error ) {
234+ span . recordException ( error ) ;
235+ }
236+
237+ span . end ( ) ;
238+
239+ if ( error ) {
240+ await functionsConfig ?. errorHook ?.( span , error ) ;
241+ throw error ;
242+ }
243+
244+ return result ;
245+ } ) ;
183246 } ;
184247
185248 if ( documentOrOptions ) {
@@ -196,86 +259,58 @@ function wrapCommonFunctions(
196259 wrap : InstrumentationBase < FirebaseInstrumentationConfig > [ '_wrap' ] ,
197260 unwrap : InstrumentationBase < FirebaseInstrumentationConfig > [ '_unwrap' ] ,
198261 tracer : Tracer ,
199- functionsSpanCreationHook : FunctionsSpanCreationHook ,
262+ functionsConfig : FirebaseInstrumentationConfig [ 'functions' ] ,
200263 triggerType : 'function' | 'firestore' | 'scheduler' | 'storage' ,
201264) : AvailableFirebaseFunctions {
202265 unwrapCommonFunctions ( moduleExports , unwrap ) ;
203266
204267 switch ( triggerType ) {
205268 case 'function' :
206- wrap ( moduleExports , 'onRequest' , patchV2Functions ( tracer , functionsSpanCreationHook , 'http.request' ) ) ;
207- wrap ( moduleExports , 'onCall' , patchV2Functions ( tracer , functionsSpanCreationHook , 'http.call' ) ) ;
269+ wrap ( moduleExports , 'onRequest' , patchV2Functions ( tracer , functionsConfig , 'http.request' ) ) ;
270+ wrap ( moduleExports , 'onCall' , patchV2Functions ( tracer , functionsConfig , 'http.call' ) ) ;
208271 break ;
209272
210273 case 'firestore' :
211- wrap (
212- moduleExports ,
213- 'onDocumentCreated' ,
214- patchV2Functions ( tracer , functionsSpanCreationHook , 'firestore.document.created' ) ,
215- ) ;
216- wrap (
217- moduleExports ,
218- 'onDocumentUpdated' ,
219- patchV2Functions ( tracer , functionsSpanCreationHook , 'firestore.document.updated' ) ,
220- ) ;
221- wrap (
222- moduleExports ,
223- 'onDocumentDeleted' ,
224- patchV2Functions ( tracer , functionsSpanCreationHook , 'firestore.document.deleted' ) ,
225- ) ;
226- wrap (
227- moduleExports ,
228- 'onDocumentWritten' ,
229- patchV2Functions ( tracer , functionsSpanCreationHook , 'firestore.document.written' ) ,
230- ) ;
274+ wrap ( moduleExports , 'onDocumentCreated' , patchV2Functions ( tracer , functionsConfig , 'firestore.document.created' ) ) ;
275+ wrap ( moduleExports , 'onDocumentUpdated' , patchV2Functions ( tracer , functionsConfig , 'firestore.document.updated' ) ) ;
276+ wrap ( moduleExports , 'onDocumentDeleted' , patchV2Functions ( tracer , functionsConfig , 'firestore.document.deleted' ) ) ;
277+ wrap ( moduleExports , 'onDocumentWritten' , patchV2Functions ( tracer , functionsConfig , 'firestore.document.written' ) ) ;
231278 wrap (
232279 moduleExports ,
233280 'onDocumentCreatedWithAuthContext' ,
234- patchV2Functions ( tracer , functionsSpanCreationHook , 'firestore.document.created' ) ,
281+ patchV2Functions ( tracer , functionsConfig , 'firestore.document.created' ) ,
235282 ) ;
236283 wrap (
237284 moduleExports ,
238285 'onDocumentUpdatedWithAuthContext' ,
239- patchV2Functions ( tracer , functionsSpanCreationHook , 'firestore.document.updated' ) ,
286+ patchV2Functions ( tracer , functionsConfig , 'firestore.document.updated' ) ,
240287 ) ;
241288
242289 wrap (
243290 moduleExports ,
244291 'onDocumentDeletedWithAuthContext' ,
245- patchV2Functions ( tracer , functionsSpanCreationHook , 'firestore.document.deleted' ) ,
292+ patchV2Functions ( tracer , functionsConfig , 'firestore.document.deleted' ) ,
246293 ) ;
247294
248295 wrap (
249296 moduleExports ,
250297 'onDocumentWrittenWithAuthContext' ,
251- patchV2Functions ( tracer , functionsSpanCreationHook , 'firestore.document.written' ) ,
298+ patchV2Functions ( tracer , functionsConfig , 'firestore.document.written' ) ,
252299 ) ;
253300 break ;
254301
255302 case 'scheduler' :
256- wrap ( moduleExports , 'onSchedule' , patchV2Functions ( tracer , functionsSpanCreationHook , 'scheduler.scheduled' ) ) ;
303+ wrap ( moduleExports , 'onSchedule' , patchV2Functions ( tracer , functionsConfig , 'scheduler.scheduled' ) ) ;
257304 break ;
258305
259306 case 'storage' :
260- wrap (
261- moduleExports ,
262- 'onObjectFinalized' ,
263- patchV2Functions ( tracer , functionsSpanCreationHook , 'storage.object.finalized' ) ,
264- ) ;
265- wrap (
266- moduleExports ,
267- 'onObjectArchived' ,
268- patchV2Functions ( tracer , functionsSpanCreationHook , 'storage.object.archived' ) ,
269- ) ;
270- wrap (
271- moduleExports ,
272- 'onObjectDeleted' ,
273- patchV2Functions ( tracer , functionsSpanCreationHook , 'storage.object.deleted' ) ,
274- ) ;
307+ wrap ( moduleExports , 'onObjectFinalized' , patchV2Functions ( tracer , functionsConfig , 'storage.object.finalized' ) ) ;
308+ wrap ( moduleExports , 'onObjectArchived' , patchV2Functions ( tracer , functionsConfig , 'storage.object.archived' ) ) ;
309+ wrap ( moduleExports , 'onObjectDeleted' , patchV2Functions ( tracer , functionsConfig , 'storage.object.deleted' ) ) ;
275310 wrap (
276311 moduleExports ,
277312 'onObjectMetadataUpdated' ,
278- patchV2Functions ( tracer , functionsSpanCreationHook , 'storage.object.metadataUpdated' ) ,
313+ patchV2Functions ( tracer , functionsConfig , 'storage.object.metadataUpdated' ) ,
279314 ) ;
280315 break ;
281316 }
0 commit comments