From 464d72531ce6828b4a4c08b855674172a9ba4163 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 19 Nov 2024 13:28:20 +0100 Subject: [PATCH 01/30] ref: Streamline sentry-trace, baggage and DSC handling --- packages/browser/src/tracing/request.ts | 24 +------ packages/core/src/baseclient.ts | 31 +++------- packages/core/src/fetch.ts | 58 +++++------------ packages/core/src/index.ts | 4 ++ packages/core/src/propagationContext.ts | 62 +++++++++++++++++++ packages/core/src/server-runtime-client.ts | 32 +++------- .../src/tracing/dynamicSamplingContext.ts | 3 +- packages/core/src/tracing/index.ts | 1 + packages/core/src/tracing/sentryHeaders.ts | 23 +++++++ packages/core/src/utils/traceData.ts | 34 +++------- packages/node/src/sdk/client.ts | 15 ++++- packages/opentelemetry/src/index.ts | 9 ++- packages/opentelemetry/src/trace.ts | 24 ++++++- packages/remix/src/utils/instrumentServer.ts | 24 +++---- packages/utils/src/tracing.ts | 30 ++++----- 15 files changed, 205 insertions(+), 169 deletions(-) create mode 100644 packages/core/src/propagationContext.ts create mode 100644 packages/core/src/tracing/sentryHeaders.ts diff --git a/packages/browser/src/tracing/request.ts b/packages/browser/src/tracing/request.ts index b41a0bf24cc6..d7cafa6b1cd3 100644 --- a/packages/browser/src/tracing/request.ts +++ b/packages/browser/src/tracing/request.ts @@ -10,15 +10,11 @@ import { SentryNonRecordingSpan, getActiveSpan, getClient, - getCurrentScope, - getDynamicSamplingContextFromClient, - getDynamicSamplingContextFromSpan, - getIsolationScope, + getSentryHeaders, hasTracingEnabled, instrumentFetchRequest, setHttpStatus, spanToJSON, - spanToTraceHeader, startInactiveSpan, } from '@sentry/core'; import type { Client, HandlerDataXhr, SentryWrappedXMLHttpRequest, Span } from '@sentry/types'; @@ -27,8 +23,6 @@ import { addFetchEndInstrumentationHandler, addFetchInstrumentationHandler, browserPerformanceTimeOrigin, - dynamicSamplingContextToSentryBaggageHeader, - generateSentryTraceHeader, parseUrl, stringMatchesSomePattern, } from '@sentry/utils'; @@ -419,21 +413,9 @@ export function xhrCallback( } function addTracingHeadersToXhrRequest(xhr: SentryWrappedXMLHttpRequest, client: Client, span?: Span): void { - const scope = getCurrentScope(); - const isolationScope = getIsolationScope(); - const { traceId, spanId, sampled, dsc } = { - ...isolationScope.getPropagationContext(), - ...scope.getPropagationContext(), - }; - - const sentryTraceHeader = - span && hasTracingEnabled() ? spanToTraceHeader(span) : generateSentryTraceHeader(traceId, spanId, sampled); - - const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader( - dsc || (span ? getDynamicSamplingContextFromSpan(span) : getDynamicSamplingContextFromClient(traceId, client)), - ); + const { sentryTrace, baggage } = getSentryHeaders({ span, client }); - setHeaderOnXhr(xhr, sentryTraceHeader, sentryBaggageHeader); + setHeaderOnXhr(xhr, sentryTrace, baggage); } function setHeaderOnXhr( diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index db79df0d1c16..a88213a0ba07 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -37,7 +37,6 @@ import { checkOrSetAlreadyCaught, createAttachmentEnvelopeItem, createClientReportEnvelope, - dropUndefinedKeys, dsnToString, isParameterizedString, isPlainObject, @@ -57,9 +56,9 @@ import { createEventEnvelope, createSessionEnvelope } from './envelope'; import type { IntegrationIndex } from './integration'; import { afterSetupIntegrations } from './integration'; import { setupIntegration, setupIntegrations } from './integration'; +import { getDynamicSamplingContextFromScopes, getTraceContextFromScopes } from './propagationContext'; import type { Scope } from './scope'; import { updateSession } from './session'; -import { getDynamicSamplingContextFromClient } from './tracing/dynamicSamplingContext'; import { parseSampleRate } from './utils/parseSampleRate'; import { prepareEvent } from './utils/prepareEvent'; @@ -689,30 +688,18 @@ export abstract class BaseClient implements Client { return evt; } - const propagationContext = { - ...isolationScope.getPropagationContext(), - ...(currentScope ? currentScope.getPropagationContext() : undefined), + evt.contexts = { + trace: getTraceContextFromScopes(currentScope, isolationScope), + ...evt.contexts, }; - const trace = evt.contexts && evt.contexts.trace; - if (!trace && propagationContext) { - const { traceId: trace_id, spanId, parentSpanId, dsc } = propagationContext; - evt.contexts = { - trace: dropUndefinedKeys({ - trace_id, - span_id: spanId, - parent_span_id: parentSpanId, - }), - ...evt.contexts, - }; + const dynamicSamplingContext = getDynamicSamplingContextFromScopes(this, currentScope, isolationScope); - const dynamicSamplingContext = dsc ? dsc : getDynamicSamplingContextFromClient(trace_id, this); + evt.sdkProcessingMetadata = { + dynamicSamplingContext, + ...evt.sdkProcessingMetadata, + }; - evt.sdkProcessingMetadata = { - dynamicSamplingContext, - ...evt.sdkProcessingMetadata, - }; - } return evt; }); } diff --git a/packages/core/src/fetch.ts b/packages/core/src/fetch.ts index 29ff5d074cd0..7d2d34bca7cb 100644 --- a/packages/core/src/fetch.ts +++ b/packages/core/src/fetch.ts @@ -1,24 +1,11 @@ import type { Client, HandlerDataFetch, Scope, Span, SpanOrigin } from '@sentry/types'; -import { - BAGGAGE_HEADER_NAME, - SENTRY_BAGGAGE_KEY_PREFIX, - dynamicSamplingContextToSentryBaggageHeader, - generateSentryTraceHeader, - isInstanceOf, - parseUrl, -} from '@sentry/utils'; -import { getClient, getCurrentScope, getIsolationScope } from './currentScopes'; +import { BAGGAGE_HEADER_NAME, SENTRY_BAGGAGE_KEY_PREFIX, isInstanceOf, parseUrl } from '@sentry/utils'; +import { getClient, getCurrentScope } from './currentScopes'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from './semanticAttributes'; -import { - SPAN_STATUS_ERROR, - getDynamicSamplingContextFromClient, - getDynamicSamplingContextFromSpan, - setHttpStatus, - startInactiveSpan, -} from './tracing'; +import { SPAN_STATUS_ERROR, getSentryHeaders, setHttpStatus, startInactiveSpan } from './tracing'; import { SentryNonRecordingSpan } from './tracing/sentryNonRecordingSpan'; import { hasTracingEnabled } from './utils/hasTracingEnabled'; -import { getActiveSpan, spanToTraceHeader } from './utils/spanUtils'; +import { getActiveSpan } from './utils/spanUtils'; type PolymorphicRequestHeaders = | Record @@ -132,31 +119,20 @@ export function addTracingHeadersToFetchRequest( }, span?: Span, ): PolymorphicRequestHeaders | undefined { - const isolationScope = getIsolationScope(); - - const { traceId, spanId, sampled, dsc } = { - ...isolationScope.getPropagationContext(), - ...scope.getPropagationContext(), - }; - - const sentryTraceHeader = span ? spanToTraceHeader(span) : generateSentryTraceHeader(traceId, spanId, sampled); - - const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader( - dsc || (span ? getDynamicSamplingContextFromSpan(span) : getDynamicSamplingContextFromClient(traceId, client)), - ); + const { sentryTrace, baggage } = getSentryHeaders({ span, client, scope }); const headers = fetchOptionsObj.headers || (typeof Request !== 'undefined' && isInstanceOf(request, Request) ? (request as Request).headers : undefined); if (!headers) { - return { 'sentry-trace': sentryTraceHeader, baggage: sentryBaggageHeader }; + return { 'sentry-trace': sentryTrace, baggage }; } else if (typeof Headers !== 'undefined' && isInstanceOf(headers, Headers)) { const newHeaders = new Headers(headers as Headers); - newHeaders.set('sentry-trace', sentryTraceHeader); + newHeaders.set('sentry-trace', sentryTrace); - if (sentryBaggageHeader) { + if (baggage) { const prevBaggageHeader = newHeaders.get(BAGGAGE_HEADER_NAME); if (prevBaggageHeader) { const prevHeaderStrippedFromSentryBaggage = stripBaggageHeaderOfSentryBaggageValues(prevBaggageHeader); @@ -164,12 +140,10 @@ export function addTracingHeadersToFetchRequest( BAGGAGE_HEADER_NAME, // If there are non-sentry entries (i.e. if the stripped string is non-empty/truthy) combine the stripped header and sentry baggage header // otherwise just set the sentry baggage header - prevHeaderStrippedFromSentryBaggage - ? `${prevHeaderStrippedFromSentryBaggage},${sentryBaggageHeader}` - : sentryBaggageHeader, + prevHeaderStrippedFromSentryBaggage ? `${prevHeaderStrippedFromSentryBaggage},${baggage}` : baggage, ); } else { - newHeaders.set(BAGGAGE_HEADER_NAME, sentryBaggageHeader); + newHeaders.set(BAGGAGE_HEADER_NAME, baggage); } } @@ -191,13 +165,13 @@ export function addTracingHeadersToFetchRequest( } }), // Attach the new sentry-trace header - ['sentry-trace', sentryTraceHeader], + ['sentry-trace', sentryTrace], ]; - if (sentryBaggageHeader) { + if (baggage) { // If there are multiple entries with the same key, the browser will merge the values into a single request header. // Its therefore safe to simply push a "baggage" entry, even though there might already be another baggage header. - newHeaders.push([BAGGAGE_HEADER_NAME, sentryBaggageHeader]); + newHeaders.push([BAGGAGE_HEADER_NAME, baggage]); } return newHeaders as PolymorphicRequestHeaders; @@ -215,13 +189,13 @@ export function addTracingHeadersToFetchRequest( newBaggageHeaders.push(stripBaggageHeaderOfSentryBaggageValues(existingBaggageHeader)); } - if (sentryBaggageHeader) { - newBaggageHeaders.push(sentryBaggageHeader); + if (baggage) { + newBaggageHeaders.push(baggage); } return { ...(headers as Exclude), - 'sentry-trace': sentryTraceHeader, + 'sentry-trace': sentryTrace, baggage: newBaggageHeaders.length > 0 ? newBaggageHeaders.join(',') : undefined, }; } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 779c01830d92..ee2fe10d0d94 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -43,6 +43,10 @@ export { getDefaultCurrentScope, getDefaultIsolationScope, } from './defaultScopes'; +export { + getDynamicSamplingContextFromScopes, + getTraceContextFromScopes, +} from './propagationContext'; export { setAsyncContextStrategy } from './asyncContext'; export { getMainCarrier } from './carrier'; export { makeSession, closeSession, updateSession } from './session'; diff --git a/packages/core/src/propagationContext.ts b/packages/core/src/propagationContext.ts new file mode 100644 index 000000000000..b8c49bba1e95 --- /dev/null +++ b/packages/core/src/propagationContext.ts @@ -0,0 +1,62 @@ +import type { Client, DynamicSamplingContext, PropagationContext, TraceContext } from '@sentry/types'; +import { dropUndefinedKeys, generateSentryTraceHeader } from '@sentry/utils'; +import { getCurrentScope, getGlobalScope, getIsolationScope } from './currentScopes'; +import { getDynamicSamplingContextFromClient } from './tracing'; + +/** + * Get a trace context for the currently active scopes. + */ +export function getTraceContextFromScopes( + scope = getCurrentScope(), + isolationScope = getIsolationScope(), + globalScope = getGlobalScope(), +): TraceContext { + const propagationContext = mergePropagationContexts(scope, isolationScope, globalScope); + + const { traceId, spanId, parentSpanId } = propagationContext; + + const traceContext: TraceContext = dropUndefinedKeys({ + trace_id: traceId, + span_id: spanId, + parent_span_id: parentSpanId, + }); + + return traceContext; +} + +/** + * Get a sentry-trace header value for the currently active scopes. + */ +export function scopesToTraceHeader( + scope = getCurrentScope(), + isolationScope = getIsolationScope(), + globalScope = getGlobalScope(), +): string { + const { traceId, sampled, spanId } = mergePropagationContexts(scope, isolationScope, globalScope); + return generateSentryTraceHeader(traceId, spanId, sampled); +} + +/** + * Get the dynamic sampling context for the currently active scopes. + */ +export function getDynamicSamplingContextFromScopes( + client: Client, + scope = getCurrentScope(), + isolationScope = getIsolationScope(), + globalScope = getGlobalScope(), +): Partial { + const propagationContext = mergePropagationContexts(scope, isolationScope, globalScope); + return propagationContext.dsc || getDynamicSamplingContextFromClient(propagationContext.traceId, client); +} + +function mergePropagationContexts( + scope = getCurrentScope(), + isolationScope = getIsolationScope(), + globalScope = getGlobalScope(), +): PropagationContext { + return { + ...globalScope.getPropagationContext(), + ...isolationScope.getPropagationContext(), + ...scope.getPropagationContext(), + }; +} diff --git a/packages/core/src/server-runtime-client.ts b/packages/core/src/server-runtime-client.ts index 6d1a3683b05b..aeaea2961601 100644 --- a/packages/core/src/server-runtime-client.ts +++ b/packages/core/src/server-runtime-client.ts @@ -17,15 +17,12 @@ import { BaseClient } from './baseclient'; import { createCheckInEnvelope } from './checkin'; import { getIsolationScope } from './currentScopes'; import { DEBUG_BUILD } from './debug-build'; +import { getDynamicSamplingContextFromScopes, getTraceContextFromScopes } from './propagationContext'; import type { Scope } from './scope'; import { SessionFlusher } from './sessionflusher'; -import { - getDynamicSamplingContextFromClient, - getDynamicSamplingContextFromSpan, - registerSpanErrorInstrumentation, -} from './tracing'; +import { getDynamicSamplingContextFromSpan, registerSpanErrorInstrumentation } from './tracing'; import { _getSpanForScope } from './utils/spanOnScope'; -import { getRootSpan, spanToTraceContext } from './utils/spanUtils'; +import { spanToTraceContext } from './utils/spanUtils'; export interface ServerRuntimeClientOptions extends ClientOptions { platform?: string; @@ -248,7 +245,7 @@ export class ServerRuntimeClient< } /** Extract trace information from scope */ - private _getTraceInfoFromScope( + protected _getTraceInfoFromScope( scope: Scope | undefined, ): [dynamicSamplingContext: Partial | undefined, traceContext: TraceContext | undefined] { if (!scope) { @@ -256,22 +253,11 @@ export class ServerRuntimeClient< } const span = _getSpanForScope(scope); - if (span) { - const rootSpan = getRootSpan(span); - const samplingContext = getDynamicSamplingContextFromSpan(rootSpan); - return [samplingContext, spanToTraceContext(rootSpan)]; - } - - const { traceId, spanId, parentSpanId, dsc } = scope.getPropagationContext(); - const traceContext: TraceContext = { - trace_id: traceId, - span_id: spanId, - parent_span_id: parentSpanId, - }; - if (dsc) { - return [dsc, traceContext]; - } - return [getDynamicSamplingContextFromClient(traceId, this), traceContext]; + const traceContext = span ? spanToTraceContext(span) : getTraceContextFromScopes(scope); + const dynamicSamplingContext = span + ? getDynamicSamplingContextFromSpan(span) + : getDynamicSamplingContextFromScopes(this, scope); + return [dynamicSamplingContext, traceContext]; } } diff --git a/packages/core/src/tracing/dynamicSamplingContext.ts b/packages/core/src/tracing/dynamicSamplingContext.ts index d96dd726d51f..0e96788be8d9 100644 --- a/packages/core/src/tracing/dynamicSamplingContext.ts +++ b/packages/core/src/tracing/dynamicSamplingContext.ts @@ -65,8 +65,6 @@ export function getDynamicSamplingContextFromSpan(span: Span): Readonly { process.on('beforeExit', this._clientReportOnExitFlushListener); } } + + /** Custom implementation for OTEL, so we can handle scope-span linking. */ + protected _getTraceInfoFromScope( + scope: Scope | undefined, + ): [dynamicSamplingContext: Partial | undefined, traceContext: TraceContext | undefined] { + if (!scope) { + return [undefined, undefined]; + } + + return getTraceContextForScope(this, scope); + } } diff --git a/packages/opentelemetry/src/index.ts b/packages/opentelemetry/src/index.ts index 98460b575c8d..55f657061989 100644 --- a/packages/opentelemetry/src/index.ts +++ b/packages/opentelemetry/src/index.ts @@ -27,7 +27,14 @@ export { enhanceDscWithOpenTelemetryRootSpanName } from './utils/enhanceDscWithO export { generateSpanContextForPropagationContext } from './utils/generateSpanContextForPropagationContext'; export { getActiveSpan } from './utils/getActiveSpan'; -export { startSpan, startSpanManual, startInactiveSpan, withActiveSpan, continueTrace } from './trace'; +export { + startSpan, + startSpanManual, + startInactiveSpan, + withActiveSpan, + continueTrace, + getTraceContextForScope, +} from './trace'; export { suppressTracing } from './utils/suppressTracing'; diff --git a/packages/opentelemetry/src/trace.ts b/packages/opentelemetry/src/trace.ts index 6ba41eec51e2..f594ee84755f 100644 --- a/packages/opentelemetry/src/trace.ts +++ b/packages/opentelemetry/src/trace.ts @@ -7,12 +7,15 @@ import { continueTrace as baseContinueTrace, getClient, getCurrentScope, + getDynamicSamplingContextFromScopes, getDynamicSamplingContextFromSpan, getRootSpan, + getTraceContextFromScopes, handleCallbackErrors, spanToJSON, + spanToTraceContext, } from '@sentry/core'; -import type { Client, Scope, Span as SentrySpan } from '@sentry/types'; +import type { Client, DynamicSamplingContext, Scope, Span as SentrySpan, TraceContext } from '@sentry/types'; import { continueTraceAsRemoteSpan } from './propagator'; import type { OpenTelemetryClient, OpenTelemetrySpanContext } from './types'; @@ -278,6 +281,25 @@ export function continueTrace(options: Parameters[0 }); } +/** + * Get the trace context for a given scope. + * We have a custom implemention here because we need an OTEL-specific way to get the span from a scope. + */ +export function getTraceContextForScope( + client: Client, + scope: Scope, +): [dynamicSamplingContext: Partial, traceContext: TraceContext] { + const ctx = getContextFromScope(scope); + const span = ctx && trace.getSpan(ctx); + + const traceContext = span ? spanToTraceContext(span) : getTraceContextFromScopes(scope); + + const dynamicSamplingContext = span + ? getDynamicSamplingContextFromSpan(span) + : getDynamicSamplingContextFromScopes(client, scope); + return [dynamicSamplingContext, traceContext]; +} + function getActiveSpanWrapper(parentSpan: Span | SentrySpan | undefined | null): (callback: () => T) => T { return parentSpan !== undefined ? (callback: () => T) => { diff --git a/packages/remix/src/utils/instrumentServer.ts b/packages/remix/src/utils/instrumentServer.ts index 666c332afa04..942f8b17c8df 100644 --- a/packages/remix/src/utils/instrumentServer.ts +++ b/packages/remix/src/utils/instrumentServer.ts @@ -6,17 +6,17 @@ import { getActiveSpan, getClient, getRootSpan, + getSentryHeaders, hasTracingEnabled, setHttpStatus, spanToJSON, - spanToTraceHeader, startSpan, withIsolationScope, } from '@sentry/core'; -import { continueTrace, getDynamicSamplingContextFromSpan } from '@sentry/opentelemetry'; +import { continueTrace } from '@sentry/opentelemetry'; import type { TransactionSource, WrappedFunction } from '@sentry/types'; import type { Span } from '@sentry/types'; -import { dynamicSamplingContextToSentryBaggageHeader, fill, isNodeEnv, loadModule, logger } from '@sentry/utils'; +import { fill, isNodeEnv, loadModule, logger } from '@sentry/utils'; import { DEBUG_BUILD } from './debug-build'; import { captureRemixServerException, errorHandleDataFunction, errorHandleDocumentRequestFunction } from './errors'; @@ -204,18 +204,14 @@ function getTraceAndBaggage(): { sentryTrace?: string; sentryBaggage?: string; } { - if (isNodeEnv() && hasTracingEnabled()) { - const span = getActiveSpan(); - const rootSpan = span && getRootSpan(span); + const client = getClient(); + if (isNodeEnv() && client) { + const { sentryTrace, baggage } = getSentryHeaders({ client, span: getActiveSpan() }); - if (rootSpan) { - const dynamicSamplingContext = getDynamicSamplingContextFromSpan(rootSpan); - - return { - sentryTrace: spanToTraceHeader(span), - sentryBaggage: dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext), - }; - } + return { + sentryTrace, + sentryBaggage: baggage, + }; } return {}; diff --git a/packages/utils/src/tracing.ts b/packages/utils/src/tracing.ts index 69a18f1a4c38..04e041407f33 100644 --- a/packages/utils/src/tracing.ts +++ b/packages/utils/src/tracing.ts @@ -2,6 +2,7 @@ import type { PropagationContext, TraceparentData } from '@sentry/types'; import { baggageHeaderToDynamicSamplingContext } from './baggage'; import { uuid4 } from './misc'; +import { generatePropagationContext } from './propagationContext'; // eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor -- RegExp is used for readability here export const TRACEPARENT_REGEXP = new RegExp( @@ -54,22 +55,21 @@ export function propagationContextFromHeaders( const traceparentData = extractTraceparentData(sentryTrace); const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(baggage); - const { traceId, parentSpanId, parentSampled } = traceparentData || {}; - - if (!traceparentData) { - return { - traceId: traceId || uuid4(), - spanId: uuid4().substring(16), - }; - } else { - return { - traceId: traceId || uuid4(), - parentSpanId: parentSpanId || uuid4().substring(16), - spanId: uuid4().substring(16), - sampled: parentSampled, - dsc: dynamicSamplingContext || {}, // If we have traceparent data but no DSC it means we are not head of trace and we must freeze it - }; + if (!traceparentData || !traceparentData.traceId) { + return generatePropagationContext(); } + + const { traceId, parentSpanId, parentSampled } = traceparentData; + + const virtualSpanId = uuid4().substring(16); + + return { + traceId, + parentSpanId, + spanId: virtualSpanId, + sampled: parentSampled, + dsc: dynamicSamplingContext || {}, // If we have traceparent data but no DSC it means we are not head of trace and we must freeze it + }; } /** From b31203e2bde76321ff8a9eeecde30ca3b7d40928 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 19 Nov 2024 13:40:29 +0100 Subject: [PATCH 02/30] move files around --- packages/core/src/baseclient.ts | 4 +- packages/core/src/currentScopes.ts | 50 ++++++++++++++- packages/core/src/index.ts | 5 +- packages/core/src/propagationContext.ts | 62 ------------------- packages/core/src/server-runtime-client.ts | 9 ++- .../src/tracing/dynamicSamplingContext.ts | 21 ++++++- packages/core/src/tracing/index.ts | 1 + packages/core/src/tracing/sentryHeaders.ts | 4 +- 8 files changed, 80 insertions(+), 76 deletions(-) delete mode 100644 packages/core/src/propagationContext.ts diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index a88213a0ba07..ed87a04d15f6 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -50,15 +50,15 @@ import { } from '@sentry/utils'; import { getEnvelopeEndpointWithUrlEncodedAuth } from './api'; -import { getIsolationScope } from './currentScopes'; +import { getIsolationScope, getTraceContextFromScopes } from './currentScopes'; import { DEBUG_BUILD } from './debug-build'; import { createEventEnvelope, createSessionEnvelope } from './envelope'; import type { IntegrationIndex } from './integration'; import { afterSetupIntegrations } from './integration'; import { setupIntegration, setupIntegrations } from './integration'; -import { getDynamicSamplingContextFromScopes, getTraceContextFromScopes } from './propagationContext'; import type { Scope } from './scope'; import { updateSession } from './session'; +import { getDynamicSamplingContextFromScopes } from './tracing/dynamicSamplingContext'; import { parseSampleRate } from './utils/parseSampleRate'; import { prepareEvent } from './utils/prepareEvent'; diff --git a/packages/core/src/currentScopes.ts b/packages/core/src/currentScopes.ts index 317042c08054..255e40d68875 100644 --- a/packages/core/src/currentScopes.ts +++ b/packages/core/src/currentScopes.ts @@ -1,6 +1,6 @@ -import type { Scope } from '@sentry/types'; +import type { PropagationContext, Scope, TraceContext } from '@sentry/types'; import type { Client } from '@sentry/types'; -import { getGlobalSingleton } from '@sentry/utils'; +import { dropUndefinedKeys, generateSentryTraceHeader, getGlobalSingleton } from '@sentry/utils'; import { getAsyncContextStrategy } from './asyncContext'; import { getMainCarrier } from './carrier'; import { Scope as ScopeClass } from './scope'; @@ -120,3 +120,49 @@ export function withIsolationScope( export function getClient(): C | undefined { return getCurrentScope().getClient(); } + +/** + * Get a trace context for the currently active scopes. + */ +export function getTraceContextFromScopes( + scope = getCurrentScope(), + isolationScope = getIsolationScope(), + globalScope = getGlobalScope(), +): TraceContext { + const propagationContext = mergePropagationContexts(scope, isolationScope, globalScope); + + const { traceId, spanId, parentSpanId } = propagationContext; + + const traceContext: TraceContext = dropUndefinedKeys({ + trace_id: traceId, + span_id: spanId, + parent_span_id: parentSpanId, + }); + + return traceContext; +} + +/** + * Get a sentry-trace header value for the currently active scopes. + */ +export function scopesToTraceHeader( + scope = getCurrentScope(), + isolationScope = getIsolationScope(), + globalScope = getGlobalScope(), +): string { + const { traceId, sampled, spanId } = mergePropagationContexts(scope, isolationScope, globalScope); + return generateSentryTraceHeader(traceId, spanId, sampled); +} + +/** Get a merged propagationContext for the current scopes. */ +export function mergePropagationContexts( + scope = getCurrentScope(), + isolationScope = getIsolationScope(), + globalScope = getGlobalScope(), +): PropagationContext { + return { + ...globalScope.getPropagationContext(), + ...isolationScope.getPropagationContext(), + ...scope.getPropagationContext(), + }; +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index ee2fe10d0d94..e170a6def835 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -38,15 +38,12 @@ export { withScope, withIsolationScope, getClient, + getTraceContextFromScopes, } from './currentScopes'; export { getDefaultCurrentScope, getDefaultIsolationScope, } from './defaultScopes'; -export { - getDynamicSamplingContextFromScopes, - getTraceContextFromScopes, -} from './propagationContext'; export { setAsyncContextStrategy } from './asyncContext'; export { getMainCarrier } from './carrier'; export { makeSession, closeSession, updateSession } from './session'; diff --git a/packages/core/src/propagationContext.ts b/packages/core/src/propagationContext.ts deleted file mode 100644 index b8c49bba1e95..000000000000 --- a/packages/core/src/propagationContext.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { Client, DynamicSamplingContext, PropagationContext, TraceContext } from '@sentry/types'; -import { dropUndefinedKeys, generateSentryTraceHeader } from '@sentry/utils'; -import { getCurrentScope, getGlobalScope, getIsolationScope } from './currentScopes'; -import { getDynamicSamplingContextFromClient } from './tracing'; - -/** - * Get a trace context for the currently active scopes. - */ -export function getTraceContextFromScopes( - scope = getCurrentScope(), - isolationScope = getIsolationScope(), - globalScope = getGlobalScope(), -): TraceContext { - const propagationContext = mergePropagationContexts(scope, isolationScope, globalScope); - - const { traceId, spanId, parentSpanId } = propagationContext; - - const traceContext: TraceContext = dropUndefinedKeys({ - trace_id: traceId, - span_id: spanId, - parent_span_id: parentSpanId, - }); - - return traceContext; -} - -/** - * Get a sentry-trace header value for the currently active scopes. - */ -export function scopesToTraceHeader( - scope = getCurrentScope(), - isolationScope = getIsolationScope(), - globalScope = getGlobalScope(), -): string { - const { traceId, sampled, spanId } = mergePropagationContexts(scope, isolationScope, globalScope); - return generateSentryTraceHeader(traceId, spanId, sampled); -} - -/** - * Get the dynamic sampling context for the currently active scopes. - */ -export function getDynamicSamplingContextFromScopes( - client: Client, - scope = getCurrentScope(), - isolationScope = getIsolationScope(), - globalScope = getGlobalScope(), -): Partial { - const propagationContext = mergePropagationContexts(scope, isolationScope, globalScope); - return propagationContext.dsc || getDynamicSamplingContextFromClient(propagationContext.traceId, client); -} - -function mergePropagationContexts( - scope = getCurrentScope(), - isolationScope = getIsolationScope(), - globalScope = getGlobalScope(), -): PropagationContext { - return { - ...globalScope.getPropagationContext(), - ...isolationScope.getPropagationContext(), - ...scope.getPropagationContext(), - }; -} diff --git a/packages/core/src/server-runtime-client.ts b/packages/core/src/server-runtime-client.ts index aeaea2961601..a04b9b57ad00 100644 --- a/packages/core/src/server-runtime-client.ts +++ b/packages/core/src/server-runtime-client.ts @@ -15,12 +15,15 @@ import { eventFromMessage, eventFromUnknownInput, logger, resolvedSyncPromise, u import { BaseClient } from './baseclient'; import { createCheckInEnvelope } from './checkin'; -import { getIsolationScope } from './currentScopes'; +import { getIsolationScope, getTraceContextFromScopes } from './currentScopes'; import { DEBUG_BUILD } from './debug-build'; -import { getDynamicSamplingContextFromScopes, getTraceContextFromScopes } from './propagationContext'; import type { Scope } from './scope'; import { SessionFlusher } from './sessionflusher'; -import { getDynamicSamplingContextFromSpan, registerSpanErrorInstrumentation } from './tracing'; +import { + getDynamicSamplingContextFromScopes, + getDynamicSamplingContextFromSpan, + registerSpanErrorInstrumentation, +} from './tracing'; import { _getSpanForScope } from './utils/spanOnScope'; import { spanToTraceContext } from './utils/spanUtils'; diff --git a/packages/core/src/tracing/dynamicSamplingContext.ts b/packages/core/src/tracing/dynamicSamplingContext.ts index 0e96788be8d9..b09421e73e65 100644 --- a/packages/core/src/tracing/dynamicSamplingContext.ts +++ b/packages/core/src/tracing/dynamicSamplingContext.ts @@ -7,7 +7,13 @@ import { } from '@sentry/utils'; import { DEFAULT_ENVIRONMENT } from '../constants'; -import { getClient } from '../currentScopes'; +import { + getClient, + getCurrentScope, + getGlobalScope, + getIsolationScope, + mergePropagationContexts, +} from '../currentScopes'; import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '../semanticAttributes'; import { hasTracingEnabled } from '../utils/hasTracingEnabled'; import { getRootSpan, spanIsSampled, spanToJSON } from '../utils/spanUtils'; @@ -52,6 +58,19 @@ export function getDynamicSamplingContextFromClient(trace_id: string, client: Cl return dsc; } +/** + * Get the dynamic sampling context for the currently active scopes. + */ +export function getDynamicSamplingContextFromScopes( + client: Client, + scope = getCurrentScope(), + isolationScope = getIsolationScope(), + globalScope = getGlobalScope(), +): Partial { + const propagationContext = mergePropagationContexts(scope, isolationScope, globalScope); + return propagationContext.dsc || getDynamicSamplingContextFromClient(propagationContext.traceId, client); +} + /** * Creates a dynamic sampling context from a span (and client and scope) * diff --git a/packages/core/src/tracing/index.ts b/packages/core/src/tracing/index.ts index b791c36cc050..4b9db008e5bf 100644 --- a/packages/core/src/tracing/index.ts +++ b/packages/core/src/tracing/index.ts @@ -22,6 +22,7 @@ export { export { getDynamicSamplingContextFromClient, getDynamicSamplingContextFromSpan, + getDynamicSamplingContextFromScopes, spanToBaggageHeader, } from './dynamicSamplingContext'; export { setMeasurement, timedEventsToMeasurements } from './measurement'; diff --git a/packages/core/src/tracing/sentryHeaders.ts b/packages/core/src/tracing/sentryHeaders.ts index f36c0165ca65..2fbeb973c35b 100644 --- a/packages/core/src/tracing/sentryHeaders.ts +++ b/packages/core/src/tracing/sentryHeaders.ts @@ -1,8 +1,8 @@ import type { Client, Scope, Span } from '@sentry/types'; import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils'; -import { getDynamicSamplingContextFromScopes, scopesToTraceHeader } from '../propagationContext'; +import { scopesToTraceHeader } from '../currentScopes'; import { spanToTraceHeader } from '../utils/spanUtils'; -import { getDynamicSamplingContextFromSpan } from './dynamicSamplingContext'; +import { getDynamicSamplingContextFromScopes, getDynamicSamplingContextFromSpan } from './dynamicSamplingContext'; /** * Get the sentry-trace and baggage headers for a given span or scope. From 07c8fcf07480dee6c290bab4e2282ae6354cddf6 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 19 Nov 2024 14:02:19 +0100 Subject: [PATCH 03/30] streamline propagator --- packages/opentelemetry/src/propagator.ts | 37 ++++-------------------- 1 file changed, 6 insertions(+), 31 deletions(-) diff --git a/packages/opentelemetry/src/propagator.ts b/packages/opentelemetry/src/propagator.ts index f4fcb1fa91e9..745015c365a2 100644 --- a/packages/opentelemetry/src/propagator.ts +++ b/packages/opentelemetry/src/propagator.ts @@ -4,16 +4,11 @@ import { context } from '@opentelemetry/api'; import { propagation, trace } from '@opentelemetry/api'; import { W3CBaggagePropagator, isTracingSuppressed } from '@opentelemetry/core'; import { ATTR_URL_FULL, SEMATTRS_HTTP_URL } from '@opentelemetry/semantic-conventions'; -import type { continueTrace } from '@sentry/core'; +import type { continueTrace} from '@sentry/core'; +import { getDynamicSamplingContextFromScopes } from '@sentry/core'; import { getRootSpan } from '@sentry/core'; import { spanToJSON } from '@sentry/core'; -import { - getClient, - getCurrentScope, - getDynamicSamplingContextFromClient, - getDynamicSamplingContextFromSpan, - getIsolationScope, -} from '@sentry/core'; +import { getClient, getCurrentScope, getDynamicSamplingContextFromSpan, getIsolationScope } from '@sentry/core'; import type { DynamicSamplingContext, Options, PropagationContext } from '@sentry/types'; import { LRUMap, @@ -204,8 +199,7 @@ function getInjectionData(context: Context): { if (span && !spanIsRemote) { const spanContext = span.spanContext(); - const propagationContext = getPropagationContextFromSpan(span); - const dynamicSamplingContext = getDynamicSamplingContext(propagationContext, spanContext.traceId); + const dynamicSamplingContext = getDynamicSamplingContextFromSpan(span); return { dynamicSamplingContext, traceId: spanContext.traceId, @@ -216,9 +210,10 @@ function getInjectionData(context: Context): { // Else we try to use the propagation context from the scope const scope = getScopesFromContext(context)?.scope || getCurrentScope(); + const client = getClient(); const propagationContext = scope.getPropagationContext(); - const dynamicSamplingContext = getDynamicSamplingContext(propagationContext, propagationContext.traceId); + const dynamicSamplingContext = client ? getDynamicSamplingContextFromScopes(client, scope) : undefined; return { dynamicSamplingContext, traceId: propagationContext.traceId, @@ -227,26 +222,6 @@ function getInjectionData(context: Context): { }; } -/** Get the DSC from a context, or fall back to use the one from the client. */ -function getDynamicSamplingContext( - propagationContext: PropagationContext, - traceId: string | undefined, -): Partial | undefined { - // If we have a DSC on the propagation context, we just use it - if (propagationContext?.dsc) { - return propagationContext.dsc; - } - - // Else, we try to generate a new one - const client = getClient(); - - if (client) { - return getDynamicSamplingContextFromClient(traceId || propagationContext.traceId, client); - } - - return undefined; -} - function getContextWithRemoteActiveSpan( ctx: Context, { sentryTrace, baggage }: Parameters[0], From 81d87fff296401782462d269d2dfffc6130c32ab Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 19 Nov 2024 14:09:57 +0100 Subject: [PATCH 04/30] ref: Rename & streamline --- packages/core/src/baseclient.ts | 10 +++--- packages/core/src/currentScopes.ts | 31 +++---------------- packages/core/src/index.ts | 2 +- packages/core/src/server-runtime-client.ts | 8 ++--- .../src/tracing/dynamicSamplingContext.ts | 19 +++--------- packages/core/src/tracing/index.ts | 2 +- packages/core/src/tracing/sentryHeaders.ts | 12 ++++--- packages/opentelemetry/src/propagator.ts | 6 ++-- packages/opentelemetry/src/trace.ts | 8 ++--- 9 files changed, 35 insertions(+), 63 deletions(-) diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index ed87a04d15f6..7302bde367f9 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -50,7 +50,7 @@ import { } from '@sentry/utils'; import { getEnvelopeEndpointWithUrlEncodedAuth } from './api'; -import { getIsolationScope, getTraceContextFromScopes } from './currentScopes'; +import { getCurrentScope, getIsolationScope, getTraceContextFromScope } from './currentScopes'; import { DEBUG_BUILD } from './debug-build'; import { createEventEnvelope, createSessionEnvelope } from './envelope'; import type { IntegrationIndex } from './integration'; @@ -58,7 +58,7 @@ import { afterSetupIntegrations } from './integration'; import { setupIntegration, setupIntegrations } from './integration'; import type { Scope } from './scope'; import { updateSession } from './session'; -import { getDynamicSamplingContextFromScopes } from './tracing/dynamicSamplingContext'; +import { getDynamicSamplingContextFromScope } from './tracing/dynamicSamplingContext'; import { parseSampleRate } from './utils/parseSampleRate'; import { prepareEvent } from './utils/prepareEvent'; @@ -668,7 +668,7 @@ export abstract class BaseClient implements Client { protected _prepareEvent( event: Event, hint: EventHint, - currentScope?: Scope, + currentScope = getCurrentScope(), isolationScope = getIsolationScope(), ): PromiseLike { const options = this.getOptions(); @@ -689,11 +689,11 @@ export abstract class BaseClient implements Client { } evt.contexts = { - trace: getTraceContextFromScopes(currentScope, isolationScope), + trace: getTraceContextFromScope(currentScope), ...evt.contexts, }; - const dynamicSamplingContext = getDynamicSamplingContextFromScopes(this, currentScope, isolationScope); + const dynamicSamplingContext = getDynamicSamplingContextFromScope(this, currentScope); evt.sdkProcessingMetadata = { dynamicSamplingContext, diff --git a/packages/core/src/currentScopes.ts b/packages/core/src/currentScopes.ts index 255e40d68875..9de867708594 100644 --- a/packages/core/src/currentScopes.ts +++ b/packages/core/src/currentScopes.ts @@ -1,4 +1,4 @@ -import type { PropagationContext, Scope, TraceContext } from '@sentry/types'; +import type { Scope, TraceContext } from '@sentry/types'; import type { Client } from '@sentry/types'; import { dropUndefinedKeys, generateSentryTraceHeader, getGlobalSingleton } from '@sentry/utils'; import { getAsyncContextStrategy } from './asyncContext'; @@ -124,12 +124,8 @@ export function getClient(): C | undefined { /** * Get a trace context for the currently active scopes. */ -export function getTraceContextFromScopes( - scope = getCurrentScope(), - isolationScope = getIsolationScope(), - globalScope = getGlobalScope(), -): TraceContext { - const propagationContext = mergePropagationContexts(scope, isolationScope, globalScope); +export function getTraceContextFromScope(scope: Scope): TraceContext { + const propagationContext = scope.getPropagationContext(); const { traceId, spanId, parentSpanId } = propagationContext; @@ -145,24 +141,7 @@ export function getTraceContextFromScopes( /** * Get a sentry-trace header value for the currently active scopes. */ -export function scopesToTraceHeader( - scope = getCurrentScope(), - isolationScope = getIsolationScope(), - globalScope = getGlobalScope(), -): string { - const { traceId, sampled, spanId } = mergePropagationContexts(scope, isolationScope, globalScope); +export function scopesToTraceHeader(scope: Scope): string { + const { traceId, sampled, spanId } = scope.getPropagationContext(); return generateSentryTraceHeader(traceId, spanId, sampled); } - -/** Get a merged propagationContext for the current scopes. */ -export function mergePropagationContexts( - scope = getCurrentScope(), - isolationScope = getIsolationScope(), - globalScope = getGlobalScope(), -): PropagationContext { - return { - ...globalScope.getPropagationContext(), - ...isolationScope.getPropagationContext(), - ...scope.getPropagationContext(), - }; -} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index e170a6def835..9fdc50ec1593 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -38,7 +38,7 @@ export { withScope, withIsolationScope, getClient, - getTraceContextFromScopes, + getTraceContextFromScope, } from './currentScopes'; export { getDefaultCurrentScope, diff --git a/packages/core/src/server-runtime-client.ts b/packages/core/src/server-runtime-client.ts index a04b9b57ad00..b99c3357e805 100644 --- a/packages/core/src/server-runtime-client.ts +++ b/packages/core/src/server-runtime-client.ts @@ -15,12 +15,12 @@ import { eventFromMessage, eventFromUnknownInput, logger, resolvedSyncPromise, u import { BaseClient } from './baseclient'; import { createCheckInEnvelope } from './checkin'; -import { getIsolationScope, getTraceContextFromScopes } from './currentScopes'; +import { getIsolationScope, getTraceContextFromScope } from './currentScopes'; import { DEBUG_BUILD } from './debug-build'; import type { Scope } from './scope'; import { SessionFlusher } from './sessionflusher'; import { - getDynamicSamplingContextFromScopes, + getDynamicSamplingContextFromScope, getDynamicSamplingContextFromSpan, registerSpanErrorInstrumentation, } from './tracing'; @@ -257,10 +257,10 @@ export class ServerRuntimeClient< const span = _getSpanForScope(scope); - const traceContext = span ? spanToTraceContext(span) : getTraceContextFromScopes(scope); + const traceContext = span ? spanToTraceContext(span) : getTraceContextFromScope(scope); const dynamicSamplingContext = span ? getDynamicSamplingContextFromSpan(span) - : getDynamicSamplingContextFromScopes(this, scope); + : getDynamicSamplingContextFromScope(this, scope); return [dynamicSamplingContext, traceContext]; } } diff --git a/packages/core/src/tracing/dynamicSamplingContext.ts b/packages/core/src/tracing/dynamicSamplingContext.ts index b09421e73e65..57721708a3c2 100644 --- a/packages/core/src/tracing/dynamicSamplingContext.ts +++ b/packages/core/src/tracing/dynamicSamplingContext.ts @@ -1,4 +1,4 @@ -import type { Client, DynamicSamplingContext, Span } from '@sentry/types'; +import type { Client, DynamicSamplingContext, Scope, Span } from '@sentry/types'; import { addNonEnumerableProperty, baggageHeaderToDynamicSamplingContext, @@ -7,13 +7,7 @@ import { } from '@sentry/utils'; import { DEFAULT_ENVIRONMENT } from '../constants'; -import { - getClient, - getCurrentScope, - getGlobalScope, - getIsolationScope, - mergePropagationContexts, -} from '../currentScopes'; +import { getClient } from '../currentScopes'; import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '../semanticAttributes'; import { hasTracingEnabled } from '../utils/hasTracingEnabled'; import { getRootSpan, spanIsSampled, spanToJSON } from '../utils/spanUtils'; @@ -61,13 +55,8 @@ export function getDynamicSamplingContextFromClient(trace_id: string, client: Cl /** * Get the dynamic sampling context for the currently active scopes. */ -export function getDynamicSamplingContextFromScopes( - client: Client, - scope = getCurrentScope(), - isolationScope = getIsolationScope(), - globalScope = getGlobalScope(), -): Partial { - const propagationContext = mergePropagationContexts(scope, isolationScope, globalScope); +export function getDynamicSamplingContextFromScope(client: Client, scope: Scope): Partial { + const propagationContext = scope.getPropagationContext(); return propagationContext.dsc || getDynamicSamplingContextFromClient(propagationContext.traceId, client); } diff --git a/packages/core/src/tracing/index.ts b/packages/core/src/tracing/index.ts index 4b9db008e5bf..12b71caff897 100644 --- a/packages/core/src/tracing/index.ts +++ b/packages/core/src/tracing/index.ts @@ -22,7 +22,7 @@ export { export { getDynamicSamplingContextFromClient, getDynamicSamplingContextFromSpan, - getDynamicSamplingContextFromScopes, + getDynamicSamplingContextFromScope, spanToBaggageHeader, } from './dynamicSamplingContext'; export { setMeasurement, timedEventsToMeasurements } from './measurement'; diff --git a/packages/core/src/tracing/sentryHeaders.ts b/packages/core/src/tracing/sentryHeaders.ts index 2fbeb973c35b..f8b184b14884 100644 --- a/packages/core/src/tracing/sentryHeaders.ts +++ b/packages/core/src/tracing/sentryHeaders.ts @@ -1,19 +1,23 @@ import type { Client, Scope, Span } from '@sentry/types'; import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils'; -import { scopesToTraceHeader } from '../currentScopes'; +import { getCurrentScope, scopesToTraceHeader } from '../currentScopes'; import { spanToTraceHeader } from '../utils/spanUtils'; -import { getDynamicSamplingContextFromScopes, getDynamicSamplingContextFromSpan } from './dynamicSamplingContext'; +import { getDynamicSamplingContextFromScope, getDynamicSamplingContextFromSpan } from './dynamicSamplingContext'; /** * Get the sentry-trace and baggage headers for a given span or scope. * If no scope is defined, it will use the current scope. */ -export function getSentryHeaders({ span, client, scope }: { span?: Span; client: Client; scope?: Scope }): { +export function getSentryHeaders({ + span, + client, + scope = getCurrentScope(), +}: { span?: Span; client: Client; scope?: Scope }): { sentryTrace: string; baggage: string | undefined; } { const sentryTrace = span ? spanToTraceHeader(span) : scopesToTraceHeader(scope); - const dsc = span ? getDynamicSamplingContextFromSpan(span) : getDynamicSamplingContextFromScopes(client, scope); + const dsc = span ? getDynamicSamplingContextFromSpan(span) : getDynamicSamplingContextFromScope(client, scope); const baggage = dynamicSamplingContextToSentryBaggageHeader(dsc); return { diff --git a/packages/opentelemetry/src/propagator.ts b/packages/opentelemetry/src/propagator.ts index 745015c365a2..731c1e9cb0a7 100644 --- a/packages/opentelemetry/src/propagator.ts +++ b/packages/opentelemetry/src/propagator.ts @@ -4,8 +4,8 @@ import { context } from '@opentelemetry/api'; import { propagation, trace } from '@opentelemetry/api'; import { W3CBaggagePropagator, isTracingSuppressed } from '@opentelemetry/core'; import { ATTR_URL_FULL, SEMATTRS_HTTP_URL } from '@opentelemetry/semantic-conventions'; -import type { continueTrace} from '@sentry/core'; -import { getDynamicSamplingContextFromScopes } from '@sentry/core'; +import type { continueTrace } from '@sentry/core'; +import { getDynamicSamplingContextFromScope } from '@sentry/core'; import { getRootSpan } from '@sentry/core'; import { spanToJSON } from '@sentry/core'; import { getClient, getCurrentScope, getDynamicSamplingContextFromSpan, getIsolationScope } from '@sentry/core'; @@ -213,7 +213,7 @@ function getInjectionData(context: Context): { const client = getClient(); const propagationContext = scope.getPropagationContext(); - const dynamicSamplingContext = client ? getDynamicSamplingContextFromScopes(client, scope) : undefined; + const dynamicSamplingContext = client ? getDynamicSamplingContextFromScope(client, scope) : undefined; return { dynamicSamplingContext, traceId: propagationContext.traceId, diff --git a/packages/opentelemetry/src/trace.ts b/packages/opentelemetry/src/trace.ts index f594ee84755f..2baeff9b01c6 100644 --- a/packages/opentelemetry/src/trace.ts +++ b/packages/opentelemetry/src/trace.ts @@ -7,10 +7,10 @@ import { continueTrace as baseContinueTrace, getClient, getCurrentScope, - getDynamicSamplingContextFromScopes, + getDynamicSamplingContextFromScope, getDynamicSamplingContextFromSpan, getRootSpan, - getTraceContextFromScopes, + getTraceContextFromScope, handleCallbackErrors, spanToJSON, spanToTraceContext, @@ -292,11 +292,11 @@ export function getTraceContextForScope( const ctx = getContextFromScope(scope); const span = ctx && trace.getSpan(ctx); - const traceContext = span ? spanToTraceContext(span) : getTraceContextFromScopes(scope); + const traceContext = span ? spanToTraceContext(span) : getTraceContextFromScope(scope); const dynamicSamplingContext = span ? getDynamicSamplingContextFromSpan(span) - : getDynamicSamplingContextFromScopes(client, scope); + : getDynamicSamplingContextFromScope(client, scope); return [dynamicSamplingContext, traceContext]; } From 8bd3ef850726569e136a2dbd3dfb00e09c5173a1 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 19 Nov 2024 14:14:59 +0100 Subject: [PATCH 05/30] better comments & small ref --- packages/core/src/currentScopes.ts | 12 ++---------- packages/core/src/tracing/sentryHeaders.ts | 12 ++++++++++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/core/src/currentScopes.ts b/packages/core/src/currentScopes.ts index 9de867708594..72cdf5c81179 100644 --- a/packages/core/src/currentScopes.ts +++ b/packages/core/src/currentScopes.ts @@ -1,6 +1,6 @@ import type { Scope, TraceContext } from '@sentry/types'; import type { Client } from '@sentry/types'; -import { dropUndefinedKeys, generateSentryTraceHeader, getGlobalSingleton } from '@sentry/utils'; +import { dropUndefinedKeys, getGlobalSingleton } from '@sentry/utils'; import { getAsyncContextStrategy } from './asyncContext'; import { getMainCarrier } from './carrier'; import { Scope as ScopeClass } from './scope'; @@ -122,7 +122,7 @@ export function getClient(): C | undefined { } /** - * Get a trace context for the currently active scopes. + * Get a trace context for the given scope. */ export function getTraceContextFromScope(scope: Scope): TraceContext { const propagationContext = scope.getPropagationContext(); @@ -137,11 +137,3 @@ export function getTraceContextFromScope(scope: Scope): TraceContext { return traceContext; } - -/** - * Get a sentry-trace header value for the currently active scopes. - */ -export function scopesToTraceHeader(scope: Scope): string { - const { traceId, sampled, spanId } = scope.getPropagationContext(); - return generateSentryTraceHeader(traceId, spanId, sampled); -} diff --git a/packages/core/src/tracing/sentryHeaders.ts b/packages/core/src/tracing/sentryHeaders.ts index f8b184b14884..bf856802359a 100644 --- a/packages/core/src/tracing/sentryHeaders.ts +++ b/packages/core/src/tracing/sentryHeaders.ts @@ -1,6 +1,6 @@ import type { Client, Scope, Span } from '@sentry/types'; -import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils'; -import { getCurrentScope, scopesToTraceHeader } from '../currentScopes'; +import { dynamicSamplingContextToSentryBaggageHeader, generateSentryTraceHeader } from '@sentry/utils'; +import { getCurrentScope } from '../currentScopes'; import { spanToTraceHeader } from '../utils/spanUtils'; import { getDynamicSamplingContextFromScope, getDynamicSamplingContextFromSpan } from './dynamicSamplingContext'; @@ -25,3 +25,11 @@ export function getSentryHeaders({ baggage, }; } + +/** + * Get a sentry-trace header value for the given scope. + */ +function scopesToTraceHeader(scope: Scope): string { + const { traceId, sampled, spanId } = scope.getPropagationContext(); + return generateSentryTraceHeader(traceId, spanId, sampled); +} From 5201fc88862d0a00e6ff07832bc884bc921bf86e Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 19 Nov 2024 15:20:43 +0100 Subject: [PATCH 06/30] fix tests --- .../test/lib/tracing/sentryHeaders.test.ts | 194 ++++++++++++++++++ .../core/test/lib/utils/traceData.test.ts | 56 ++--- 2 files changed, 224 insertions(+), 26 deletions(-) create mode 100644 packages/core/test/lib/tracing/sentryHeaders.test.ts diff --git a/packages/core/test/lib/tracing/sentryHeaders.test.ts b/packages/core/test/lib/tracing/sentryHeaders.test.ts new file mode 100644 index 000000000000..de8fe0a00eda --- /dev/null +++ b/packages/core/test/lib/tracing/sentryHeaders.test.ts @@ -0,0 +1,194 @@ +import type { Client } from '@sentry/types'; +import { Scope, SentrySpan, getCurrentScope, getGlobalScope, getIsolationScope, setCurrentClient } from '../../../src'; +import { freezeDscOnSpan } from '../../../src/tracing/dynamicSamplingContext'; +import { getSentryHeaders } from '../../../src/tracing/sentryHeaders'; +import type { TestClientOptions } from '../../mocks/client'; +import { TestClient, getDefaultTestClientOptions } from '../../mocks/client'; + +const dsn = 'https://123@sentry.io/42'; + +const SCOPE_TRACE_ID = '12345678901234567890123456789012'; +const SCOPE_SPAN_ID = '1234567890123456'; + +function setupClient(opts?: TestClientOptions): Client { + getCurrentScope().clear(); + getIsolationScope().clear(); + getGlobalScope().clear(); + + getCurrentScope().setPropagationContext({ + traceId: SCOPE_TRACE_ID, + spanId: SCOPE_SPAN_ID, + }); + + const options = getDefaultTestClientOptions({ + dsn, + ...opts, + }); + const client = new TestClient(options); + setCurrentClient(client); + client.init(); + + return client; +} + +describe('getSentryHeaders', () => { + beforeEach(() => {}); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('works with a minimal client', () => { + const client = setupClient(); + + const { sentryTrace, baggage } = getSentryHeaders({ client }); + + expect(sentryTrace).toEqual(`${SCOPE_TRACE_ID}-${SCOPE_SPAN_ID}`); + expect(baggage).toEqual(`sentry-environment=production,sentry-public_key=123,sentry-trace_id=${SCOPE_TRACE_ID}`); + }); + + it('allows to pass a specific scope', () => { + const client = setupClient(); + + const traceId = '12345678901234567890123456789099'; + const spanId = '1234567890123499'; + const scope = new Scope(); + scope.setPropagationContext({ + traceId, + spanId, + }); + + const { sentryTrace, baggage } = getSentryHeaders({ client, scope }); + + expect(sentryTrace).toEqual(`${traceId}-${spanId}`); + expect(baggage).toEqual(`sentry-environment=production,sentry-public_key=123,sentry-trace_id=${traceId}`); + }); + + it('uses DSC from scope, if available', () => { + const client = setupClient(); + + const traceId = '12345678901234567890123456789099'; + const spanId = '1234567890123499'; + const scope = new Scope(); + scope.setPropagationContext({ + traceId, + spanId, + dsc: { + environment: 'test-dev', + public_key: '456', + trace_id: '12345678901234567890123456789088', + }, + }); + + const { sentryTrace, baggage } = getSentryHeaders({ client, scope }); + + expect(sentryTrace).toEqual(`${traceId}-${spanId}`); + expect(baggage).toEqual( + 'sentry-environment=test-dev,sentry-public_key=456,sentry-trace_id=12345678901234567890123456789088', + ); + }); + + it('works with a minimal unsampled span', () => { + const client = setupClient(); + + const traceId = '12345678901234567890123456789099'; + const spanId = '1234567890123499'; + + const span = new SentrySpan({ + traceId, + spanId, + sampled: false, + }); + + const { sentryTrace, baggage } = getSentryHeaders({ client, span }); + + expect(sentryTrace).toEqual(`${traceId}-${spanId}-0`); + expect(baggage).toEqual(`sentry-environment=production,sentry-public_key=123,sentry-trace_id=${traceId}`); + }); + + it('works with a minimal sampled span', () => { + const client = setupClient(); + + const traceId = '12345678901234567890123456789099'; + const spanId = '1234567890123499'; + + const span = new SentrySpan({ + traceId, + spanId, + sampled: true, + }); + + const { sentryTrace, baggage } = getSentryHeaders({ client, span }); + + expect(sentryTrace).toEqual(`${traceId}-${spanId}-1`); + expect(baggage).toEqual(`sentry-environment=production,sentry-public_key=123,sentry-trace_id=${traceId}`); + }); + + it('works with a SentrySpan with frozen DSC', () => { + const client = setupClient(); + + const traceId = '12345678901234567890123456789099'; + const spanId = '1234567890123499'; + + const span = new SentrySpan({ + traceId, + spanId, + sampled: true, + }); + + freezeDscOnSpan(span, { + environment: 'test-dev', + public_key: '456', + trace_id: '12345678901234567890123456789088', + }); + + const { sentryTrace, baggage } = getSentryHeaders({ client, span }); + + expect(sentryTrace).toEqual(`${traceId}-${spanId}-1`); + expect(baggage).toEqual( + 'sentry-environment=test-dev,sentry-public_key=456,sentry-trace_id=12345678901234567890123456789088', + ); + }); + + it('works with an OTEL span with frozen DSC in traceState', () => { + const client = setupClient(); + + const traceId = '12345678901234567890123456789099'; + const spanId = '1234567890123499'; + + const span = new SentrySpan({ + traceId, + spanId, + sampled: true, + }); + + span.spanContext = () => { + const traceState = { + set: () => traceState, + unset: () => traceState, + get: (key: string) => { + if (key === 'sentry.dsc') { + return 'sentry-environment=test-dev,sentry-public_key=456,sentry-trace_id=12345678901234567890123456789088'; + } + return undefined; + }, + serialize: () => '', + }; + + return { + traceId, + spanId, + sampled: true, + traceFlags: 1, + traceState, + }; + }; + + const { sentryTrace, baggage } = getSentryHeaders({ client, span }); + + expect(sentryTrace).toEqual(`${traceId}-${spanId}-1`); + expect(baggage).toEqual( + 'sentry-environment=test-dev,sentry-public_key=456,sentry-trace_id=12345678901234567890123456789088', + ); + }); +}); diff --git a/packages/core/test/lib/utils/traceData.test.ts b/packages/core/test/lib/utils/traceData.test.ts index aa6d2497dd54..d35425ea28f6 100644 --- a/packages/core/test/lib/utils/traceData.test.ts +++ b/packages/core/test/lib/utils/traceData.test.ts @@ -2,23 +2,20 @@ import { SentrySpan, getTraceData } from '../../../src/'; import * as SentryCoreCurrentScopes from '../../../src/currentScopes'; import * as SentryCoreExports from '../../../src/exports'; import * as SentryCoreTracing from '../../../src/tracing'; +import * as SentryCoreTracingDsc from '../../../src/tracing/dynamicSamplingContext'; +import { freezeDscOnSpan } from '../../../src/tracing/dynamicSamplingContext'; import * as SentryCoreSpanUtils from '../../../src/utils/spanUtils'; import { isValidBaggageString } from '../../../src/utils/traceData'; const TRACE_FLAG_SAMPLED = 1; -const mockedSpan = new SentrySpan({ - traceId: '12345678901234567890123456789012', - spanId: '1234567890123456', - sampled: true, -}); - const mockedClient = {} as any; const mockedScope = { getPropagationContext: () => ({ traceId: '123', + spanId: '456', }), } as any; @@ -33,11 +30,16 @@ describe('getTraceData', () => { it('returns the tracing data from the span, if a span is available', () => { { - jest.spyOn(SentryCoreTracing, 'getDynamicSamplingContextFromSpan').mockReturnValueOnce({ - environment: 'production', + const mockedSpan = new SentrySpan({ + traceId: '12345678901234567890123456789012', + spanId: '1234567890123456', + sampled: true, }); - jest.spyOn(SentryCoreSpanUtils, 'getActiveSpan').mockImplementationOnce(() => mockedSpan); - jest.spyOn(SentryCoreCurrentScopes, 'getCurrentScope').mockImplementationOnce(() => mockedScope); + freezeDscOnSpan(mockedSpan, { environment: 'production' }); + + jest.spyOn(SentryCoreSpanUtils, 'getActiveSpan').mockImplementation(() => mockedSpan); + jest.spyOn(SentryCoreCurrentScopes, 'getCurrentScope').mockImplementation(() => mockedScope); + jest.spyOn(SentryCoreCurrentScopes, 'getClient').mockImplementation(() => mockedClient); const data = getTraceData(); @@ -49,8 +51,8 @@ describe('getTraceData', () => { }); it('returns propagationContext DSC data if no span is available', () => { - jest.spyOn(SentryCoreSpanUtils, 'getActiveSpan').mockImplementationOnce(() => undefined); - jest.spyOn(SentryCoreCurrentScopes, 'getCurrentScope').mockImplementationOnce( + jest.spyOn(SentryCoreSpanUtils, 'getActiveSpan').mockImplementation(() => undefined); + jest.spyOn(SentryCoreCurrentScopes, 'getCurrentScope').mockImplementation( () => ({ getPropagationContext: () => ({ @@ -65,7 +67,7 @@ describe('getTraceData', () => { }), }) as any, ); - jest.spyOn(SentryCoreCurrentScopes, 'getClient').mockImplementationOnce(() => mockedClient); + jest.spyOn(SentryCoreCurrentScopes, 'getClient').mockImplementation(() => mockedClient); const traceData = getTraceData(); @@ -76,13 +78,13 @@ describe('getTraceData', () => { }); it('returns only the `sentry-trace` value if no DSC is available', () => { - jest.spyOn(SentryCoreTracing, 'getDynamicSamplingContextFromClient').mockReturnValueOnce({ + jest.spyOn(SentryCoreTracingDsc, 'getDynamicSamplingContextFromSpan').mockReturnValue({ trace_id: '', public_key: undefined, }); // @ts-expect-error - we don't need to provide all the properties - jest.spyOn(SentryCoreSpanUtils, 'getActiveSpan').mockImplementationOnce(() => ({ + jest.spyOn(SentryCoreSpanUtils, 'getActiveSpan').mockImplementation(() => ({ isRecording: () => true, spanContext: () => { return { @@ -93,8 +95,13 @@ describe('getTraceData', () => { }, })); - jest.spyOn(SentryCoreCurrentScopes, 'getCurrentScope').mockImplementationOnce(() => mockedScope); - jest.spyOn(SentryCoreCurrentScopes, 'getClient').mockImplementationOnce(() => mockedClient); + jest.spyOn(SentryCoreCurrentScopes, 'getCurrentScope').mockImplementation(() => mockedScope); + jest.spyOn(SentryCoreCurrentScopes, 'getClient').mockImplementation(() => { + return { + getOptions: () => ({}), + getDsn: () => ({}), + } as any; + }); const traceData = getTraceData(); @@ -103,14 +110,14 @@ describe('getTraceData', () => { }); }); - it('returns only the `sentry-trace` tag if no DSC is available without a client', () => { + it('returns empty object without a client', () => { jest.spyOn(SentryCoreTracing, 'getDynamicSamplingContextFromClient').mockReturnValueOnce({ trace_id: '', public_key: undefined, }); // @ts-expect-error - we don't need to provide all the properties - jest.spyOn(SentryCoreSpanUtils, 'getActiveSpan').mockImplementationOnce(() => ({ + jest.spyOn(SentryCoreSpanUtils, 'getActiveSpan').mockImplementation(() => ({ isRecording: () => true, spanContext: () => { return { @@ -120,20 +127,17 @@ describe('getTraceData', () => { }; }, })); - jest.spyOn(SentryCoreCurrentScopes, 'getCurrentScope').mockImplementationOnce(() => mockedScope); - jest.spyOn(SentryCoreCurrentScopes, 'getClient').mockImplementationOnce(() => undefined); + jest.spyOn(SentryCoreCurrentScopes, 'getCurrentScope').mockImplementation(() => mockedScope); + jest.spyOn(SentryCoreCurrentScopes, 'getClient').mockImplementation(() => undefined); const traceData = getTraceData(); - expect(traceData).toEqual({ - 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1', - }); - expect('baggage' in traceData).toBe(false); + expect(traceData).toEqual({}); }); it('returns an empty object if the `sentry-trace` value is invalid', () => { // @ts-expect-error - we don't need to provide all the properties - jest.spyOn(SentryCoreSpanUtils, 'getActiveSpan').mockImplementationOnce(() => ({ + jest.spyOn(SentryCoreSpanUtils, 'getActiveSpan').mockImplementation(() => ({ isRecording: () => true, spanContext: () => { return { From 1608c4c040c659e7e47450458159f318e46a7ee0 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 20 Nov 2024 11:18:02 +0100 Subject: [PATCH 07/30] ref: Use `getTraceData` instead --- packages/browser/src/tracing/request.ts | 16 +- packages/core/src/fetch.ts | 40 ++- packages/core/src/index.ts | 6 +- packages/core/src/tracing/index.ts | 1 - packages/core/src/tracing/sentryHeaders.ts | 35 -- packages/core/src/utils/traceData.ts | 38 +- .../test/lib/tracing/sentryHeaders.test.ts | 194 ----------- .../core/test/lib/utils/traceData.test.ts | 326 ++++++++++++------ .../opentelemetry/src/utils/getTraceData.ts | 16 +- .../test/utils/getTraceData.test.ts | 93 +++++ packages/remix/src/utils/instrumentServer.ts | 25 +- 11 files changed, 400 insertions(+), 390 deletions(-) delete mode 100644 packages/core/src/tracing/sentryHeaders.ts delete mode 100644 packages/core/test/lib/tracing/sentryHeaders.test.ts create mode 100644 packages/opentelemetry/test/utils/getTraceData.test.ts diff --git a/packages/browser/src/tracing/request.ts b/packages/browser/src/tracing/request.ts index d7cafa6b1cd3..155708dfb242 100644 --- a/packages/browser/src/tracing/request.ts +++ b/packages/browser/src/tracing/request.ts @@ -9,8 +9,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SentryNonRecordingSpan, getActiveSpan, - getClient, - getSentryHeaders, + getTraceData, hasTracingEnabled, instrumentFetchRequest, setHttpStatus, @@ -396,12 +395,9 @@ export function xhrCallback( xhr.__sentry_xhr_span_id__ = span.spanContext().spanId; spans[xhr.__sentry_xhr_span_id__] = span; - const client = getClient(); - - if (xhr.setRequestHeader && shouldAttachHeaders(sentryXhrData.url) && client) { + if (xhr.setRequestHeader && shouldAttachHeaders(sentryXhrData.url)) { addTracingHeadersToXhrRequest( xhr, - client, // If performance is disabled (TWP) or there's no active root span (pageload/navigation/interaction), // we do not want to use the span as base for the trace headers, // which means that the headers will be generated from the scope and the sampling decision is deferred @@ -412,10 +408,12 @@ export function xhrCallback( return span; } -function addTracingHeadersToXhrRequest(xhr: SentryWrappedXMLHttpRequest, client: Client, span?: Span): void { - const { sentryTrace, baggage } = getSentryHeaders({ span, client }); +function addTracingHeadersToXhrRequest(xhr: SentryWrappedXMLHttpRequest, span?: Span): void { + const { 'sentry-trace': sentryTrace, baggage } = getTraceData({ span }); - setHeaderOnXhr(xhr, sentryTrace, baggage); + if (sentryTrace) { + setHeaderOnXhr(xhr, sentryTrace, baggage); + } } function setHeaderOnXhr( diff --git a/packages/core/src/fetch.ts b/packages/core/src/fetch.ts index 7d2d34bca7cb..7050937b8ab2 100644 --- a/packages/core/src/fetch.ts +++ b/packages/core/src/fetch.ts @@ -1,11 +1,11 @@ import type { Client, HandlerDataFetch, Scope, Span, SpanOrigin } from '@sentry/types'; import { BAGGAGE_HEADER_NAME, SENTRY_BAGGAGE_KEY_PREFIX, isInstanceOf, parseUrl } from '@sentry/utils'; -import { getClient, getCurrentScope } from './currentScopes'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from './semanticAttributes'; -import { SPAN_STATUS_ERROR, getSentryHeaders, setHttpStatus, startInactiveSpan } from './tracing'; +import { SPAN_STATUS_ERROR, setHttpStatus, startInactiveSpan } from './tracing'; import { SentryNonRecordingSpan } from './tracing/sentryNonRecordingSpan'; import { hasTracingEnabled } from './utils/hasTracingEnabled'; import { getActiveSpan } from './utils/spanUtils'; +import { getTraceData } from './utils/traceData'; type PolymorphicRequestHeaders = | Record @@ -50,9 +50,6 @@ export function instrumentFetchRequest( return undefined; } - const scope = getCurrentScope(); - const client = getClient(); - const { method, url } = handlerData.fetchData; const fullUrl = getFullURL(url); @@ -79,7 +76,7 @@ export function instrumentFetchRequest( handlerData.fetchData.__span = span.spanContext().spanId; spans[span.spanContext().spanId] = span; - if (shouldAttachHeaders(handlerData.fetchData.url) && client) { + if (shouldAttachHeaders(handlerData.fetchData.url)) { const request: string | Request = handlerData.args[0]; // In case the user hasn't set the second argument of a fetch call we default it to `{}`. @@ -88,10 +85,10 @@ export function instrumentFetchRequest( // eslint-disable-next-line @typescript-eslint/no-explicit-any const options: { [key: string]: any } = handlerData.args[1]; - options.headers = addTracingHeadersToFetchRequest( + options.headers = _addTracingHeadersToFetchRequest( request, - client, - scope, + undefined, + undefined, options, // If performance is disabled (TWP) or there's no active root span (pageload/navigation/interaction), // we do not want to use the span as base for the trace headers, @@ -104,12 +101,19 @@ export function instrumentFetchRequest( } /** - * Adds sentry-trace and baggage headers to the various forms of fetch headers + * Adds sentry-trace and baggage headers to the various forms of fetch headers. + * + * @deprecated This function will not be exported anymore in v9. + */ +export const addTracingHeadersToFetchRequest = _addTracingHeadersToFetchRequest; + +/** + * Adds sentry-trace and baggage headers to the various forms of fetch headers. */ -export function addTracingHeadersToFetchRequest( +function _addTracingHeadersToFetchRequest( request: string | unknown, // unknown is actually type Request but we can't export DOM types from this package, - client: Client, - scope: Scope, + _client: Client | undefined, + _scope: Scope | undefined, fetchOptionsObj: { headers?: | { @@ -119,18 +123,22 @@ export function addTracingHeadersToFetchRequest( }, span?: Span, ): PolymorphicRequestHeaders | undefined { - const { sentryTrace, baggage } = getSentryHeaders({ span, client, scope }); + const traceHeaders = getTraceData({ span }); + const sentryTrace = traceHeaders['sentry-trace']; + const baggage = traceHeaders.baggage; const headers = fetchOptionsObj.headers || (typeof Request !== 'undefined' && isInstanceOf(request, Request) ? (request as Request).headers : undefined); if (!headers) { - return { 'sentry-trace': sentryTrace, baggage }; + return { ...traceHeaders }; } else if (typeof Headers !== 'undefined' && isInstanceOf(headers, Headers)) { const newHeaders = new Headers(headers as Headers); - newHeaders.set('sentry-trace', sentryTrace); + if (sentryTrace) { + newHeaders.set('sentry-trace', sentryTrace); + } if (baggage) { const prevBaggageHeader = newHeaders.get(BAGGAGE_HEADER_NAME); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 9fdc50ec1593..dbe2086e9f6e 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -110,7 +110,11 @@ export type { MetricData } from '@sentry/types'; export { metricsDefault } from './metrics/exports-default'; export { BrowserMetricsAggregator } from './metrics/browser-aggregator'; export { getMetricSummaryJsonForSpan } from './metrics/metric-summary'; -export { addTracingHeadersToFetchRequest, instrumentFetchRequest } from './fetch'; +export { + // eslint-disable-next-line deprecation/deprecation + addTracingHeadersToFetchRequest, + instrumentFetchRequest, +} from './fetch'; export { trpcMiddleware } from './trpc'; export { captureFeedback } from './feedback'; diff --git a/packages/core/src/tracing/index.ts b/packages/core/src/tracing/index.ts index 12b71caff897..a6905741d1c9 100644 --- a/packages/core/src/tracing/index.ts +++ b/packages/core/src/tracing/index.ts @@ -28,4 +28,3 @@ export { export { setMeasurement, timedEventsToMeasurements } from './measurement'; export { sampleSpan } from './sampling'; export { logSpanEnd, logSpanStart } from './logSpans'; -export { getSentryHeaders } from './sentryHeaders'; diff --git a/packages/core/src/tracing/sentryHeaders.ts b/packages/core/src/tracing/sentryHeaders.ts deleted file mode 100644 index bf856802359a..000000000000 --- a/packages/core/src/tracing/sentryHeaders.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { Client, Scope, Span } from '@sentry/types'; -import { dynamicSamplingContextToSentryBaggageHeader, generateSentryTraceHeader } from '@sentry/utils'; -import { getCurrentScope } from '../currentScopes'; -import { spanToTraceHeader } from '../utils/spanUtils'; -import { getDynamicSamplingContextFromScope, getDynamicSamplingContextFromSpan } from './dynamicSamplingContext'; - -/** - * Get the sentry-trace and baggage headers for a given span or scope. - * If no scope is defined, it will use the current scope. - */ -export function getSentryHeaders({ - span, - client, - scope = getCurrentScope(), -}: { span?: Span; client: Client; scope?: Scope }): { - sentryTrace: string; - baggage: string | undefined; -} { - const sentryTrace = span ? spanToTraceHeader(span) : scopesToTraceHeader(scope); - const dsc = span ? getDynamicSamplingContextFromSpan(span) : getDynamicSamplingContextFromScope(client, scope); - const baggage = dynamicSamplingContextToSentryBaggageHeader(dsc); - - return { - sentryTrace, - baggage, - }; -} - -/** - * Get a sentry-trace header value for the given scope. - */ -function scopesToTraceHeader(scope: Scope): string { - const { traceId, sampled, spanId } = scope.getPropagationContext(); - return generateSentryTraceHeader(traceId, spanId, sampled); -} diff --git a/packages/core/src/utils/traceData.ts b/packages/core/src/utils/traceData.ts index e2cec9ca8053..105cf3272140 100644 --- a/packages/core/src/utils/traceData.ts +++ b/packages/core/src/utils/traceData.ts @@ -1,11 +1,20 @@ -import type { SerializedTraceData } from '@sentry/types'; -import { TRACEPARENT_REGEXP, logger } from '@sentry/utils'; +import type { SerializedTraceData, Span } from '@sentry/types'; +import { + TRACEPARENT_REGEXP, + dynamicSamplingContextToSentryBaggageHeader, + generateSentryTraceHeader, + logger, +} from '@sentry/utils'; import { getAsyncContextStrategy } from '../asyncContext'; import { getMainCarrier } from '../carrier'; -import { getClient } from '../currentScopes'; +import { getClient, getCurrentScope } from '../currentScopes'; import { isEnabled } from '../exports'; -import { getSentryHeaders } from '../tracing'; -import { getActiveSpan } from './spanUtils'; +import type { Scope } from '../scope'; +import { + getDynamicSamplingContextFromScope, + getDynamicSamplingContextFromSpan, +} from '../tracing/dynamicSamplingContext'; +import { getActiveSpan, spanToTraceHeader } from './spanUtils'; /** * Extracts trace propagation data from the current span or from the client's scope (via transaction or propagation @@ -18,7 +27,7 @@ import { getActiveSpan } from './spanUtils'; * @returns an object with the tracing data values. The object keys are the name of the tracing key to be used as header * or meta tag name. */ -export function getTraceData(): SerializedTraceData { +export function getTraceData(options: { span?: Span } = {}): SerializedTraceData { const client = getClient(); if (!isEnabled() || !client) { return {}; @@ -27,11 +36,14 @@ export function getTraceData(): SerializedTraceData { const carrier = getMainCarrier(); const acs = getAsyncContextStrategy(carrier); if (acs.getTraceData) { - return acs.getTraceData(); + return acs.getTraceData(options); } - const span = getActiveSpan(); - const { sentryTrace, baggage } = getSentryHeaders({ span, client }); + const scope = getCurrentScope(); + const span = options.span || getActiveSpan(); + const sentryTrace = span ? spanToTraceHeader(span) : scopeToTraceHeader(scope); + const dsc = span ? getDynamicSamplingContextFromSpan(span) : getDynamicSamplingContextFromScope(client, scope); + const baggage = dynamicSamplingContextToSentryBaggageHeader(dsc); const isValidSentryTraceHeader = TRACEPARENT_REGEXP.test(sentryTrace); if (!isValidSentryTraceHeader) { @@ -71,3 +83,11 @@ export function isValidBaggageString(baggage?: string): boolean { ); return baggageRegex.test(baggage); } + +/** + * Get a sentry-trace header value for the given scope. + */ +function scopeToTraceHeader(scope: Scope): string { + const { traceId, sampled, spanId } = scope.getPropagationContext(); + return generateSentryTraceHeader(traceId, spanId, sampled); +} diff --git a/packages/core/test/lib/tracing/sentryHeaders.test.ts b/packages/core/test/lib/tracing/sentryHeaders.test.ts deleted file mode 100644 index de8fe0a00eda..000000000000 --- a/packages/core/test/lib/tracing/sentryHeaders.test.ts +++ /dev/null @@ -1,194 +0,0 @@ -import type { Client } from '@sentry/types'; -import { Scope, SentrySpan, getCurrentScope, getGlobalScope, getIsolationScope, setCurrentClient } from '../../../src'; -import { freezeDscOnSpan } from '../../../src/tracing/dynamicSamplingContext'; -import { getSentryHeaders } from '../../../src/tracing/sentryHeaders'; -import type { TestClientOptions } from '../../mocks/client'; -import { TestClient, getDefaultTestClientOptions } from '../../mocks/client'; - -const dsn = 'https://123@sentry.io/42'; - -const SCOPE_TRACE_ID = '12345678901234567890123456789012'; -const SCOPE_SPAN_ID = '1234567890123456'; - -function setupClient(opts?: TestClientOptions): Client { - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); - - getCurrentScope().setPropagationContext({ - traceId: SCOPE_TRACE_ID, - spanId: SCOPE_SPAN_ID, - }); - - const options = getDefaultTestClientOptions({ - dsn, - ...opts, - }); - const client = new TestClient(options); - setCurrentClient(client); - client.init(); - - return client; -} - -describe('getSentryHeaders', () => { - beforeEach(() => {}); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('works with a minimal client', () => { - const client = setupClient(); - - const { sentryTrace, baggage } = getSentryHeaders({ client }); - - expect(sentryTrace).toEqual(`${SCOPE_TRACE_ID}-${SCOPE_SPAN_ID}`); - expect(baggage).toEqual(`sentry-environment=production,sentry-public_key=123,sentry-trace_id=${SCOPE_TRACE_ID}`); - }); - - it('allows to pass a specific scope', () => { - const client = setupClient(); - - const traceId = '12345678901234567890123456789099'; - const spanId = '1234567890123499'; - const scope = new Scope(); - scope.setPropagationContext({ - traceId, - spanId, - }); - - const { sentryTrace, baggage } = getSentryHeaders({ client, scope }); - - expect(sentryTrace).toEqual(`${traceId}-${spanId}`); - expect(baggage).toEqual(`sentry-environment=production,sentry-public_key=123,sentry-trace_id=${traceId}`); - }); - - it('uses DSC from scope, if available', () => { - const client = setupClient(); - - const traceId = '12345678901234567890123456789099'; - const spanId = '1234567890123499'; - const scope = new Scope(); - scope.setPropagationContext({ - traceId, - spanId, - dsc: { - environment: 'test-dev', - public_key: '456', - trace_id: '12345678901234567890123456789088', - }, - }); - - const { sentryTrace, baggage } = getSentryHeaders({ client, scope }); - - expect(sentryTrace).toEqual(`${traceId}-${spanId}`); - expect(baggage).toEqual( - 'sentry-environment=test-dev,sentry-public_key=456,sentry-trace_id=12345678901234567890123456789088', - ); - }); - - it('works with a minimal unsampled span', () => { - const client = setupClient(); - - const traceId = '12345678901234567890123456789099'; - const spanId = '1234567890123499'; - - const span = new SentrySpan({ - traceId, - spanId, - sampled: false, - }); - - const { sentryTrace, baggage } = getSentryHeaders({ client, span }); - - expect(sentryTrace).toEqual(`${traceId}-${spanId}-0`); - expect(baggage).toEqual(`sentry-environment=production,sentry-public_key=123,sentry-trace_id=${traceId}`); - }); - - it('works with a minimal sampled span', () => { - const client = setupClient(); - - const traceId = '12345678901234567890123456789099'; - const spanId = '1234567890123499'; - - const span = new SentrySpan({ - traceId, - spanId, - sampled: true, - }); - - const { sentryTrace, baggage } = getSentryHeaders({ client, span }); - - expect(sentryTrace).toEqual(`${traceId}-${spanId}-1`); - expect(baggage).toEqual(`sentry-environment=production,sentry-public_key=123,sentry-trace_id=${traceId}`); - }); - - it('works with a SentrySpan with frozen DSC', () => { - const client = setupClient(); - - const traceId = '12345678901234567890123456789099'; - const spanId = '1234567890123499'; - - const span = new SentrySpan({ - traceId, - spanId, - sampled: true, - }); - - freezeDscOnSpan(span, { - environment: 'test-dev', - public_key: '456', - trace_id: '12345678901234567890123456789088', - }); - - const { sentryTrace, baggage } = getSentryHeaders({ client, span }); - - expect(sentryTrace).toEqual(`${traceId}-${spanId}-1`); - expect(baggage).toEqual( - 'sentry-environment=test-dev,sentry-public_key=456,sentry-trace_id=12345678901234567890123456789088', - ); - }); - - it('works with an OTEL span with frozen DSC in traceState', () => { - const client = setupClient(); - - const traceId = '12345678901234567890123456789099'; - const spanId = '1234567890123499'; - - const span = new SentrySpan({ - traceId, - spanId, - sampled: true, - }); - - span.spanContext = () => { - const traceState = { - set: () => traceState, - unset: () => traceState, - get: (key: string) => { - if (key === 'sentry.dsc') { - return 'sentry-environment=test-dev,sentry-public_key=456,sentry-trace_id=12345678901234567890123456789088'; - } - return undefined; - }, - serialize: () => '', - }; - - return { - traceId, - spanId, - sampled: true, - traceFlags: 1, - traceState, - }; - }; - - const { sentryTrace, baggage } = getSentryHeaders({ client, span }); - - expect(sentryTrace).toEqual(`${traceId}-${spanId}-1`); - expect(baggage).toEqual( - 'sentry-environment=test-dev,sentry-public_key=456,sentry-trace_id=12345678901234567890123456789088', - ); - }); -}); diff --git a/packages/core/test/lib/utils/traceData.test.ts b/packages/core/test/lib/utils/traceData.test.ts index d35425ea28f6..c6ef21101830 100644 --- a/packages/core/test/lib/utils/traceData.test.ts +++ b/packages/core/test/lib/utils/traceData.test.ts @@ -1,160 +1,280 @@ -import { SentrySpan, getTraceData } from '../../../src/'; -import * as SentryCoreCurrentScopes from '../../../src/currentScopes'; -import * as SentryCoreExports from '../../../src/exports'; -import * as SentryCoreTracing from '../../../src/tracing'; -import * as SentryCoreTracingDsc from '../../../src/tracing/dynamicSamplingContext'; +import type { Client, Span } from '@sentry/types'; +import { + SentrySpan, + getCurrentScope, + getGlobalScope, + getIsolationScope, + getMainCarrier, + getTraceData, + setAsyncContextStrategy, + setCurrentClient, + withActiveSpan, +} from '../../../src/'; +import { getAsyncContextStrategy } from '../../../src/asyncContext'; import { freezeDscOnSpan } from '../../../src/tracing/dynamicSamplingContext'; -import * as SentryCoreSpanUtils from '../../../src/utils/spanUtils'; import { isValidBaggageString } from '../../../src/utils/traceData'; +import type { TestClientOptions } from '../../mocks/client'; +import { TestClient, getDefaultTestClientOptions } from '../../mocks/client'; -const TRACE_FLAG_SAMPLED = 1; +const dsn = 'https://123@sentry.io/42'; -const mockedClient = {} as any; +const SCOPE_TRACE_ID = '12345678901234567890123456789012'; +const SCOPE_SPAN_ID = '1234567890123456'; -const mockedScope = { - getPropagationContext: () => ({ - traceId: '123', - spanId: '456', - }), -} as any; +function setupClient(opts?: Partial): Client { + getCurrentScope().setPropagationContext({ + traceId: SCOPE_TRACE_ID, + spanId: SCOPE_SPAN_ID, + }); + + const options = getDefaultTestClientOptions({ + dsn, + tracesSampleRate: 1, + ...opts, + }); + const client = new TestClient(options); + setCurrentClient(client); + client.init(); + + return client; +} describe('getTraceData', () => { beforeEach(() => { - jest.spyOn(SentryCoreExports, 'isEnabled').mockReturnValue(true); + setAsyncContextStrategy(undefined); + getCurrentScope().clear(); + getIsolationScope().clear(); + getGlobalScope().clear(); + getCurrentScope().setClient(undefined); }); afterEach(() => { jest.clearAllMocks(); }); - it('returns the tracing data from the span, if a span is available', () => { - { - const mockedSpan = new SentrySpan({ - traceId: '12345678901234567890123456789012', - spanId: '1234567890123456', - sampled: true, + it('uses the ACS implementation, if available', () => { + setupClient(); + + const carrier = getMainCarrier(); + + const customFn = jest.fn((options?: { span?: Span }) => { + expect(options).toEqual({ span: undefined }); + return { + 'sentry-trace': 'abc', + baggage: 'xyz', + }; + }) as typeof getTraceData; + + const acs = { + ...getAsyncContextStrategy(carrier), + getTraceData: customFn, + }; + setAsyncContextStrategy(acs); + + const span = new SentrySpan({ + traceId: '12345678901234567890123456789012', + spanId: '1234567890123456', + sampled: true, + }); + + withActiveSpan(span, () => { + const data = getTraceData(); + + expect(data).toEqual({ + 'sentry-trace': 'abc', + baggage: 'xyz', }); - freezeDscOnSpan(mockedSpan, { environment: 'production' }); + }); + }); + + it('passes span to ACS implementation, if available', () => { + setupClient(); + + const carrier = getMainCarrier(); - jest.spyOn(SentryCoreSpanUtils, 'getActiveSpan').mockImplementation(() => mockedSpan); - jest.spyOn(SentryCoreCurrentScopes, 'getCurrentScope').mockImplementation(() => mockedScope); - jest.spyOn(SentryCoreCurrentScopes, 'getClient').mockImplementation(() => mockedClient); + const span = new SentrySpan({ + traceId: '12345678901234567890123456789012', + spanId: '1234567890123456', + sampled: true, + }); + + const customFn = jest.fn((options?: { span?: Span }) => { + expect(options).toEqual({ span }); + return { + 'sentry-trace': 'abc', + baggage: 'xyz', + }; + }) as typeof getTraceData; + + const acs = { + ...getAsyncContextStrategy(carrier), + getTraceData: customFn, + }; + setAsyncContextStrategy(acs); + + const data = getTraceData({ span }); + + expect(data).toEqual({ + 'sentry-trace': 'abc', + baggage: 'xyz', + }); + }); + + it('returns the tracing data from the span, if a span is available', () => { + setupClient(); + + const span = new SentrySpan({ + traceId: '12345678901234567890123456789012', + spanId: '1234567890123456', + sampled: true, + }); + withActiveSpan(span, () => { const data = getTraceData(); expect(data).toEqual({ 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1', - baggage: 'sentry-environment=production', + baggage: + 'sentry-environment=production,sentry-public_key=123,sentry-trace_id=12345678901234567890123456789012,sentry-sampled=true', }); - } + }); + }); + + it('allows to pass a span directly', () => { + setupClient(); + + const span = new SentrySpan({ + traceId: '12345678901234567890123456789012', + spanId: '1234567890123456', + sampled: true, + }); + + const data = getTraceData({ span }); + + expect(data).toEqual({ + 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1', + baggage: + 'sentry-environment=production,sentry-public_key=123,sentry-trace_id=12345678901234567890123456789012,sentry-sampled=true', + }); }); it('returns propagationContext DSC data if no span is available', () => { - jest.spyOn(SentryCoreSpanUtils, 'getActiveSpan').mockImplementation(() => undefined); - jest.spyOn(SentryCoreCurrentScopes, 'getCurrentScope').mockImplementation( - () => - ({ - getPropagationContext: () => ({ - traceId: '12345678901234567890123456789012', - sampled: true, - spanId: '1234567890123456', - dsc: { - environment: 'staging', - public_key: 'key', - trace_id: '12345678901234567890123456789012', - }, - }), - }) as any, - ); - jest.spyOn(SentryCoreCurrentScopes, 'getClient').mockImplementation(() => mockedClient); + setupClient(); + + getCurrentScope().setPropagationContext({ + traceId: '12345678901234567890123456789012', + sampled: true, + spanId: '1234567890123456', + dsc: { + environment: 'staging', + public_key: 'key', + trace_id: '12345678901234567890123456789012', + }, + }); const traceData = getTraceData(); expect(traceData).toEqual({ - 'sentry-trace': expect.stringMatching(/12345678901234567890123456789012-(.{16})-1/), + 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1', baggage: 'sentry-environment=staging,sentry-public_key=key,sentry-trace_id=12345678901234567890123456789012', }); }); - it('returns only the `sentry-trace` value if no DSC is available', () => { - jest.spyOn(SentryCoreTracingDsc, 'getDynamicSamplingContextFromSpan').mockReturnValue({ - trace_id: '', - public_key: undefined, + it('returns frozen DSC from SentrySpan if available', () => { + setupClient(); + + const span = new SentrySpan({ + traceId: '12345678901234567890123456789012', + spanId: '1234567890123456', + sampled: true, }); - // @ts-expect-error - we don't need to provide all the properties - jest.spyOn(SentryCoreSpanUtils, 'getActiveSpan').mockImplementation(() => ({ - isRecording: () => true, - spanContext: () => { - return { - traceId: '12345678901234567890123456789012', - spanId: '1234567890123456', - traceFlags: TRACE_FLAG_SAMPLED, - }; - }, - })); + freezeDscOnSpan(span, { + environment: 'test-dev', + public_key: '456', + trace_id: '12345678901234567890123456789088', + }); - jest.spyOn(SentryCoreCurrentScopes, 'getCurrentScope').mockImplementation(() => mockedScope); - jest.spyOn(SentryCoreCurrentScopes, 'getClient').mockImplementation(() => { - return { - getOptions: () => ({}), - getDsn: () => ({}), - } as any; + withActiveSpan(span, () => { + const data = getTraceData(); + + expect(data).toEqual({ + 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1', + baggage: 'sentry-environment=test-dev,sentry-public_key=456,sentry-trace_id=12345678901234567890123456789088', + }); }); + }); - const traceData = getTraceData(); + it('works with an OTEL span with frozen DSC in traceState', () => { + setupClient(); - expect(traceData).toEqual({ - 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1', + const traceId = '12345678901234567890123456789099'; + const spanId = '1234567890123499'; + + const span = new SentrySpan({ + traceId, + spanId, + sampled: true, + }); + + span.spanContext = () => { + const traceState = { + set: () => traceState, + unset: () => traceState, + get: (key: string) => { + if (key === 'sentry.dsc') { + return 'sentry-environment=test-dev,sentry-public_key=456,sentry-trace_id=12345678901234567890123456789088'; + } + return undefined; + }, + serialize: () => '', + }; + + return { + traceId, + spanId, + sampled: true, + traceFlags: 1, + traceState, + }; + }; + + withActiveSpan(span, () => { + const data = getTraceData(); + + expect(data).toEqual({ + 'sentry-trace': '12345678901234567890123456789099-1234567890123499-1', + baggage: 'sentry-environment=test-dev,sentry-public_key=456,sentry-trace_id=12345678901234567890123456789088', + }); }); }); it('returns empty object without a client', () => { - jest.spyOn(SentryCoreTracing, 'getDynamicSamplingContextFromClient').mockReturnValueOnce({ - trace_id: '', - public_key: undefined, - }); - - // @ts-expect-error - we don't need to provide all the properties - jest.spyOn(SentryCoreSpanUtils, 'getActiveSpan').mockImplementation(() => ({ - isRecording: () => true, - spanContext: () => { - return { - traceId: '12345678901234567890123456789012', - spanId: '1234567890123456', - traceFlags: TRACE_FLAG_SAMPLED, - }; - }, - })); - jest.spyOn(SentryCoreCurrentScopes, 'getCurrentScope').mockImplementation(() => mockedScope); - jest.spyOn(SentryCoreCurrentScopes, 'getClient').mockImplementation(() => undefined); - const traceData = getTraceData(); expect(traceData).toEqual({}); }); it('returns an empty object if the `sentry-trace` value is invalid', () => { - // @ts-expect-error - we don't need to provide all the properties - jest.spyOn(SentryCoreSpanUtils, 'getActiveSpan').mockImplementation(() => ({ - isRecording: () => true, - spanContext: () => { - return { - traceId: '1234567890123456789012345678901+', - spanId: '1234567890123456', - traceFlags: TRACE_FLAG_SAMPLED, - }; - }, - })); - - const traceData = getTraceData(); + // Invalid traceID + const traceId = '1234567890123456789012345678901+'; + const spanId = '1234567890123499'; + + const span = new SentrySpan({ + traceId, + spanId, + sampled: true, + }); - expect(traceData).toEqual({}); + withActiveSpan(span, () => { + const data = getTraceData(); + expect(data).toEqual({}); + }); }); it('returns an empty object if the SDK is disabled', () => { - jest.spyOn(SentryCoreExports, 'isEnabled').mockReturnValueOnce(false); + setupClient({ dsn: undefined }); const traceData = getTraceData(); diff --git a/packages/opentelemetry/src/utils/getTraceData.ts b/packages/opentelemetry/src/utils/getTraceData.ts index d85f6f699ef3..9f1b87ecda8e 100644 --- a/packages/opentelemetry/src/utils/getTraceData.ts +++ b/packages/opentelemetry/src/utils/getTraceData.ts @@ -1,15 +1,25 @@ import * as api from '@opentelemetry/api'; -import type { SerializedTraceData } from '@sentry/types'; +import { getCapturedScopesOnSpan } from '@sentry/core'; +import type { SerializedTraceData, Span } from '@sentry/types'; import { dropUndefinedKeys } from '@sentry/utils'; +import { getContextFromScope } from './contextData'; /** * Otel-specific implementation of `getTraceData`. * @see `@sentry/core` version of `getTraceData` for more information */ -export function getTraceData(): SerializedTraceData { +export function getTraceData({ span }: { span?: Span } = {}): SerializedTraceData { const headersObject: Record = {}; - api.propagation.inject(api.context.active(), headersObject); + if (span) { + const { scope } = getCapturedScopesOnSpan(span); + // fall back to current context if for whatever reason we can't find the one of the span + const ctx = (scope && getContextFromScope(scope)) || api.trace.setSpan(api.context.active(), span); + + api.propagation.inject(ctx, headersObject); + } else { + api.propagation.inject(api.context.active(), headersObject); + } if (!headersObject['sentry-trace']) { return {}; diff --git a/packages/opentelemetry/test/utils/getTraceData.test.ts b/packages/opentelemetry/test/utils/getTraceData.test.ts new file mode 100644 index 000000000000..e0f2270d8e22 --- /dev/null +++ b/packages/opentelemetry/test/utils/getTraceData.test.ts @@ -0,0 +1,93 @@ +import { context, trace } from '@opentelemetry/api'; +import { getCurrentScope, setAsyncContextStrategy } from '@sentry/core'; +import { getTraceData } from '../../src/utils/getTraceData'; +import { makeTraceState } from '../../src/utils/makeTraceState'; +import { cleanupOtel, mockSdkInit } from '../helpers/mockSdkInit'; + +describe('getTraceData', () => { + beforeEach(() => { + setAsyncContextStrategy(undefined); + mockSdkInit(); + }); + + afterEach(() => { + cleanupOtel(); + jest.clearAllMocks(); + }); + + it('returns the tracing data from the span, if a span is available', () => { + const ctx = trace.setSpanContext(context.active(), { + traceId: '12345678901234567890123456789012', + spanId: '1234567890123456', + traceFlags: 1, + }); + + context.with(ctx, () => { + const data = getTraceData(); + + expect(data).toEqual({ + 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1', + baggage: + 'sentry-environment=production,sentry-public_key=username,sentry-trace_id=12345678901234567890123456789012,sentry-sampled=true', + }); + }); + }); + + it('allows to pass a span directly', () => { + const ctx = trace.setSpanContext(context.active(), { + traceId: '12345678901234567890123456789012', + spanId: '1234567890123456', + traceFlags: 1, + }); + + const span = trace.getSpan(ctx)!; + + const data = getTraceData({ span }); + + expect(data).toEqual({ + 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1', + baggage: + 'sentry-environment=production,sentry-public_key=username,sentry-trace_id=12345678901234567890123456789012,sentry-sampled=true', + }); + }); + + it('returns propagationContext DSC data if no span is available', () => { + getCurrentScope().setPropagationContext({ + traceId: '12345678901234567890123456789012', + sampled: true, + spanId: '1234567890123456', + dsc: { + environment: 'staging', + public_key: 'key', + trace_id: '12345678901234567890123456789012', + }, + }); + + const traceData = getTraceData(); + + expect(traceData).toEqual({ + 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1', + baggage: 'sentry-environment=staging,sentry-public_key=key,sentry-trace_id=12345678901234567890123456789012', + }); + }); + + it('works with an span with frozen DSC in traceState', () => { + const ctx = trace.setSpanContext(context.active(), { + traceId: '12345678901234567890123456789012', + spanId: '1234567890123456', + traceFlags: 1, + traceState: makeTraceState({ + dsc: { environment: 'test-dev', public_key: '456', trace_id: '12345678901234567890123456789088' }, + }), + }); + + context.with(ctx, () => { + const data = getTraceData(); + + expect(data).toEqual({ + 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1', + baggage: 'sentry-environment=test-dev,sentry-public_key=456,sentry-trace_id=12345678901234567890123456789088', + }); + }); + }); +}); diff --git a/packages/remix/src/utils/instrumentServer.ts b/packages/remix/src/utils/instrumentServer.ts index 942f8b17c8df..d2d3119b7917 100644 --- a/packages/remix/src/utils/instrumentServer.ts +++ b/packages/remix/src/utils/instrumentServer.ts @@ -6,7 +6,7 @@ import { getActiveSpan, getClient, getRootSpan, - getSentryHeaders, + getTraceData, hasTracingEnabled, setHttpStatus, spanToJSON, @@ -14,7 +14,7 @@ import { withIsolationScope, } from '@sentry/core'; import { continueTrace } from '@sentry/opentelemetry'; -import type { TransactionSource, WrappedFunction } from '@sentry/types'; +import type { SerializedTraceData, TransactionSource, WrappedFunction } from '@sentry/types'; import type { Span } from '@sentry/types'; import { fill, isNodeEnv, loadModule, logger } from '@sentry/utils'; @@ -200,21 +200,8 @@ const makeWrappedLoader = return makeWrappedDataFunction(origLoader, id, 'loader', remixVersion, autoInstrumentRemix); }; -function getTraceAndBaggage(): { - sentryTrace?: string; - sentryBaggage?: string; -} { - const client = getClient(); - if (isNodeEnv() && client) { - const { sentryTrace, baggage } = getSentryHeaders({ client, span: getActiveSpan() }); - - return { - sentryTrace, - sentryBaggage: baggage, - }; - } - - return {}; +function getTraceAndBaggage(): SerializedTraceData { + return isNodeEnv() ? getTraceData() : {}; } function makeWrappedRootLoader(remixVersion: number) { @@ -224,8 +211,8 @@ function makeWrappedRootLoader(remixVersion: number) { const traceAndBaggage = getTraceAndBaggage(); if (isDeferredData(res)) { - res.data['sentryTrace'] = traceAndBaggage.sentryTrace; - res.data['sentryBaggage'] = traceAndBaggage.sentryBaggage; + res.data['sentryTrace'] = traceAndBaggage['sentry-trace']; + res.data['sentryBaggage'] = traceAndBaggage.baggage; res.data['remixVersion'] = remixVersion; return res; From f6208e05bb0e5f6402b0a6dc571aeef71893ae38 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 20 Nov 2024 12:08:31 +0100 Subject: [PATCH 08/30] fix tests & checks --- docs/migration/draft-v9-migration-guide.md | 1 + packages/browser/src/tracing/request.ts | 3 ++- packages/core/src/fetch.ts | 13 ++++++++----- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/migration/draft-v9-migration-guide.md b/docs/migration/draft-v9-migration-guide.md index 3a7c8cb2343c..351e1a611bfb 100644 --- a/docs/migration/draft-v9-migration-guide.md +++ b/docs/migration/draft-v9-migration-guide.md @@ -13,6 +13,7 @@ - Deprecated `transactionNamingScheme` option in `requestDataIntegration`. - Deprecated `debugIntegration`. To log outgoing events, use [Hook Options](https://docs.sentry.io/platforms/javascript/configuration/options/#hooks) (`beforeSend`, `beforeSendTransaction`, ...). - Deprecated `sessionTimingIntegration`. To capture session durations alongside events, use [Context](https://docs.sentry.io/platforms/javascript/enriching-events/context/) (`Sentry.setContext()`). +- Deprecated `addTracingHeadersToFetchRequest` method - this was only meant for internal use and is not needed anymore. ## `@sentry/nestjs` diff --git a/packages/browser/src/tracing/request.ts b/packages/browser/src/tracing/request.ts index 155708dfb242..c61748e72865 100644 --- a/packages/browser/src/tracing/request.ts +++ b/packages/browser/src/tracing/request.ts @@ -15,6 +15,7 @@ import { setHttpStatus, spanToJSON, startInactiveSpan, + getClient, } from '@sentry/core'; import type { Client, HandlerDataXhr, SentryWrappedXMLHttpRequest, Span } from '@sentry/types'; import { @@ -395,7 +396,7 @@ export function xhrCallback( xhr.__sentry_xhr_span_id__ = span.spanContext().spanId; spans[xhr.__sentry_xhr_span_id__] = span; - if (xhr.setRequestHeader && shouldAttachHeaders(sentryXhrData.url)) { + if (xhr.setRequestHeader && shouldAttachHeaders(sentryXhrData.url) && getClient()) { addTracingHeadersToXhrRequest( xhr, // If performance is disabled (TWP) or there's no active root span (pageload/navigation/interaction), diff --git a/packages/core/src/fetch.ts b/packages/core/src/fetch.ts index 7050937b8ab2..d8d4bff31da3 100644 --- a/packages/core/src/fetch.ts +++ b/packages/core/src/fetch.ts @@ -1,5 +1,6 @@ import type { Client, HandlerDataFetch, Scope, Span, SpanOrigin } from '@sentry/types'; import { BAGGAGE_HEADER_NAME, SENTRY_BAGGAGE_KEY_PREFIX, isInstanceOf, parseUrl } from '@sentry/utils'; +import { getClient } from './currentScopes'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from './semanticAttributes'; import { SPAN_STATUS_ERROR, setHttpStatus, startInactiveSpan } from './tracing'; import { SentryNonRecordingSpan } from './tracing/sentryNonRecordingSpan'; @@ -76,7 +77,7 @@ export function instrumentFetchRequest( handlerData.fetchData.__span = span.spanContext().spanId; spans[span.spanContext().spanId] = span; - if (shouldAttachHeaders(handlerData.fetchData.url)) { + if (shouldAttachHeaders(handlerData.fetchData.url) && getClient()) { const request: string | Request = handlerData.args[0]; // In case the user hasn't set the second argument of a fetch call we default it to `{}`. @@ -127,6 +128,11 @@ function _addTracingHeadersToFetchRequest( const sentryTrace = traceHeaders['sentry-trace']; const baggage = traceHeaders.baggage; + // Nothing to do, we just return the existing headers untouched + if (!sentryTrace) { + return fetchOptionsObj && (fetchOptionsObj.headers as PolymorphicRequestHeaders); + } + const headers = fetchOptionsObj.headers || (typeof Request !== 'undefined' && isInstanceOf(request, Request) ? (request as Request).headers : undefined); @@ -135,10 +141,7 @@ function _addTracingHeadersToFetchRequest( return { ...traceHeaders }; } else if (typeof Headers !== 'undefined' && isInstanceOf(headers, Headers)) { const newHeaders = new Headers(headers as Headers); - - if (sentryTrace) { - newHeaders.set('sentry-trace', sentryTrace); - } + newHeaders.set('sentry-trace', sentryTrace); if (baggage) { const prevBaggageHeader = newHeaders.get(BAGGAGE_HEADER_NAME); From 04227f28b8d189ceb7efb7a6091b94af248fd95e Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 20 Nov 2024 12:28:21 +0100 Subject: [PATCH 09/30] fix linting --- packages/browser/src/tracing/request.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/browser/src/tracing/request.ts b/packages/browser/src/tracing/request.ts index c61748e72865..50cdf5d9a47d 100644 --- a/packages/browser/src/tracing/request.ts +++ b/packages/browser/src/tracing/request.ts @@ -9,13 +9,13 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SentryNonRecordingSpan, getActiveSpan, + getClient, getTraceData, hasTracingEnabled, instrumentFetchRequest, setHttpStatus, spanToJSON, startInactiveSpan, - getClient, } from '@sentry/core'; import type { Client, HandlerDataXhr, SentryWrappedXMLHttpRequest, Span } from '@sentry/types'; import { From a5f8c1e06ae00e3519f78df610ab41391eadd5ad Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 20 Nov 2024 12:30:14 +0100 Subject: [PATCH 10/30] "fix" remix test??? --- packages/remix/src/utils/instrumentServer.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/remix/src/utils/instrumentServer.ts b/packages/remix/src/utils/instrumentServer.ts index d2d3119b7917..0547a4303465 100644 --- a/packages/remix/src/utils/instrumentServer.ts +++ b/packages/remix/src/utils/instrumentServer.ts @@ -201,7 +201,8 @@ const makeWrappedLoader = }; function getTraceAndBaggage(): SerializedTraceData { - return isNodeEnv() ? getTraceData() : {}; + // TODO: Do we need to check for hasTracingEnabled() here? + return isNodeEnv() && hasTracingEnabled() ? getTraceData() : {}; } function makeWrappedRootLoader(remixVersion: number) { From 12c9f456f6d07cf1bce9de5c31f53307bff20b2d Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 20 Nov 2024 13:17:18 +0100 Subject: [PATCH 11/30] revert --- packages/remix/src/utils/instrumentServer.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/remix/src/utils/instrumentServer.ts b/packages/remix/src/utils/instrumentServer.ts index 0547a4303465..d2d3119b7917 100644 --- a/packages/remix/src/utils/instrumentServer.ts +++ b/packages/remix/src/utils/instrumentServer.ts @@ -201,8 +201,7 @@ const makeWrappedLoader = }; function getTraceAndBaggage(): SerializedTraceData { - // TODO: Do we need to check for hasTracingEnabled() here? - return isNodeEnv() && hasTracingEnabled() ? getTraceData() : {}; + return isNodeEnv() ? getTraceData() : {}; } function makeWrappedRootLoader(remixVersion: number) { From e2de55dacb5cf7630a2211c945add7dcaa3e459e Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 20 Nov 2024 13:17:23 +0100 Subject: [PATCH 12/30] update test?? --- .../test/client/root-loader.test.ts | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/remix/test/integration/test/client/root-loader.test.ts b/packages/remix/test/integration/test/client/root-loader.test.ts index 53b7648756e5..431195e8eab7 100644 --- a/packages/remix/test/integration/test/client/root-loader.test.ts +++ b/packages/remix/test/integration/test/client/root-loader.test.ts @@ -25,8 +25,8 @@ test('should inject `sentry-trace` and `baggage` into root loader returning an e const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page); - expect(sentryTrace).toEqual(expect.any(String)); - expect(sentryBaggage).toEqual(expect.any(String)); + expect(sentryTrace).toMatch(/.+/); + expect(sentryBaggage).toMatch(/.+/); const rootData = (await getRouteData(page))['root']; @@ -41,8 +41,8 @@ test('should inject `sentry-trace` and `baggage` into root loader returning a pl const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page); - expect(sentryTrace).toEqual(expect.any(String)); - expect(sentryBaggage).toEqual(expect.any(String)); + expect(sentryTrace).toMatch(/.+/); + expect(sentryBaggage).toMatch(/.+/); const rootData = (await getRouteData(page))['root']; @@ -59,8 +59,8 @@ test('should inject `sentry-trace` and `baggage` into root loader returning a `J const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page); - expect(sentryTrace).toEqual(expect.any(String)); - expect(sentryBaggage).toEqual(expect.any(String)); + expect(sentryTrace).toMatch(/.+/); + expect(sentryBaggage).toMatch(/.+/); const rootData = (await getRouteData(page))['root']; @@ -77,8 +77,8 @@ test('should inject `sentry-trace` and `baggage` into root loader returning a de const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page); - expect(sentryTrace).toEqual(expect.any(String)); - expect(sentryBaggage).toEqual(expect.any(String)); + expect(sentryTrace).toMatch(/.+/); + expect(sentryBaggage).toMatch(/.+/); const rootData = (await getRouteData(page))['root']; @@ -93,8 +93,8 @@ test('should inject `sentry-trace` and `baggage` into root loader returning `nul const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page); - expect(sentryTrace).toEqual(expect.any(String)); - expect(sentryBaggage).toEqual(expect.any(String)); + expect(sentryTrace).toMatch(/.+/); + expect(sentryBaggage).toMatch(/.+/); const rootData = (await getRouteData(page))['root']; @@ -109,8 +109,8 @@ test('should inject `sentry-trace` and `baggage` into root loader returning `und const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page); - expect(sentryTrace).toEqual(expect.any(String)); - expect(sentryBaggage).toEqual(expect.any(String)); + expect(sentryTrace).toMatch(/.+/); + expect(sentryBaggage).toMatch(/.+/); const rootData = (await getRouteData(page))['root']; @@ -130,8 +130,8 @@ test('should inject `sentry-trace` and `baggage` into root loader throwing a red const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page); - expect(sentryTrace).toEqual(expect.any(String)); - expect(sentryBaggage).toEqual(expect.any(String)); + expect(sentryTrace).toMatch(/.+/); + expect(sentryBaggage).toMatch(/.+/); const rootData = (await getRouteData(page))['root']; @@ -151,8 +151,8 @@ test('should inject `sentry-trace` and `baggage` into root loader returning a re const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page); - expect(sentryTrace).toEqual(expect.any(String)); - expect(sentryBaggage).toEqual(expect.any(String)); + expect(sentryTrace).toMatch(/.+/); + expect(sentryBaggage).toMatch(/.+/); const rootData = (await getRouteData(page))['root']; From d0c92b87b622c86ebed51e5cfe2cf4da59af9b3d Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 20 Nov 2024 14:24:43 +0100 Subject: [PATCH 13/30] fix remix instrument server --- packages/remix/src/utils/instrumentServer.ts | 31 +++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/packages/remix/src/utils/instrumentServer.ts b/packages/remix/src/utils/instrumentServer.ts index d2d3119b7917..cc8589ed41aa 100644 --- a/packages/remix/src/utils/instrumentServer.ts +++ b/packages/remix/src/utils/instrumentServer.ts @@ -14,7 +14,7 @@ import { withIsolationScope, } from '@sentry/core'; import { continueTrace } from '@sentry/opentelemetry'; -import type { SerializedTraceData, TransactionSource, WrappedFunction } from '@sentry/types'; +import type { TransactionSource, WrappedFunction } from '@sentry/types'; import type { Span } from '@sentry/types'; import { fill, isNodeEnv, loadModule, logger } from '@sentry/utils'; @@ -200,8 +200,17 @@ const makeWrappedLoader = return makeWrappedDataFunction(origLoader, id, 'loader', remixVersion, autoInstrumentRemix); }; -function getTraceAndBaggage(): SerializedTraceData { - return isNodeEnv() ? getTraceData() : {}; +function getTraceAndBaggage(): { sentryTrace?: string; sentryBaggage?: string } { + if (isNodeEnv()) { + const traceData = getTraceData(); + + return { + sentryTrace: traceData['sentry-trace'], + sentryBaggage: traceData.baggage, + }; + } + + return {}; } function makeWrappedRootLoader(remixVersion: number) { @@ -211,8 +220,8 @@ function makeWrappedRootLoader(remixVersion: number) { const traceAndBaggage = getTraceAndBaggage(); if (isDeferredData(res)) { - res.data['sentryTrace'] = traceAndBaggage['sentry-trace']; - res.data['sentryBaggage'] = traceAndBaggage.baggage; + res.data['sentryTrace'] = traceAndBaggage.sentryTrace; + res.data['sentryBaggage'] = traceAndBaggage.sentryBaggage; res.data['remixVersion'] = remixVersion; return res; @@ -230,7 +239,11 @@ function makeWrappedRootLoader(remixVersion: number) { if (typeof data === 'object') { return json( - { ...data, ...traceAndBaggage, remixVersion }, + { + ...data, + ...traceAndBaggage, + remixVersion, + }, { headers: res.headers, statusText: res.statusText, @@ -244,7 +257,11 @@ function makeWrappedRootLoader(remixVersion: number) { } } - return { ...res, ...traceAndBaggage, remixVersion }; + return { + ...res, + ...traceAndBaggage, + remixVersion, + }; }; }; } From 112ca89522fa2bf527b41f6c26a5f237d0721d92 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 20 Nov 2024 14:48:56 +0100 Subject: [PATCH 14/30] unflake unrelated test --- .../suites/integrations/ContextLines/noAddedLines/test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/dev-packages/browser-integration-tests/suites/integrations/ContextLines/noAddedLines/test.ts b/dev-packages/browser-integration-tests/suites/integrations/ContextLines/noAddedLines/test.ts index 8fa8ec16cddd..a3c8614bfb2b 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/ContextLines/noAddedLines/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/ContextLines/noAddedLines/test.ts @@ -7,6 +7,7 @@ sentryTest('should not add source context lines to errors from script files', as const url = await getLocalTestUrl({ testDir: __dirname }); const eventReqPromise = waitForErrorRequestOnUrl(page, url); + await page.waitForFunction('window.Sentry'); const clickPromise = page.locator('#script-error-btn').click(); From c47f0ac5ffe9668edf2d4e4b4dabcd86d8e88f4e Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 20 Nov 2024 14:56:19 +0100 Subject: [PATCH 15/30] minimal changes --- packages/remix/src/utils/instrumentServer.ts | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/packages/remix/src/utils/instrumentServer.ts b/packages/remix/src/utils/instrumentServer.ts index cc8589ed41aa..3ca241cbce2c 100644 --- a/packages/remix/src/utils/instrumentServer.ts +++ b/packages/remix/src/utils/instrumentServer.ts @@ -200,7 +200,10 @@ const makeWrappedLoader = return makeWrappedDataFunction(origLoader, id, 'loader', remixVersion, autoInstrumentRemix); }; -function getTraceAndBaggage(): { sentryTrace?: string; sentryBaggage?: string } { +function getTraceAndBaggage(): { + sentryTrace?: string; + sentryBaggage?: string; +} { if (isNodeEnv()) { const traceData = getTraceData(); @@ -239,11 +242,7 @@ function makeWrappedRootLoader(remixVersion: number) { if (typeof data === 'object') { return json( - { - ...data, - ...traceAndBaggage, - remixVersion, - }, + { ...data, ...traceAndBaggage, remixVersion }, { headers: res.headers, statusText: res.statusText, @@ -257,11 +256,7 @@ function makeWrappedRootLoader(remixVersion: number) { } } - return { - ...res, - ...traceAndBaggage, - remixVersion, - }; + return { ...res, ...traceAndBaggage, remixVersion }; }; }; } From 58bb2aca8142dc8463e8adcc3fa6eb009e5d74e9 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 20 Nov 2024 16:04:50 +0100 Subject: [PATCH 16/30] fix propagator!! --- packages/opentelemetry/src/propagator.ts | 7 ++ .../opentelemetry/src/utils/getTraceData.ts | 10 +- .../opentelemetry/test/propagator.test.ts | 110 +++++++++++++++++- 3 files changed, 121 insertions(+), 6 deletions(-) diff --git a/packages/opentelemetry/src/propagator.ts b/packages/opentelemetry/src/propagator.ts index 731c1e9cb0a7..f90847739cb1 100644 --- a/packages/opentelemetry/src/propagator.ts +++ b/packages/opentelemetry/src/propagator.ts @@ -1,4 +1,5 @@ import type { Baggage, Context, Span, TextMapGetter, TextMapSetter } from '@opentelemetry/api'; +import { SpanKind } from '@opentelemetry/api'; import { INVALID_TRACEID } from '@opentelemetry/api'; import { context } from '@opentelemetry/api'; import { propagation, trace } from '@opentelemetry/api'; @@ -32,6 +33,7 @@ import { DEBUG_BUILD } from './debug-build'; import { getScopesFromContext, setScopesOnContext } from './utils/contextData'; import { generateSpanContextForPropagationContext } from './utils/generateSpanContextForPropagationContext'; import { getSamplingDecision } from './utils/getSamplingDecision'; +import { getSpanKind } from './utils/getSpanKind'; import { setIsSetup } from './utils/setupCheck'; /** Get the Sentry propagation context from a span context. */ @@ -86,9 +88,14 @@ export class SentryPropagator extends W3CBaggagePropagator { const activeSpan = trace.getSpan(context); const url = activeSpan && getCurrentURL(activeSpan); + // We only want to check the URL against tracePropagationTargets for outgoing request spans + // In other cases, we always inject the trace data + const shouldCheckUrl = + activeSpan && (spanToJSON(activeSpan).op === 'http.client' || getSpanKind(activeSpan) === SpanKind.CLIENT); const tracePropagationTargets = getClient()?.getOptions()?.tracePropagationTargets; if ( + shouldCheckUrl && typeof url === 'string' && tracePropagationTargets && !this._shouldInjectTraceData(tracePropagationTargets, url) diff --git a/packages/opentelemetry/src/utils/getTraceData.ts b/packages/opentelemetry/src/utils/getTraceData.ts index 9f1b87ecda8e..924d096cc7f7 100644 --- a/packages/opentelemetry/src/utils/getTraceData.ts +++ b/packages/opentelemetry/src/utils/getTraceData.ts @@ -11,16 +11,16 @@ import { getContextFromScope } from './contextData'; export function getTraceData({ span }: { span?: Span } = {}): SerializedTraceData { const headersObject: Record = {}; + let ctx = api.context.active(); + if (span) { const { scope } = getCapturedScopesOnSpan(span); // fall back to current context if for whatever reason we can't find the one of the span - const ctx = (scope && getContextFromScope(scope)) || api.trace.setSpan(api.context.active(), span); - - api.propagation.inject(ctx, headersObject); - } else { - api.propagation.inject(api.context.active(), headersObject); + ctx = (scope && getContextFromScope(scope)) || api.trace.setSpan(api.context.active(), span); } + api.propagation.inject(ctx, headersObject); + if (!headersObject['sentry-trace']) { return {}; } diff --git a/packages/opentelemetry/test/propagator.test.ts b/packages/opentelemetry/test/propagator.test.ts index d3b9483674a1..016d4b5d04a0 100644 --- a/packages/opentelemetry/test/propagator.test.ts +++ b/packages/opentelemetry/test/propagator.test.ts @@ -1,5 +1,6 @@ import { ROOT_CONTEXT, + SpanKind, TraceFlags, context, defaultTextMapGetter, @@ -8,7 +9,8 @@ import { trace, } from '@opentelemetry/api'; import { suppressTracing } from '@opentelemetry/core'; -import { getCurrentScope, withScope } from '@sentry/core'; +import { ATTR_URL_FULL, SEMATTRS_HTTP_URL } from '@opentelemetry/semantic-conventions'; +import { SEMANTIC_ATTRIBUTE_SENTRY_OP, getCurrentScope, withScope } from '@sentry/core'; import { SENTRY_BAGGAGE_HEADER, SENTRY_SCOPES_CONTEXT_KEY, SENTRY_TRACE_HEADER } from '../src/constants'; import { SentryPropagator } from '../src/propagator'; @@ -28,6 +30,7 @@ describe('SentryPropagator', () => { release: '1.0.0', enableTracing: true, dsn: 'https://abc@domain/123', + tracePropagationTargets: ['/path2'], }); }); @@ -545,6 +548,111 @@ describe('SentryPropagator', () => { expect(carrier[SENTRY_TRACE_HEADER]).toBe(undefined); expect(carrier[SENTRY_BAGGAGE_HEADER]).toBe(undefined); }); + + it.each([ + [ + { + attributes: { [ATTR_URL_FULL]: 'https://domain.com/path2' }, + kind: SpanKind.CLIENT, + }, + true, + ], + [ + { + // eslint-disable-next-line deprecation/deprecation + attributes: { [SEMATTRS_HTTP_URL]: 'https://domain.com/path2' }, + kind: SpanKind.CLIENT, + }, + true, + ], + [ + { + attributes: { [ATTR_URL_FULL]: 'https://domain.com/path3' }, + kind: SpanKind.CLIENT, + }, + false, + ], + [ + { + // eslint-disable-next-line deprecation/deprecation + attributes: { [SEMATTRS_HTTP_URL]: 'https://domain.com/path3' }, + kind: SpanKind.CLIENT, + }, + false, + ], + [ + { + attributes: { [ATTR_URL_FULL]: 'https://domain.com/path2' }, + }, + true, + ], + [ + { + // eslint-disable-next-line deprecation/deprecation + attributes: { [SEMATTRS_HTTP_URL]: 'https://domain.com/path2' }, + }, + true, + ], + [ + { + attributes: { [ATTR_URL_FULL]: 'https://domain.com/path3' }, + }, + true, + ], + [ + { + // eslint-disable-next-line deprecation/deprecation + attributes: { [SEMATTRS_HTTP_URL]: 'https://domain.com/path3' }, + }, + true, + ], + [ + { + attributes: { [ATTR_URL_FULL]: 'https://domain.com/path3' }, + kind: SpanKind.SERVER, + }, + true, + ], + [ + { + // eslint-disable-next-line deprecation/deprecation + attributes: { [SEMATTRS_HTTP_URL]: 'https://domain.com/path3' }, + kind: SpanKind.SERVER, + }, + true, + ], + [ + { + attributes: { + [ATTR_URL_FULL]: 'https://domain.com/path3', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.client', + }, + }, + false, + ], + [ + { + attributes: { + // eslint-disable-next-line deprecation/deprecation + [SEMATTRS_HTTP_URL]: 'https://domain.com/path3', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.client', + }, + }, + false, + ], + ])('for %p injection is %p', (spanContext, shouldInject) => { + trace.getTracer('test').startActiveSpan('test', spanContext, span => { + propagator.inject(context.active(), carrier, defaultTextMapSetter); + + if (shouldInject) { + expect(carrier[SENTRY_TRACE_HEADER]).toBe(`${span.spanContext().traceId}-${span.spanContext().spanId}-1`); + expect(carrier[SENTRY_BAGGAGE_HEADER]).toMatch(/.+/); + } else { + expect(carrier[SENTRY_TRACE_HEADER]).toBe(undefined); + expect(carrier[SENTRY_BAGGAGE_HEADER]).toBe(undefined); + } + }); + }); }); describe('extract', () => { From ec4aa583eec34c56c3a442cf6f6d10f00fabe4f3 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 20 Nov 2024 16:59:59 +0100 Subject: [PATCH 17/30] Revert "fix propagator!!" This reverts commit 58bb2aca8142dc8463e8adcc3fa6eb009e5d74e9. --- packages/opentelemetry/src/propagator.ts | 7 -- .../opentelemetry/src/utils/getTraceData.ts | 10 +- .../opentelemetry/test/propagator.test.ts | 110 +----------------- 3 files changed, 6 insertions(+), 121 deletions(-) diff --git a/packages/opentelemetry/src/propagator.ts b/packages/opentelemetry/src/propagator.ts index f90847739cb1..731c1e9cb0a7 100644 --- a/packages/opentelemetry/src/propagator.ts +++ b/packages/opentelemetry/src/propagator.ts @@ -1,5 +1,4 @@ import type { Baggage, Context, Span, TextMapGetter, TextMapSetter } from '@opentelemetry/api'; -import { SpanKind } from '@opentelemetry/api'; import { INVALID_TRACEID } from '@opentelemetry/api'; import { context } from '@opentelemetry/api'; import { propagation, trace } from '@opentelemetry/api'; @@ -33,7 +32,6 @@ import { DEBUG_BUILD } from './debug-build'; import { getScopesFromContext, setScopesOnContext } from './utils/contextData'; import { generateSpanContextForPropagationContext } from './utils/generateSpanContextForPropagationContext'; import { getSamplingDecision } from './utils/getSamplingDecision'; -import { getSpanKind } from './utils/getSpanKind'; import { setIsSetup } from './utils/setupCheck'; /** Get the Sentry propagation context from a span context. */ @@ -88,14 +86,9 @@ export class SentryPropagator extends W3CBaggagePropagator { const activeSpan = trace.getSpan(context); const url = activeSpan && getCurrentURL(activeSpan); - // We only want to check the URL against tracePropagationTargets for outgoing request spans - // In other cases, we always inject the trace data - const shouldCheckUrl = - activeSpan && (spanToJSON(activeSpan).op === 'http.client' || getSpanKind(activeSpan) === SpanKind.CLIENT); const tracePropagationTargets = getClient()?.getOptions()?.tracePropagationTargets; if ( - shouldCheckUrl && typeof url === 'string' && tracePropagationTargets && !this._shouldInjectTraceData(tracePropagationTargets, url) diff --git a/packages/opentelemetry/src/utils/getTraceData.ts b/packages/opentelemetry/src/utils/getTraceData.ts index 924d096cc7f7..9f1b87ecda8e 100644 --- a/packages/opentelemetry/src/utils/getTraceData.ts +++ b/packages/opentelemetry/src/utils/getTraceData.ts @@ -11,15 +11,15 @@ import { getContextFromScope } from './contextData'; export function getTraceData({ span }: { span?: Span } = {}): SerializedTraceData { const headersObject: Record = {}; - let ctx = api.context.active(); - if (span) { const { scope } = getCapturedScopesOnSpan(span); // fall back to current context if for whatever reason we can't find the one of the span - ctx = (scope && getContextFromScope(scope)) || api.trace.setSpan(api.context.active(), span); - } + const ctx = (scope && getContextFromScope(scope)) || api.trace.setSpan(api.context.active(), span); - api.propagation.inject(ctx, headersObject); + api.propagation.inject(ctx, headersObject); + } else { + api.propagation.inject(api.context.active(), headersObject); + } if (!headersObject['sentry-trace']) { return {}; diff --git a/packages/opentelemetry/test/propagator.test.ts b/packages/opentelemetry/test/propagator.test.ts index 016d4b5d04a0..d3b9483674a1 100644 --- a/packages/opentelemetry/test/propagator.test.ts +++ b/packages/opentelemetry/test/propagator.test.ts @@ -1,6 +1,5 @@ import { ROOT_CONTEXT, - SpanKind, TraceFlags, context, defaultTextMapGetter, @@ -9,8 +8,7 @@ import { trace, } from '@opentelemetry/api'; import { suppressTracing } from '@opentelemetry/core'; -import { ATTR_URL_FULL, SEMATTRS_HTTP_URL } from '@opentelemetry/semantic-conventions'; -import { SEMANTIC_ATTRIBUTE_SENTRY_OP, getCurrentScope, withScope } from '@sentry/core'; +import { getCurrentScope, withScope } from '@sentry/core'; import { SENTRY_BAGGAGE_HEADER, SENTRY_SCOPES_CONTEXT_KEY, SENTRY_TRACE_HEADER } from '../src/constants'; import { SentryPropagator } from '../src/propagator'; @@ -30,7 +28,6 @@ describe('SentryPropagator', () => { release: '1.0.0', enableTracing: true, dsn: 'https://abc@domain/123', - tracePropagationTargets: ['/path2'], }); }); @@ -548,111 +545,6 @@ describe('SentryPropagator', () => { expect(carrier[SENTRY_TRACE_HEADER]).toBe(undefined); expect(carrier[SENTRY_BAGGAGE_HEADER]).toBe(undefined); }); - - it.each([ - [ - { - attributes: { [ATTR_URL_FULL]: 'https://domain.com/path2' }, - kind: SpanKind.CLIENT, - }, - true, - ], - [ - { - // eslint-disable-next-line deprecation/deprecation - attributes: { [SEMATTRS_HTTP_URL]: 'https://domain.com/path2' }, - kind: SpanKind.CLIENT, - }, - true, - ], - [ - { - attributes: { [ATTR_URL_FULL]: 'https://domain.com/path3' }, - kind: SpanKind.CLIENT, - }, - false, - ], - [ - { - // eslint-disable-next-line deprecation/deprecation - attributes: { [SEMATTRS_HTTP_URL]: 'https://domain.com/path3' }, - kind: SpanKind.CLIENT, - }, - false, - ], - [ - { - attributes: { [ATTR_URL_FULL]: 'https://domain.com/path2' }, - }, - true, - ], - [ - { - // eslint-disable-next-line deprecation/deprecation - attributes: { [SEMATTRS_HTTP_URL]: 'https://domain.com/path2' }, - }, - true, - ], - [ - { - attributes: { [ATTR_URL_FULL]: 'https://domain.com/path3' }, - }, - true, - ], - [ - { - // eslint-disable-next-line deprecation/deprecation - attributes: { [SEMATTRS_HTTP_URL]: 'https://domain.com/path3' }, - }, - true, - ], - [ - { - attributes: { [ATTR_URL_FULL]: 'https://domain.com/path3' }, - kind: SpanKind.SERVER, - }, - true, - ], - [ - { - // eslint-disable-next-line deprecation/deprecation - attributes: { [SEMATTRS_HTTP_URL]: 'https://domain.com/path3' }, - kind: SpanKind.SERVER, - }, - true, - ], - [ - { - attributes: { - [ATTR_URL_FULL]: 'https://domain.com/path3', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.client', - }, - }, - false, - ], - [ - { - attributes: { - // eslint-disable-next-line deprecation/deprecation - [SEMATTRS_HTTP_URL]: 'https://domain.com/path3', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.client', - }, - }, - false, - ], - ])('for %p injection is %p', (spanContext, shouldInject) => { - trace.getTracer('test').startActiveSpan('test', spanContext, span => { - propagator.inject(context.active(), carrier, defaultTextMapSetter); - - if (shouldInject) { - expect(carrier[SENTRY_TRACE_HEADER]).toBe(`${span.spanContext().traceId}-${span.spanContext().spanId}-1`); - expect(carrier[SENTRY_BAGGAGE_HEADER]).toMatch(/.+/); - } else { - expect(carrier[SENTRY_TRACE_HEADER]).toBe(undefined); - expect(carrier[SENTRY_BAGGAGE_HEADER]).toBe(undefined); - } - }); - }); }); describe('extract', () => { From ad674719ed8215d17609d53685254d0b7925a7ce Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 20 Nov 2024 17:00:35 +0100 Subject: [PATCH 18/30] WIP do not use custom getTraceData in OTEL??? --- packages/opentelemetry/src/asyncContextStrategy.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/opentelemetry/src/asyncContextStrategy.ts b/packages/opentelemetry/src/asyncContextStrategy.ts index 31da9479921f..c1e8cd290d34 100644 --- a/packages/opentelemetry/src/asyncContextStrategy.ts +++ b/packages/opentelemetry/src/asyncContextStrategy.ts @@ -12,7 +12,6 @@ import { startInactiveSpan, startSpan, startSpanManual, withActiveSpan } from '. import type { CurrentScopes } from './types'; import { getScopesFromContext } from './utils/contextData'; import { getActiveSpan } from './utils/getActiveSpan'; -import { getTraceData } from './utils/getTraceData'; import { suppressTracing } from './utils/suppressTracing'; /** @@ -104,7 +103,7 @@ export function setOpenTelemetryContextAsyncContextStrategy(): void { startInactiveSpan, getActiveSpan, suppressTracing, - getTraceData, + // getTraceData, // The types here don't fully align, because our own `Span` type is narrower // than the OTEL one - but this is OK for here, as we now we'll only have OTEL spans passed around withActiveSpan: withActiveSpan as typeof defaultWithActiveSpan, From 7b423e88e9eb2dc19a0518474f9ab7c9322bb62e Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 20 Nov 2024 17:17:43 +0100 Subject: [PATCH 19/30] ref: Use `getInjectionData` for otel-specific `getTraceData` --- .../opentelemetry/src/asyncContextStrategy.ts | 3 ++- packages/opentelemetry/src/propagator.ts | 5 +++- .../opentelemetry/src/utils/getTraceData.ts | 23 ++++++++----------- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/packages/opentelemetry/src/asyncContextStrategy.ts b/packages/opentelemetry/src/asyncContextStrategy.ts index c1e8cd290d34..31da9479921f 100644 --- a/packages/opentelemetry/src/asyncContextStrategy.ts +++ b/packages/opentelemetry/src/asyncContextStrategy.ts @@ -12,6 +12,7 @@ import { startInactiveSpan, startSpan, startSpanManual, withActiveSpan } from '. import type { CurrentScopes } from './types'; import { getScopesFromContext } from './utils/contextData'; import { getActiveSpan } from './utils/getActiveSpan'; +import { getTraceData } from './utils/getTraceData'; import { suppressTracing } from './utils/suppressTracing'; /** @@ -103,7 +104,7 @@ export function setOpenTelemetryContextAsyncContextStrategy(): void { startInactiveSpan, getActiveSpan, suppressTracing, - // getTraceData, + getTraceData, // The types here don't fully align, because our own `Span` type is narrower // than the OTEL one - but this is OK for here, as we now we'll only have OTEL spans passed around withActiveSpan: withActiveSpan as typeof defaultWithActiveSpan, diff --git a/packages/opentelemetry/src/propagator.ts b/packages/opentelemetry/src/propagator.ts index 731c1e9cb0a7..dc5659f1a7d3 100644 --- a/packages/opentelemetry/src/propagator.ts +++ b/packages/opentelemetry/src/propagator.ts @@ -186,7 +186,10 @@ export class SentryPropagator extends W3CBaggagePropagator { } } -function getInjectionData(context: Context): { +/** + * Get propagation injection data for the given context. + */ +export function getInjectionData(context: Context): { dynamicSamplingContext: Partial | undefined; traceId: string | undefined; spanId: string | undefined; diff --git a/packages/opentelemetry/src/utils/getTraceData.ts b/packages/opentelemetry/src/utils/getTraceData.ts index 9f1b87ecda8e..c4776871f503 100644 --- a/packages/opentelemetry/src/utils/getTraceData.ts +++ b/packages/opentelemetry/src/utils/getTraceData.ts @@ -1,7 +1,8 @@ import * as api from '@opentelemetry/api'; import { getCapturedScopesOnSpan } from '@sentry/core'; import type { SerializedTraceData, Span } from '@sentry/types'; -import { dropUndefinedKeys } from '@sentry/utils'; +import { dynamicSamplingContextToSentryBaggageHeader, generateSentryTraceHeader } from '@sentry/utils'; +import { getInjectionData } from '../propagator'; import { getContextFromScope } from './contextData'; /** @@ -9,24 +10,18 @@ import { getContextFromScope } from './contextData'; * @see `@sentry/core` version of `getTraceData` for more information */ export function getTraceData({ span }: { span?: Span } = {}): SerializedTraceData { - const headersObject: Record = {}; + let ctx = api.context.active(); if (span) { const { scope } = getCapturedScopesOnSpan(span); // fall back to current context if for whatever reason we can't find the one of the span - const ctx = (scope && getContextFromScope(scope)) || api.trace.setSpan(api.context.active(), span); - - api.propagation.inject(ctx, headersObject); - } else { - api.propagation.inject(api.context.active(), headersObject); + ctx = (scope && getContextFromScope(scope)) || api.trace.setSpan(api.context.active(), span); } - if (!headersObject['sentry-trace']) { - return {}; - } + const { traceId, spanId, sampled, dynamicSamplingContext } = getInjectionData(ctx); - return dropUndefinedKeys({ - 'sentry-trace': headersObject['sentry-trace'], - baggage: headersObject.baggage, - }); + return { + 'sentry-trace': generateSentryTraceHeader(traceId, spanId, sampled), + baggage: dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext), + }; } From e0263144479d19726530783b464c4096fed03e55 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Thu, 21 Nov 2024 09:16:04 +0100 Subject: [PATCH 20/30] fix imports after merge --- packages/core/src/baseclient.ts | 3 +-- packages/core/src/currentScopes.ts | 1 + packages/core/src/fetch.ts | 9 ++------- packages/core/src/tracing/dynamicSamplingContext.ts | 2 +- packages/core/src/utils/traceData.ts | 6 +++--- packages/node/src/sdk/client.ts | 5 +++-- packages/opentelemetry/src/propagator.ts | 8 +------- packages/opentelemetry/src/utils/getTraceData.ts | 10 ++++++++-- packages/remix/src/utils/instrumentServer.ts | 4 ++-- 9 files changed, 22 insertions(+), 26 deletions(-) diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index 8c2bd7959f18..bd60bcce4ae5 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -40,7 +40,7 @@ import { afterSetupIntegrations } from './integration'; import { setupIntegration, setupIntegrations } from './integration'; import type { Scope } from './scope'; import { updateSession } from './session'; -import { getDynamicSamplingContextFromClient } from './tracing/dynamicSamplingContext'; +import { getDynamicSamplingContextFromScope } from './tracing/dynamicSamplingContext'; import { createClientReportEnvelope } from './utils-hoist/clientreport'; import { dsnToString, makeDsn } from './utils-hoist/dsn'; import { addItemToEnvelope, createAttachmentEnvelopeItem } from './utils-hoist/envelope'; @@ -48,7 +48,6 @@ import { SentryError } from './utils-hoist/error'; import { isParameterizedString, isPlainObject, isPrimitive, isThenable } from './utils-hoist/is'; import { logger } from './utils-hoist/logger'; import { checkOrSetAlreadyCaught, uuid4 } from './utils-hoist/misc'; -import { dropUndefinedKeys } from './utils-hoist/object'; import { SyncPromise, rejectedSyncPromise, resolvedSyncPromise } from './utils-hoist/syncpromise'; import { parseSampleRate } from './utils/parseSampleRate'; import { prepareEvent } from './utils/prepareEvent'; diff --git a/packages/core/src/currentScopes.ts b/packages/core/src/currentScopes.ts index 05d019be04d0..825092fb2d5d 100644 --- a/packages/core/src/currentScopes.ts +++ b/packages/core/src/currentScopes.ts @@ -3,6 +3,7 @@ import type { Client } from '@sentry/types'; import { getAsyncContextStrategy } from './asyncContext'; import { getMainCarrier } from './carrier'; import { Scope as ScopeClass } from './scope'; +import { dropUndefinedKeys } from './utils-hoist/object'; import { getGlobalSingleton } from './utils-hoist/worldwide'; /** diff --git a/packages/core/src/fetch.ts b/packages/core/src/fetch.ts index 7f392a8e625f..f7a1a75cf391 100644 --- a/packages/core/src/fetch.ts +++ b/packages/core/src/fetch.ts @@ -1,15 +1,10 @@ import type { Client, HandlerDataFetch, Scope, Span, SpanOrigin } from '@sentry/types'; -import { getClient, getCurrentScope, getIsolationScope } from './currentScopes'; +import { getClient } from './currentScopes'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from './semanticAttributes'; import { SPAN_STATUS_ERROR, setHttpStatus, startInactiveSpan } from './tracing'; import { SentryNonRecordingSpan } from './tracing/sentryNonRecordingSpan'; -import { - BAGGAGE_HEADER_NAME, - SENTRY_BAGGAGE_KEY_PREFIX, - dynamicSamplingContextToSentryBaggageHeader, -} from './utils-hoist/baggage'; +import { BAGGAGE_HEADER_NAME, SENTRY_BAGGAGE_KEY_PREFIX } from './utils-hoist/baggage'; import { isInstanceOf } from './utils-hoist/is'; -import { generateSentryTraceHeader } from './utils-hoist/tracing'; import { parseUrl } from './utils-hoist/url'; import { hasTracingEnabled } from './utils/hasTracingEnabled'; import { getActiveSpan } from './utils/spanUtils'; diff --git a/packages/core/src/tracing/dynamicSamplingContext.ts b/packages/core/src/tracing/dynamicSamplingContext.ts index 06e2abd48386..a1bb008a2572 100644 --- a/packages/core/src/tracing/dynamicSamplingContext.ts +++ b/packages/core/src/tracing/dynamicSamplingContext.ts @@ -1,4 +1,4 @@ -import type { Client, DynamicSamplingContext, Span } from '@sentry/types'; +import type { Client, DynamicSamplingContext, Scope, Span } from '@sentry/types'; import { DEFAULT_ENVIRONMENT } from '../constants'; import { getClient } from '../currentScopes'; diff --git a/packages/core/src/utils/traceData.ts b/packages/core/src/utils/traceData.ts index 87c560123b2e..bd93948d7953 100644 --- a/packages/core/src/utils/traceData.ts +++ b/packages/core/src/utils/traceData.ts @@ -1,13 +1,13 @@ -import type { SerializedTraceData } from '@sentry/types'; +import type { Scope, SerializedTraceData, Span } from '@sentry/types'; import { getAsyncContextStrategy } from '../asyncContext'; import { getMainCarrier } from '../carrier'; import { getClient, getCurrentScope } from '../currentScopes'; import { isEnabled } from '../exports'; -import { getDynamicSamplingContextFromClient, getDynamicSamplingContextFromSpan } from '../tracing'; +import { getDynamicSamplingContextFromScope, getDynamicSamplingContextFromSpan } from '../tracing'; import { dynamicSamplingContextToSentryBaggageHeader } from '../utils-hoist/baggage'; import { logger } from '../utils-hoist/logger'; import { TRACEPARENT_REGEXP, generateSentryTraceHeader } from '../utils-hoist/tracing'; -import { getActiveSpan, getRootSpan, spanToTraceHeader } from './spanUtils'; +import { getActiveSpan, spanToTraceHeader } from './spanUtils'; /** * Extracts trace propagation data from the current span or from the client's scope (via transaction or propagation diff --git a/packages/node/src/sdk/client.ts b/packages/node/src/sdk/client.ts index 81a37cd1377b..b4730ac0f07c 100644 --- a/packages/node/src/sdk/client.ts +++ b/packages/node/src/sdk/client.ts @@ -3,8 +3,9 @@ import type { Tracer } from '@opentelemetry/api'; import { trace } from '@opentelemetry/api'; import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; import type { Scope, ServerRuntimeClientOptions } from '@sentry/core'; -import { SDK_VERSION, ServerRuntimeClient, applySdkMetadata } from '@sentry/core'; -import { logger } from '@sentry/core'; +import { SDK_VERSION, ServerRuntimeClient, applySdkMetadata, logger } from '@sentry/core'; +import { getTraceContextForScope } from '@sentry/opentelemetry'; +import type { DynamicSamplingContext, TraceContext } from '@sentry/types'; import { isMainThread, threadId } from 'worker_threads'; import { DEBUG_BUILD } from '../debug-build'; import type { NodeClientOptions } from '../types'; diff --git a/packages/opentelemetry/src/propagator.ts b/packages/opentelemetry/src/propagator.ts index 7f5c2279926c..6c4009888416 100644 --- a/packages/opentelemetry/src/propagator.ts +++ b/packages/opentelemetry/src/propagator.ts @@ -8,13 +8,7 @@ import type { continueTrace } from '@sentry/core'; import { getDynamicSamplingContextFromScope } from '@sentry/core'; import { getRootSpan } from '@sentry/core'; import { spanToJSON } from '@sentry/core'; -import { - getClient, - getCurrentScope, - getDynamicSamplingContextFromClient, - getDynamicSamplingContextFromSpan, - getIsolationScope, -} from '@sentry/core'; +import { getClient, getCurrentScope, getDynamicSamplingContextFromSpan, getIsolationScope } from '@sentry/core'; import { LRUMap, SENTRY_BAGGAGE_KEY_PREFIX, diff --git a/packages/opentelemetry/src/utils/getTraceData.ts b/packages/opentelemetry/src/utils/getTraceData.ts index 87da8eea96c1..c79fc2a6e957 100644 --- a/packages/opentelemetry/src/utils/getTraceData.ts +++ b/packages/opentelemetry/src/utils/getTraceData.ts @@ -1,6 +1,12 @@ import * as api from '@opentelemetry/api'; -import { dropUndefinedKeys } from '@sentry/core'; -import type { SerializedTraceData } from '@sentry/types'; +import { + dynamicSamplingContextToSentryBaggageHeader, + generateSentryTraceHeader, + getCapturedScopesOnSpan, +} from '@sentry/core'; +import type { SerializedTraceData, Span } from '@sentry/types'; +import { getInjectionData } from '../propagator'; +import { getContextFromScope } from './contextData'; /** * Otel-specific implementation of `getTraceData`. diff --git a/packages/remix/src/utils/instrumentServer.ts b/packages/remix/src/utils/instrumentServer.ts index e1c4b2ee5287..9db8fd925d1f 100644 --- a/packages/remix/src/utils/instrumentServer.ts +++ b/packages/remix/src/utils/instrumentServer.ts @@ -13,8 +13,8 @@ import { startSpan, withIsolationScope, } from '@sentry/core'; -import { dynamicSamplingContextToSentryBaggageHeader, fill, isNodeEnv, loadModule, logger } from '@sentry/core'; -import { continueTrace, getDynamicSamplingContextFromSpan } from '@sentry/opentelemetry'; +import { fill, isNodeEnv, loadModule, logger } from '@sentry/core'; +import { continueTrace } from '@sentry/opentelemetry'; import type { TransactionSource, WrappedFunction } from '@sentry/types'; import type { Span } from '@sentry/types'; From 7d5865e6be1e0c59ff4081f7e40582941ae26f94 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Thu, 21 Nov 2024 10:19:35 +0100 Subject: [PATCH 21/30] bump size-limit --- .size-limit.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index b28892aecb89..0026a3efa0e3 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -40,7 +40,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/index.js', import: createImport('init', 'browserTracingIntegration'), gzip: true, - limit: '36.5 KB', + limit: '37 KB', }, { name: '@sentry/browser (incl. Tracing, Replay)', @@ -187,7 +187,7 @@ module.exports = [ path: createCDNPath('bundle.tracing.min.js'), gzip: false, brotli: false, - limit: '113 KB', + limit: '115 KB', }, { name: 'CDN Bundle (incl. Tracing, Replay) - uncompressed', @@ -219,7 +219,7 @@ module.exports = [ import: createImport('init'), ignore: ['$app/stores'], gzip: true, - limit: '37 KB', + limit: '38 KB', }, // Node SDK (ESM) { From f8a5d82d5b6bde71b61a2784241a319efb014211 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Thu, 21 Nov 2024 15:44:48 +0100 Subject: [PATCH 22/30] fix linting --- packages/remix/src/utils/instrumentServer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/remix/src/utils/instrumentServer.ts b/packages/remix/src/utils/instrumentServer.ts index 0ef30bd01c5a..fbd874a12df9 100644 --- a/packages/remix/src/utils/instrumentServer.ts +++ b/packages/remix/src/utils/instrumentServer.ts @@ -14,8 +14,8 @@ import { winterCGRequestToRequestData, withIsolationScope, } from '@sentry/core'; -import { dynamicSamplingContextToSentryBaggageHeader, fill, isNodeEnv, loadModule, logger } from '@sentry/core'; -import { continueTrace, getDynamicSamplingContextFromSpan } from '@sentry/opentelemetry'; +import { fill, isNodeEnv, loadModule, logger } from '@sentry/core'; +import { continueTrace } from '@sentry/opentelemetry'; import type { RequestEventData, TransactionSource, WrappedFunction } from '@sentry/types'; import type { Span } from '@sentry/types'; From a2e4c8f7e510d89aa7a9c0c56198ac96ac3d3bd1 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 22 Nov 2024 11:08:26 +0100 Subject: [PATCH 23/30] small size improvement --- packages/browser/src/tracing/request.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/browser/src/tracing/request.ts b/packages/browser/src/tracing/request.ts index eb3e6858edd9..16ce21555570 100644 --- a/packages/browser/src/tracing/request.ts +++ b/packages/browser/src/tracing/request.ts @@ -9,7 +9,6 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SentryNonRecordingSpan, getActiveSpan, - getClient, getTraceData, hasTracingEnabled, instrumentFetchRequest, @@ -71,7 +70,9 @@ export interface RequestInstrumentationOptions { * * Default: true */ - traceXHR: boolean /** + traceXHR: boolean; + + /** * Flag to disable tracking of long-lived streams, like server-sent events (SSE) via fetch. * Do not enable this in case you have live streams or very long running streams. * @@ -79,7 +80,7 @@ export interface RequestInstrumentationOptions { * (https://github.com/getsentry/sentry-javascript/issues/13950) * * Default: false - */; + */ trackFetchStreamPerformance: boolean; /** @@ -396,7 +397,7 @@ export function xhrCallback( xhr.__sentry_xhr_span_id__ = span.spanContext().spanId; spans[xhr.__sentry_xhr_span_id__] = span; - if (xhr.setRequestHeader && shouldAttachHeaders(sentryXhrData.url) && getClient()) { + if (shouldAttachHeaders(sentryXhrData.url)) { addTracingHeadersToXhrRequest( xhr, // If performance is disabled (TWP) or there's no active root span (pageload/navigation/interaction), From d43ceae0ee9fcd9c39637e6fbd6f3fae6d19c980 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 22 Nov 2024 11:17:16 +0100 Subject: [PATCH 24/30] improve fetch bundle size slightly --- packages/core/src/fetch.ts | 55 ++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/packages/core/src/fetch.ts b/packages/core/src/fetch.ts index f7a1a75cf391..3a8f8fd3b768 100644 --- a/packages/core/src/fetch.ts +++ b/packages/core/src/fetch.ts @@ -1,5 +1,4 @@ import type { Client, HandlerDataFetch, Scope, Span, SpanOrigin } from '@sentry/types'; -import { getClient } from './currentScopes'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from './semanticAttributes'; import { SPAN_STATUS_ERROR, setHttpStatus, startInactiveSpan } from './tracing'; import { SentryNonRecordingSpan } from './tracing/sentryNonRecordingSpan'; @@ -79,7 +78,7 @@ export function instrumentFetchRequest( handlerData.fetchData.__span = span.spanContext().spanId; spans[span.spanContext().spanId] = span; - if (shouldAttachHeaders(handlerData.fetchData.url) && getClient()) { + if (shouldAttachHeaders(handlerData.fetchData.url)) { const request: string | Request = handlerData.args[0]; // In case the user hasn't set the second argument of a fetch call we default it to `{}`. @@ -90,8 +89,6 @@ export function instrumentFetchRequest( options.headers = _addTracingHeadersToFetchRequest( request, - undefined, - undefined, options, // If performance is disabled (TWP) or there's no active root span (pageload/navigation/interaction), // we do not want to use the span as base for the trace headers, @@ -103,20 +100,11 @@ export function instrumentFetchRequest( return span; } -/** - * Adds sentry-trace and baggage headers to the various forms of fetch headers. - * - * @deprecated This function will not be exported anymore in v9. - */ -export const addTracingHeadersToFetchRequest = _addTracingHeadersToFetchRequest; - /** * Adds sentry-trace and baggage headers to the various forms of fetch headers. */ function _addTracingHeadersToFetchRequest( - request: string | unknown, // unknown is actually type Request but we can't export DOM types from this package, - _client: Client | undefined, - _scope: Scope | undefined, + request: string | Request, fetchOptionsObj: { headers?: | { @@ -135,14 +123,12 @@ function _addTracingHeadersToFetchRequest( return fetchOptionsObj && (fetchOptionsObj.headers as PolymorphicRequestHeaders); } - const headers = - fetchOptionsObj.headers || - (typeof Request !== 'undefined' && isInstanceOf(request, Request) ? (request as Request).headers : undefined); + const headers = fetchOptionsObj.headers || (isRequest(request) ? request.headers : undefined); if (!headers) { return { ...traceHeaders }; - } else if (typeof Headers !== 'undefined' && isInstanceOf(headers, Headers)) { - const newHeaders = new Headers(headers as Headers); + } else if (isHeaders(headers)) { + const newHeaders = new Headers(headers); newHeaders.set('sentry-trace', sentryTrace); if (baggage) { @@ -160,7 +146,7 @@ function _addTracingHeadersToFetchRequest( } } - return newHeaders as PolymorphicRequestHeaders; + return newHeaders; } else if (Array.isArray(headers)) { const newHeaders = [ ...headers @@ -214,6 +200,27 @@ function _addTracingHeadersToFetchRequest( } } +/** + * Adds sentry-trace and baggage headers to the various forms of fetch headers. + * + * @deprecated This function will not be exported anymore in v9. + */ +export function addTracingHeadersToFetchRequest( + request: string | unknown, + _client: Client | undefined, + _scope: Scope | undefined, + fetchOptionsObj: { + headers?: + | { + [key: string]: string[] | string | undefined; + } + | PolymorphicRequestHeaders; + }, + span?: Span, +): PolymorphicRequestHeaders | undefined { + return _addTracingHeadersToFetchRequest(request as Request, fetchOptionsObj, span); +} + function getFullURL(url: string): string | undefined { try { const parsed = new URL(url); @@ -251,3 +258,11 @@ function stripBaggageHeaderOfSentryBaggageValues(baggageHeader: string): string .join(',') ); } + +function isRequest(request: unknown): request is Request { + return typeof Request !== 'undefined' && isInstanceOf(request, Request); +} + +function isHeaders(headers: unknown): headers is Headers { + return typeof Headers !== 'undefined' && isInstanceOf(headers, Headers); +} From aa3846a51c118b53a7dd91d83a02911b73e95b7e Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 22 Nov 2024 13:30:23 +0100 Subject: [PATCH 25/30] fixes for tests --- packages/core/src/fetch.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/core/src/fetch.ts b/packages/core/src/fetch.ts index 3a8f8fd3b768..bf65ecb20062 100644 --- a/packages/core/src/fetch.ts +++ b/packages/core/src/fetch.ts @@ -81,13 +81,9 @@ export function instrumentFetchRequest( if (shouldAttachHeaders(handlerData.fetchData.url)) { const request: string | Request = handlerData.args[0]; - // In case the user hasn't set the second argument of a fetch call we default it to `{}`. - handlerData.args[1] = handlerData.args[1] || {}; + const options: { [key: string]: unknown } = handlerData.args[1] || {}; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const options: { [key: string]: any } = handlerData.args[1]; - - options.headers = _addTracingHeadersToFetchRequest( + const headers = _addTracingHeadersToFetchRequest( request, options, // If performance is disabled (TWP) or there's no active root span (pageload/navigation/interaction), @@ -95,6 +91,9 @@ export function instrumentFetchRequest( // which means that the headers will be generated from the scope and the sampling decision is deferred hasTracingEnabled() && hasParent ? span : undefined, ); + if (headers) { + options.headers = headers; + } } return span; @@ -118,9 +117,9 @@ function _addTracingHeadersToFetchRequest( const sentryTrace = traceHeaders['sentry-trace']; const baggage = traceHeaders.baggage; - // Nothing to do, we just return the existing headers untouched + // Nothing to do, when we return undefined here, the original headers will be used if (!sentryTrace) { - return fetchOptionsObj && (fetchOptionsObj.headers as PolymorphicRequestHeaders); + return undefined; } const headers = fetchOptionsObj.headers || (isRequest(request) ? request.headers : undefined); From 9f0b1eaf265a62fdde6db7f6c93a25999f6f85e7 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 25 Nov 2024 10:05:00 +0100 Subject: [PATCH 26/30] update size limits :( --- .size-limit.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index 31b3703b9e61..2c3e9b35161e 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -124,7 +124,7 @@ module.exports = [ import: createImport('init', 'ErrorBoundary', 'reactRouterV6BrowserTracingIntegration'), ignore: ['react/jsx-runtime'], gzip: true, - limit: '39.5 KB', + limit: '40.5 KB', }, // Vue SDK (ESM) { @@ -132,7 +132,7 @@ module.exports = [ path: 'packages/vue/build/esm/index.js', import: createImport('init'), gzip: true, - limit: '28 KB', + limit: '29 KB', }, { name: '@sentry/vue (incl. Tracing)', From a20fc4377b2578cf5608a37d65b15bf535a1d838 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 26 Nov 2024 09:08:28 +0100 Subject: [PATCH 27/30] fix linting --- packages/core/src/fetch.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/fetch.ts b/packages/core/src/fetch.ts index 87cfe8977336..6a962f28616c 100644 --- a/packages/core/src/fetch.ts +++ b/packages/core/src/fetch.ts @@ -2,7 +2,7 @@ import type { Client, HandlerDataFetch, Scope, Span, SpanOrigin } from '@sentry/ import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from './semanticAttributes'; import { SPAN_STATUS_ERROR, setHttpStatus, startInactiveSpan } from './tracing'; import { SentryNonRecordingSpan } from './tracing/sentryNonRecordingSpan'; -import { BAGGAGE_HEADER_NAME, SENTRY_BAGGAGE_KEY_PREFIX } from './utils-hoist/baggage'; +import { SENTRY_BAGGAGE_KEY_PREFIX } from './utils-hoist/baggage'; import { isInstanceOf } from './utils-hoist/is'; import { parseUrl } from './utils-hoist/url'; import { hasTracingEnabled } from './utils/hasTracingEnabled'; From c3963be692b77dc165c4e0a3868cd6a7308a502c Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 26 Nov 2024 09:57:48 +0100 Subject: [PATCH 28/30] fix merge, oops --- packages/core/src/fetch.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/fetch.ts b/packages/core/src/fetch.ts index 6a962f28616c..f9b1ef91c4df 100644 --- a/packages/core/src/fetch.ts +++ b/packages/core/src/fetch.ts @@ -141,7 +141,7 @@ function _addTracingHeadersToFetchRequest( prevHeaderStrippedFromSentryBaggage ? `${prevHeaderStrippedFromSentryBaggage},${baggage}` : baggage, ); } else { - newHeaders.set('baggage', sentryBaggageHeader); + newHeaders.set('baggage', baggage); } } @@ -169,7 +169,7 @@ function _addTracingHeadersToFetchRequest( if (baggage) { // If there are multiple entries with the same key, the browser will merge the values into a single request header. // Its therefore safe to simply push a "baggage" entry, even though there might already be another baggage header. - newHeaders.push(['baggage', sentryBaggageHeader]); + newHeaders.push(['baggage', baggage]); } return newHeaders as PolymorphicRequestHeaders; From 57bfd0e81ae616d4b738b22efd0bccea3eb48b94 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 26 Nov 2024 10:33:08 +0100 Subject: [PATCH 29/30] fix fetch --- packages/core/src/fetch.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/fetch.ts b/packages/core/src/fetch.ts index f9b1ef91c4df..82b27288026d 100644 --- a/packages/core/src/fetch.ts +++ b/packages/core/src/fetch.ts @@ -81,7 +81,7 @@ export function instrumentFetchRequest( if (shouldAttachHeaders(handlerData.fetchData.url)) { const request: string | Request = handlerData.args[0]; - const options: { [key: string]: unknown } = handlerData.args[1] || {}; + const options: { [key: string]: unknown } = (handlerData.args[1] = handlerData.args[1] || {}); const headers = _addTracingHeadersToFetchRequest( request, From 80ecf6be31ec06397c2e4013ce69007924126551 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 26 Nov 2024 10:58:31 +0100 Subject: [PATCH 30/30] fix fetch order --- packages/core/src/fetch.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core/src/fetch.ts b/packages/core/src/fetch.ts index 82b27288026d..1dbb84423678 100644 --- a/packages/core/src/fetch.ts +++ b/packages/core/src/fetch.ts @@ -81,7 +81,7 @@ export function instrumentFetchRequest( if (shouldAttachHeaders(handlerData.fetchData.url)) { const request: string | Request = handlerData.args[0]; - const options: { [key: string]: unknown } = (handlerData.args[1] = handlerData.args[1] || {}); + const options: { [key: string]: unknown } = handlerData.args[1] || {}; const headers = _addTracingHeadersToFetchRequest( request, @@ -92,6 +92,8 @@ export function instrumentFetchRequest( hasTracingEnabled() && hasParent ? span : undefined, ); if (headers) { + // Ensure this is actually set, if no options have been passed previously + handlerData.args[1] = options; options.headers = headers; } }