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';