From bb8ba5b6609890aa30e0052d686c07f87722a330 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Fri, 20 Sep 2024 11:52:32 +0000 Subject: [PATCH] Update trace fetcher wrapper --- .../nextjs/src/common/utils/wrapperUtils.ts | 98 ++++++++++--------- packages/nextjs/src/server/index.ts | 21 ++-- 2 files changed, 68 insertions(+), 51 deletions(-) diff --git a/packages/nextjs/src/common/utils/wrapperUtils.ts b/packages/nextjs/src/common/utils/wrapperUtils.ts index f07970e4db3b..754f57198b8e 100644 --- a/packages/nextjs/src/common/utils/wrapperUtils.ts +++ b/packages/nextjs/src/common/utils/wrapperUtils.ts @@ -6,6 +6,8 @@ import { SPAN_STATUS_OK, captureException, continueTrace, + getActiveSpan, + getRootSpan, getTraceData, startInactiveSpan, startSpan, @@ -17,7 +19,7 @@ import type { Span } from '@sentry/types'; import { isString } from '@sentry/utils'; import { autoEndSpanOnResponseEnd, flushSafelyWithTimeout } from './responseEnd'; -import { commonObjectToIsolationScope, escapeNextjsTracing } from './tracingUtils'; +import { commonObjectToIsolationScope } from './tracingUtils'; import { vercelWaitUntil } from './vercelWaitUntil'; declare module 'http' { @@ -94,49 +96,49 @@ export function withTracedServerSideDataFetcher Pr this: unknown, ...args: Parameters ): Promise<{ data: ReturnType; sentryTrace?: string; baggage?: string }> { - return escapeNextjsTracing(() => { - const isolationScope = commonObjectToIsolationScope(req); - return withIsolationScope(isolationScope, () => { - isolationScope.setTransactionName(`${options.dataFetchingMethodName} (${options.dataFetcherRouteName})`); - isolationScope.setSDKProcessingMetadata({ - request: req, - }); + const isolationScope = commonObjectToIsolationScope(req); + return withIsolationScope(isolationScope, () => { + isolationScope.setTransactionName(`${options.dataFetchingMethodName} (${options.dataFetcherRouteName})`); + isolationScope.setSDKProcessingMetadata({ + request: req, + }); - const sentryTrace = - req.headers && isString(req.headers['sentry-trace']) ? req.headers['sentry-trace'] : undefined; - const baggage = req.headers?.baggage; - - return continueTrace({ sentryTrace, baggage }, () => { - const requestSpan = getOrStartRequestSpan(req, res, options.requestedRouteName); - return withActiveSpan(requestSpan, () => { - return startSpanManual( - { - op: 'function.nextjs', - name: `${options.dataFetchingMethodName} (${options.dataFetcherRouteName})`, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - }, + const sentryTrace = + req.headers && isString(req.headers['sentry-trace']) ? req.headers['sentry-trace'] : undefined; + const baggage = req.headers?.baggage; + + return continueTrace({ sentryTrace, baggage }, () => { + const overarchingSpan = getOrStartOverarchingSpan(req, res, options.requestedRouteName); + getRootSpan(overarchingSpan).setAttribute('sentry.datafetcher', true); + + return withActiveSpan(overarchingSpan, () => { + return startSpanManual( + { + op: 'function.nextjs', + name: `${options.dataFetchingMethodName} (${options.dataFetcherRouteName})`, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', }, - async dataFetcherSpan => { - dataFetcherSpan.setStatus({ code: SPAN_STATUS_OK }); - const { 'sentry-trace': sentryTrace, baggage } = getTraceData(); - try { - return { - sentryTrace: sentryTrace, - baggage: baggage, - data: await origDataFetcher.apply(this, args), - }; - } catch (e) { - dataFetcherSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); - requestSpan?.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); - throw e; - } finally { - dataFetcherSpan.end(); - } - }, - ); - }); + }, + async dataFetcherSpan => { + dataFetcherSpan.setStatus({ code: SPAN_STATUS_OK }); + const { 'sentry-trace': sentryTrace, baggage } = getTraceData(); + try { + return { + sentryTrace: sentryTrace, + baggage: baggage, + data: await origDataFetcher.apply(this, args), + }; + } catch (e) { + dataFetcherSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); + overarchingSpan?.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); + throw e; + } finally { + dataFetcherSpan.end(); + } + }, + ); }); }); }).finally(() => { @@ -145,14 +147,22 @@ export function withTracedServerSideDataFetcher Pr }; } -function getOrStartRequestSpan(req: IncomingMessage, res: ServerResponse, name: string): Span { +/** + * TODO + */ +function getOrStartOverarchingSpan(req: IncomingMessage, res: ServerResponse, routeName: string): Span { + const activeSpan = getActiveSpan(); + if (activeSpan) { + return activeSpan; + } + const existingSpan = getSpanFromRequest(req); if (existingSpan) { return existingSpan; } const requestSpan = startInactiveSpan({ - name, + name: req.method ? `${req.method} ${routeName}` : routeName, forceTransaction: true, op: 'http.server', attributes: { diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index 1bfc57b44418..1361e6571126 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -14,6 +14,7 @@ import { GLOBAL_OBJ, logger } from '@sentry/utils'; import { ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_ROUTE, + ATTR_URL_QUERY, SEMATTRS_HTTP_METHOD, SEMATTRS_HTTP_TARGET, } from '@opentelemetry/semantic-conventions'; @@ -157,11 +158,14 @@ export function init(options: NodeOptions): NodeClient | undefined { // We need to drop these spans. if ( // eslint-disable-next-line deprecation/deprecation - typeof spanAttributes[SEMATTRS_HTTP_TARGET] === 'string' && - // eslint-disable-next-line deprecation/deprecation - spanAttributes[SEMATTRS_HTTP_TARGET].includes('sentry_key') && - // eslint-disable-next-line deprecation/deprecation - spanAttributes[SEMATTRS_HTTP_TARGET].includes('sentry_client') + (typeof spanAttributes[SEMATTRS_HTTP_TARGET] === 'string' && + // eslint-disable-next-line deprecation/deprecation + spanAttributes[SEMATTRS_HTTP_TARGET].includes('sentry_key') && + // eslint-disable-next-line deprecation/deprecation + spanAttributes[SEMATTRS_HTTP_TARGET].includes('sentry_client')) || + (typeof spanAttributes[ATTR_URL_QUERY] === 'string' && + spanAttributes[ATTR_URL_QUERY].includes('sentry_key') && + spanAttributes[ATTR_URL_QUERY].includes('sentry_client')) ) { samplingDecision.decision = false; } @@ -211,11 +215,14 @@ export function init(options: NodeOptions): NodeClient | undefined { return null; } - // We only want to use our HTTP integration/instrumentation for app router requests, which are marked with the `sentry.rsc` attribute. + // We only want to use our HTTP integration/instrumentation for + // - app router requests, which are marked with the `sentry.rsc` attribute. + // - pages router requests, which are marked with the `sentry.datafetcher` attribute. if ( (event.contexts?.trace?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] === 'auto.http.otel.http' || event.contexts?.trace?.data?.['next.span_type'] === 'BaseServer.handleRequest') && - event.contexts?.trace?.data?.['sentry.rsc'] !== true + event.contexts?.trace?.data?.['sentry.rsc'] !== true && + event.contexts?.trace?.data?.['sentry.datafetcher'] !== true ) { return null; }