UNPKG

@sentry/node

Version:

Sentry Node SDK using OpenTelemetry for performance instrumentation

266 lines (233 loc) 8.22 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); const api = require('@opentelemetry/api'); const instrumentation = require('@opentelemetry/instrumentation'); /** * Patches Firebase Functions v2 to add OpenTelemetry instrumentation * @param tracer - Opentelemetry Tracer * @param functionsSupportedVersions - supported versions of firebase-functions * @param wrap - reference to native instrumentation wrap function * @param unwrap - reference to native instrumentation unwrap function * @param config - Firebase instrumentation config */ function patchFunctions( tracer, functionsSupportedVersions, wrap, unwrap, config, ) { let requestHook = () => {}; let responseHook = () => {}; const errorHook = config.functions?.errorHook; const configRequestHook = config.functions?.requestHook; const configResponseHook = config.functions?.responseHook; if (typeof configResponseHook === 'function') { responseHook = (span, err) => { instrumentation.safeExecuteInTheMiddle( () => configResponseHook(span, err), error => { if (!error) { return; } api.diag.error(error?.message); }, true, ); }; } if (typeof configRequestHook === 'function') { requestHook = (span) => { instrumentation.safeExecuteInTheMiddle( () => configRequestHook(span), error => { if (!error) { return; } api.diag.error(error?.message); }, true, ); }; } const moduleFunctionsCJS = new instrumentation.InstrumentationNodeModuleDefinition('firebase-functions', functionsSupportedVersions); const modulesToInstrument = [ { name: 'firebase-functions/lib/v2/providers/https.js', triggerType: 'function' }, { name: 'firebase-functions/lib/v2/providers/firestore.js', triggerType: 'firestore' }, { name: 'firebase-functions/lib/v2/providers/scheduler.js', triggerType: 'scheduler' }, { name: 'firebase-functions/lib/v2/storage.js', triggerType: 'storage' }, ] ; modulesToInstrument.forEach(({ name, triggerType }) => { moduleFunctionsCJS.files.push( new instrumentation.InstrumentationNodeModuleFile( name, functionsSupportedVersions, moduleExports => wrapCommonFunctions( moduleExports, wrap, unwrap, tracer, { requestHook, responseHook, errorHook }, triggerType, ), moduleExports => unwrapCommonFunctions(moduleExports, unwrap), ), ); }); return moduleFunctionsCJS; } /** * Patches Cloud Functions for Firebase (v2) to add OpenTelemetry instrumentation * * @param tracer - Opentelemetry Tracer * @param functionsConfig - Firebase instrumentation config * @param triggerType - Type of trigger * @returns A function that patches the function */ function patchV2Functions( tracer, functionsConfig, triggerType, ) { return function v2FunctionsWrapper(original) { return function ( ...args) { const handler = typeof args[0] === 'function' ? args[0] : args[1]; const documentOrOptions = typeof args[0] === 'function' ? undefined : args[0]; if (!handler) { return original.call(this, ...args); } const wrappedHandler = async function ( ...handlerArgs) { const functionName = process.env.FUNCTION_TARGET || process.env.K_SERVICE || 'unknown'; const span = tracer.startSpan(`firebase.function.${triggerType}`, { kind: api.SpanKind.SERVER, }); const attributes = { 'faas.name': functionName, 'faas.trigger': triggerType, 'faas.provider': 'firebase', }; if (process.env.GCLOUD_PROJECT) { attributes['cloud.project_id'] = process.env.GCLOUD_PROJECT; } if (process.env.EVENTARC_CLOUD_EVENT_SOURCE) { attributes['cloud.event_source'] = process.env.EVENTARC_CLOUD_EVENT_SOURCE; } span.setAttributes(attributes); functionsConfig?.requestHook?.(span); // Can be changed to safeExecuteInTheMiddleAsync once following is merged and released // https://github.com/open-telemetry/opentelemetry-js/pull/6032 return api.context.with(api.trace.setSpan(api.context.active(), span), async () => { let error; let result; try { result = await handler.apply(this, handlerArgs); } catch (e) { error = e ; } functionsConfig?.responseHook?.(span, error); if (error) { span.recordException(error); } span.end(); if (error) { await functionsConfig?.errorHook?.(span, error); throw error; } return result; }); }; if (documentOrOptions) { return original.call(this, documentOrOptions, wrappedHandler); } else { return original.call(this, wrappedHandler); } }; }; } function wrapCommonFunctions( moduleExports, wrap, unwrap, tracer, functionsConfig, triggerType, ) { unwrapCommonFunctions(moduleExports, unwrap); switch (triggerType) { case 'function': wrap(moduleExports, 'onRequest', patchV2Functions(tracer, functionsConfig, 'http.request')); wrap(moduleExports, 'onCall', patchV2Functions(tracer, functionsConfig, 'http.call')); break; case 'firestore': wrap(moduleExports, 'onDocumentCreated', patchV2Functions(tracer, functionsConfig, 'firestore.document.created')); wrap(moduleExports, 'onDocumentUpdated', patchV2Functions(tracer, functionsConfig, 'firestore.document.updated')); wrap(moduleExports, 'onDocumentDeleted', patchV2Functions(tracer, functionsConfig, 'firestore.document.deleted')); wrap(moduleExports, 'onDocumentWritten', patchV2Functions(tracer, functionsConfig, 'firestore.document.written')); wrap( moduleExports, 'onDocumentCreatedWithAuthContext', patchV2Functions(tracer, functionsConfig, 'firestore.document.created'), ); wrap( moduleExports, 'onDocumentUpdatedWithAuthContext', patchV2Functions(tracer, functionsConfig, 'firestore.document.updated'), ); wrap( moduleExports, 'onDocumentDeletedWithAuthContext', patchV2Functions(tracer, functionsConfig, 'firestore.document.deleted'), ); wrap( moduleExports, 'onDocumentWrittenWithAuthContext', patchV2Functions(tracer, functionsConfig, 'firestore.document.written'), ); break; case 'scheduler': wrap(moduleExports, 'onSchedule', patchV2Functions(tracer, functionsConfig, 'scheduler.scheduled')); break; case 'storage': wrap(moduleExports, 'onObjectFinalized', patchV2Functions(tracer, functionsConfig, 'storage.object.finalized')); wrap(moduleExports, 'onObjectArchived', patchV2Functions(tracer, functionsConfig, 'storage.object.archived')); wrap(moduleExports, 'onObjectDeleted', patchV2Functions(tracer, functionsConfig, 'storage.object.deleted')); wrap( moduleExports, 'onObjectMetadataUpdated', patchV2Functions(tracer, functionsConfig, 'storage.object.metadataUpdated'), ); break; } return moduleExports; } function unwrapCommonFunctions( moduleExports, unwrap, ) { const methods = [ 'onSchedule', 'onRequest', 'onCall', 'onObjectFinalized', 'onObjectArchived', 'onObjectDeleted', 'onObjectMetadataUpdated', 'onDocumentCreated', 'onDocumentUpdated', 'onDocumentDeleted', 'onDocumentWritten', 'onDocumentCreatedWithAuthContext', 'onDocumentUpdatedWithAuthContext', 'onDocumentDeletedWithAuthContext', 'onDocumentWrittenWithAuthContext', ]; for (const method of methods) { if (instrumentation.isWrapped(moduleExports[method])) { unwrap(moduleExports, method); } } return moduleExports; } exports.patchFunctions = patchFunctions; exports.patchV2Functions = patchV2Functions; //# sourceMappingURL=functions.js.map