UNPKG

@sentry/node

Version:

Sentry Node SDK using OpenTelemetry for performance instrumentation

178 lines (147 loc) • 5.81 kB
var { _optionalChain } = require('@sentry/utils'); Object.defineProperty(exports, '__esModule', { value: true }); const api = require('@opentelemetry/api'); const core = require('@sentry/core'); const opentelemetry = require('@sentry/opentelemetry'); const utils = require('@sentry/utils'); const debugBuild = require('../debug-build.js'); const nodeVersion = require('../nodeVersion.js'); const addOriginToSpan = require('../utils/addOriginToSpan.js'); const _nativeNodeFetchIntegration = ((options = {}) => { const _breadcrumbs = typeof options.breadcrumbs === 'undefined' ? true : options.breadcrumbs; const _ignoreOutgoingRequests = options.ignoreOutgoingRequests; async function getInstrumentation() { // Only add NodeFetch if Node >= 18, as previous versions do not support it if (nodeVersion.NODE_MAJOR < 18) { debugBuild.DEBUG_BUILD && utils.logger.log('NodeFetch is not supported on Node < 18, skipping instrumentation...'); return; } try { const pkg = await import('opentelemetry-instrumentation-fetch-node'); const { FetchInstrumentation } = pkg; class SentryNodeFetchInstrumentation extends FetchInstrumentation { // We extend this method so we have access to request _and_ response for the breadcrumb onHeaders({ request, response }) { if (_breadcrumbs) { _addRequestBreadcrumb(request, response); } return super.onHeaders({ request, response }); } } return new SentryNodeFetchInstrumentation({ ignoreRequestHook: (request) => { const url = getAbsoluteUrl(request.origin, request.path); const tracingDisabled = !core.hasTracingEnabled(); const shouldIgnore = _ignoreOutgoingRequests && url && _ignoreOutgoingRequests(url); if (shouldIgnore) { return true; } // If tracing is disabled, we still want to propagate traces // So we do that manually here, matching what the instrumentation does otherwise if (tracingDisabled) { const ctx = api.context.active(); const addedHeaders = {}; // We generate a virtual span context from the active one, // Where we attach the URL to the trace state, so the propagator can pick it up const activeSpan = api.trace.getSpan(ctx); const propagationContext = activeSpan ? opentelemetry.getPropagationContextFromSpan(activeSpan) : core.getCurrentScope().getPropagationContext(); const spanContext = opentelemetry.generateSpanContextForPropagationContext(propagationContext); // We know that in practice we'll _always_ haven a traceState here spanContext.traceState = _optionalChain([spanContext, 'access', _ => _.traceState, 'optionalAccess', _2 => _2.set, 'call', _3 => _3('sentry.url', url)]); const ctxWithUrlTraceState = api.trace.setSpanContext(ctx, spanContext); api.propagation.inject(ctxWithUrlTraceState, addedHeaders); const requestHeaders = request.headers; if (Array.isArray(requestHeaders)) { Object.entries(addedHeaders).forEach(headers => requestHeaders.push(...headers)); } else { request.headers += Object.entries(addedHeaders) .map(([k, v]) => `${k}: ${v}\r\n`) .join(''); } // Prevent starting a span for this request return true; } return false; }, onRequest: ({ span }) => { _updateSpan(span); }, // eslint-disable-next-line @typescript-eslint/no-explicit-any } ); } catch (error) { // Could not load instrumentation debugBuild.DEBUG_BUILD && utils.logger.log('Error while loading NodeFetch instrumentation: \n', error); } } return { name: 'NodeFetch', setupOnce() { // eslint-disable-next-line @typescript-eslint/no-floating-promises getInstrumentation().then(instrumentation => { if (instrumentation) { opentelemetry.addOpenTelemetryInstrumentation(instrumentation); } }); }, }; }) ; const nativeNodeFetchIntegration = core.defineIntegration(_nativeNodeFetchIntegration); /** Update the span with data we need. */ function _updateSpan(span) { addOriginToSpan.addOriginToSpan(span, 'auto.http.otel.node_fetch'); } /** Add a breadcrumb for outgoing requests. */ function _addRequestBreadcrumb(request, response) { const data = getBreadcrumbData(request); core.addBreadcrumb( { category: 'http', data: { status_code: response.statusCode, ...data, }, type: 'http', }, { event: 'response', request, response, }, ); } function getBreadcrumbData(request) { try { const url = new URL(request.path, request.origin); const parsedUrl = utils.parseUrl(url.toString()); const data = { url: utils.getSanitizedUrlString(parsedUrl), 'http.method': request.method || 'GET', }; if (parsedUrl.search) { data['http.query'] = parsedUrl.search; } if (parsedUrl.hash) { data['http.fragment'] = parsedUrl.hash; } return data; } catch (e) { return {}; } } // Matching the behavior of the base instrumentation function getAbsoluteUrl(origin, path = '/') { const url = `${origin}`; if (origin.endsWith('/') && path.startsWith('/')) { return `${url}${path.slice(1)}`; } if (!origin.endsWith('/') && !path.startsWith('/')) { return `${url}/${path.slice(1)}`; } return `${url}${path}`; } exports.nativeNodeFetchIntegration = nativeNodeFetchIntegration; //# sourceMappingURL=node-fetch.js.map