From a0eb998a904c8f7852c0210e1998ff5a0c811435 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 3 Oct 2023 11:05:45 +0200 Subject: [PATCH] feat(core): Add `continueTrace` method --- packages/browser/src/exports.ts | 1 + packages/bun/src/index.ts | 1 + packages/core/src/tracing/index.ts | 12 +- packages/core/src/tracing/trace.ts | 44 +++++- packages/core/test/lib/tracing/trace.test.ts | 153 ++++++++++++++++++- packages/node/src/index.ts | 1 + packages/serverless/src/index.ts | 1 + packages/sveltekit/src/server/index.ts | 1 + packages/vercel-edge/src/index.ts | 1 + 9 files changed, 211 insertions(+), 4 deletions(-) diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts index f46b55f45214..c9e7e6e34c73 100644 --- a/packages/browser/src/exports.ts +++ b/packages/browser/src/exports.ts @@ -41,6 +41,7 @@ export { startSpan, startInactiveSpan, startSpanManual, + continueTrace, SDK_VERSION, setContext, setExtra, diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index d1c4a69f0ae5..b1bd9dac5553 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -59,6 +59,7 @@ export { startSpan, startInactiveSpan, startSpanManual, + continueTrace, } from '@sentry/core'; export type { SpanStatusType } from '@sentry/core'; export { autoDiscoverNodePerformanceMonitoringIntegrations } from '@sentry/node'; diff --git a/packages/core/src/tracing/index.ts b/packages/core/src/tracing/index.ts index 40d667c67ff0..2ace95aef323 100644 --- a/packages/core/src/tracing/index.ts +++ b/packages/core/src/tracing/index.ts @@ -7,8 +7,16 @@ export { extractTraceparentData, getActiveTransaction } from './utils'; // eslint-disable-next-line deprecation/deprecation export { SpanStatus } from './spanstatus'; export type { SpanStatusType } from './span'; -// eslint-disable-next-line deprecation/deprecation -export { trace, getActiveSpan, startSpan, startInactiveSpan, startActiveSpan, startSpanManual } from './trace'; +export { + trace, + getActiveSpan, + startSpan, + startInactiveSpan, + // eslint-disable-next-line deprecation/deprecation + startActiveSpan, + startSpanManual, + continueTrace, +} from './trace'; export { getDynamicSamplingContextFromClient } from './dynamicSamplingContext'; export { setMeasurement } from './measurement'; export { sampleTransaction } from './sampling'; diff --git a/packages/core/src/tracing/trace.ts b/packages/core/src/tracing/trace.ts index 8f9b226b4afb..4572eed79ee9 100644 --- a/packages/core/src/tracing/trace.ts +++ b/packages/core/src/tracing/trace.ts @@ -1,5 +1,5 @@ import type { TransactionContext } from '@sentry/types'; -import { isThenable } from '@sentry/utils'; +import { dropUndefinedKeys, isThenable, logger, tracingContextFromHeaders } from '@sentry/utils'; import type { Hub } from '../hub'; import { getCurrentHub } from '../hub'; @@ -203,6 +203,48 @@ export function getActiveSpan(): Span | undefined { return getCurrentHub().getScope().getSpan(); } +/** + * Continue a trace from `sentry-trace` and `baggage` values. + * These values can be obtained from incoming request headers, + * or in the browser from `` and `` HTML tags. + * + * It also takes an optional `request` option, which if provided will also be added to the scope & transaction metadata. + * The callback receives a transactionContext that may be used for `startTransaction` or `startSpan`. + */ +export function continueTrace( + { + sentryTrace, + baggage, + }: { + sentryTrace: Parameters[0]; + baggage: Parameters[1]; + }, + callback: (transactionContext: Partial) => V, +): V { + const hub = getCurrentHub(); + const currentScope = hub.getScope(); + + const { traceparentData, dynamicSamplingContext, propagationContext } = tracingContextFromHeaders( + sentryTrace, + baggage, + ); + + currentScope.setPropagationContext(propagationContext); + + if (__DEBUG_BUILD__ && traceparentData) { + logger.log(`[Tracing] Continuing trace ${traceparentData.traceId}.`); + } + + const transactionContext: Partial = { + ...traceparentData, + metadata: dropUndefinedKeys({ + dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, + }), + }; + + return callback(transactionContext); +} + function createChildSpanOrTransaction( hub: Hub, parentSpan: Span | undefined, diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts index 2480d449a9d9..144ec35f1f0e 100644 --- a/packages/core/test/lib/tracing/trace.test.ts +++ b/packages/core/test/lib/tracing/trace.test.ts @@ -1,5 +1,5 @@ import { addTracingExtensions, Hub, makeMain } from '../../../src'; -import { startSpan } from '../../../src/tracing'; +import { continueTrace, startSpan } from '../../../src/tracing'; import { getDefaultTestClientOptions, TestClient } from '../../mocks/client'; beforeAll(() => { @@ -170,3 +170,154 @@ describe('startSpan', () => { }); }); }); + +describe('continueTrace', () => { + beforeEach(() => { + const options = getDefaultTestClientOptions({ tracesSampleRate: 0.0 }); + client = new TestClient(options); + hub = new Hub(client); + makeMain(hub); + }); + + it('works without trace & baggage data', () => { + const expectedContext = { + metadata: {}, + }; + + const result = continueTrace({ sentryTrace: undefined, baggage: undefined }, ctx => { + expect(ctx).toEqual(expectedContext); + return ctx; + }); + + expect(result).toEqual(expectedContext); + + const scope = hub.getScope(); + + expect(scope.getPropagationContext()).toEqual({ + sampled: undefined, + spanId: expect.any(String), + traceId: expect.any(String), + }); + + expect(scope['_sdkProcessingMetadata']).toEqual({}); + }); + + it('works with trace data', () => { + const expectedContext = { + metadata: { + dynamicSamplingContext: {}, + }, + parentSampled: false, + parentSpanId: '1121201211212012', + traceId: '12312012123120121231201212312012', + }; + + const result = continueTrace( + { + sentryTrace: '12312012123120121231201212312012-1121201211212012-0', + baggage: undefined, + }, + ctx => { + expect(ctx).toEqual(expectedContext); + return ctx; + }, + ); + + expect(result).toEqual(expectedContext); + + const scope = hub.getScope(); + + expect(scope.getPropagationContext()).toEqual({ + sampled: false, + parentSpanId: '1121201211212012', + spanId: expect.any(String), + traceId: '12312012123120121231201212312012', + }); + + expect(scope['_sdkProcessingMetadata']).toEqual({}); + }); + + it('works with trace & baggage data', () => { + const expectedContext = { + metadata: { + dynamicSamplingContext: { + environment: 'production', + version: '1.0', + }, + }, + parentSampled: true, + parentSpanId: '1121201211212012', + traceId: '12312012123120121231201212312012', + }; + + const result = continueTrace( + { + sentryTrace: '12312012123120121231201212312012-1121201211212012-1', + baggage: 'sentry-version=1.0,sentry-environment=production', + }, + ctx => { + expect(ctx).toEqual(expectedContext); + return ctx; + }, + ); + + expect(result).toEqual(expectedContext); + + const scope = hub.getScope(); + + expect(scope.getPropagationContext()).toEqual({ + dsc: { + environment: 'production', + version: '1.0', + }, + sampled: true, + parentSpanId: '1121201211212012', + spanId: expect.any(String), + traceId: '12312012123120121231201212312012', + }); + + expect(scope['_sdkProcessingMetadata']).toEqual({}); + }); + + it('works with trace & 3rd party baggage data', () => { + const expectedContext = { + metadata: { + dynamicSamplingContext: { + environment: 'production', + version: '1.0', + }, + }, + parentSampled: true, + parentSpanId: '1121201211212012', + traceId: '12312012123120121231201212312012', + }; + + const result = continueTrace( + { + sentryTrace: '12312012123120121231201212312012-1121201211212012-1', + baggage: 'sentry-version=1.0,sentry-environment=production,dogs=great,cats=boring', + }, + ctx => { + expect(ctx).toEqual(expectedContext); + return ctx; + }, + ); + + expect(result).toEqual(expectedContext); + + const scope = hub.getScope(); + + expect(scope.getPropagationContext()).toEqual({ + dsc: { + environment: 'production', + version: '1.0', + }, + sampled: true, + parentSpanId: '1121201211212012', + spanId: expect.any(String), + traceId: '12312012123120121231201212312012', + }); + + expect(scope['_sdkProcessingMetadata']).toEqual({}); + }); +}); diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 503f2749ea29..5fede4a51074 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -61,6 +61,7 @@ export { startActiveSpan, startInactiveSpan, startSpanManual, + continueTrace, } from '@sentry/core'; export type { SpanStatusType } from '@sentry/core'; export { autoDiscoverNodePerformanceMonitoringIntegrations } from './tracing'; diff --git a/packages/serverless/src/index.ts b/packages/serverless/src/index.ts index a17d0463202d..e0490df7e0d2 100644 --- a/packages/serverless/src/index.ts +++ b/packages/serverless/src/index.ts @@ -56,4 +56,5 @@ export { startActiveSpan, startInactiveSpan, startSpanManual, + continueTrace, } from '@sentry/node'; diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index 90c651a41175..6f02af4669fa 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -51,6 +51,7 @@ export { startActiveSpan, startInactiveSpan, startSpanManual, + continueTrace, } from '@sentry/node'; // We can still leave this for the carrier init and type exports diff --git a/packages/vercel-edge/src/index.ts b/packages/vercel-edge/src/index.ts index cd596269a36f..43aa34b56557 100644 --- a/packages/vercel-edge/src/index.ts +++ b/packages/vercel-edge/src/index.ts @@ -58,6 +58,7 @@ export { startSpan, startInactiveSpan, startSpanManual, + continueTrace, } from '@sentry/core'; export type { SpanStatusType } from '@sentry/core';