Skip to content

Commit dee4f69

Browse files
authored
feat(core): Add continueTrace method (#9164)
This adds a new `continueTrace` method which can be used to continue a trace from headers. This method has the following signature: ```ts function continueTrace<V>( { sentryTrace, baggage, }: { sentryTrace: Parameters<typeof tracingContextFromHeaders>[0]; baggage: Parameters<typeof tracingContextFromHeaders>[1]; }, callback: (transactionContext: Partial<TransactionContext>) => V, ): V ```
1 parent abd8b4d commit dee4f69

File tree

9 files changed

+211
-4
lines changed

9 files changed

+211
-4
lines changed

packages/browser/src/exports.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export {
4141
startSpan,
4242
startInactiveSpan,
4343
startSpanManual,
44+
continueTrace,
4445
SDK_VERSION,
4546
setContext,
4647
setExtra,

packages/bun/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export {
5959
startSpan,
6060
startInactiveSpan,
6161
startSpanManual,
62+
continueTrace,
6263
} from '@sentry/core';
6364
export type { SpanStatusType } from '@sentry/core';
6465
export { autoDiscoverNodePerformanceMonitoringIntegrations } from '@sentry/node';

packages/core/src/tracing/index.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,16 @@ export { extractTraceparentData, getActiveTransaction } from './utils';
77
// eslint-disable-next-line deprecation/deprecation
88
export { SpanStatus } from './spanstatus';
99
export type { SpanStatusType } from './span';
10-
// eslint-disable-next-line deprecation/deprecation
11-
export { trace, getActiveSpan, startSpan, startInactiveSpan, startActiveSpan, startSpanManual } from './trace';
10+
export {
11+
trace,
12+
getActiveSpan,
13+
startSpan,
14+
startInactiveSpan,
15+
// eslint-disable-next-line deprecation/deprecation
16+
startActiveSpan,
17+
startSpanManual,
18+
continueTrace,
19+
} from './trace';
1220
export { getDynamicSamplingContextFromClient } from './dynamicSamplingContext';
1321
export { setMeasurement } from './measurement';
1422
export { sampleTransaction } from './sampling';

packages/core/src/tracing/trace.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { TransactionContext } from '@sentry/types';
2-
import { isThenable } from '@sentry/utils';
2+
import { dropUndefinedKeys, isThenable, logger, tracingContextFromHeaders } from '@sentry/utils';
33

44
import type { Hub } from '../hub';
55
import { getCurrentHub } from '../hub';
@@ -203,6 +203,48 @@ export function getActiveSpan(): Span | undefined {
203203
return getCurrentHub().getScope().getSpan();
204204
}
205205

206+
/**
207+
* Continue a trace from `sentry-trace` and `baggage` values.
208+
* These values can be obtained from incoming request headers,
209+
* or in the browser from `<meta name="sentry-trace">` and `<meta name="baggage">` HTML tags.
210+
*
211+
* It also takes an optional `request` option, which if provided will also be added to the scope & transaction metadata.
212+
* The callback receives a transactionContext that may be used for `startTransaction` or `startSpan`.
213+
*/
214+
export function continueTrace<V>(
215+
{
216+
sentryTrace,
217+
baggage,
218+
}: {
219+
sentryTrace: Parameters<typeof tracingContextFromHeaders>[0];
220+
baggage: Parameters<typeof tracingContextFromHeaders>[1];
221+
},
222+
callback: (transactionContext: Partial<TransactionContext>) => V,
223+
): V {
224+
const hub = getCurrentHub();
225+
const currentScope = hub.getScope();
226+
227+
const { traceparentData, dynamicSamplingContext, propagationContext } = tracingContextFromHeaders(
228+
sentryTrace,
229+
baggage,
230+
);
231+
232+
currentScope.setPropagationContext(propagationContext);
233+
234+
if (__DEBUG_BUILD__ && traceparentData) {
235+
logger.log(`[Tracing] Continuing trace ${traceparentData.traceId}.`);
236+
}
237+
238+
const transactionContext: Partial<TransactionContext> = {
239+
...traceparentData,
240+
metadata: dropUndefinedKeys({
241+
dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext,
242+
}),
243+
};
244+
245+
return callback(transactionContext);
246+
}
247+
206248
function createChildSpanOrTransaction(
207249
hub: Hub,
208250
parentSpan: Span | undefined,

packages/core/test/lib/tracing/trace.test.ts

Lines changed: 152 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { addTracingExtensions, Hub, makeMain } from '../../../src';
2-
import { startSpan } from '../../../src/tracing';
2+
import { continueTrace, startSpan } from '../../../src/tracing';
33
import { getDefaultTestClientOptions, TestClient } from '../../mocks/client';
44

55
beforeAll(() => {
@@ -170,3 +170,154 @@ describe('startSpan', () => {
170170
});
171171
});
172172
});
173+
174+
describe('continueTrace', () => {
175+
beforeEach(() => {
176+
const options = getDefaultTestClientOptions({ tracesSampleRate: 0.0 });
177+
client = new TestClient(options);
178+
hub = new Hub(client);
179+
makeMain(hub);
180+
});
181+
182+
it('works without trace & baggage data', () => {
183+
const expectedContext = {
184+
metadata: {},
185+
};
186+
187+
const result = continueTrace({ sentryTrace: undefined, baggage: undefined }, ctx => {
188+
expect(ctx).toEqual(expectedContext);
189+
return ctx;
190+
});
191+
192+
expect(result).toEqual(expectedContext);
193+
194+
const scope = hub.getScope();
195+
196+
expect(scope.getPropagationContext()).toEqual({
197+
sampled: undefined,
198+
spanId: expect.any(String),
199+
traceId: expect.any(String),
200+
});
201+
202+
expect(scope['_sdkProcessingMetadata']).toEqual({});
203+
});
204+
205+
it('works with trace data', () => {
206+
const expectedContext = {
207+
metadata: {
208+
dynamicSamplingContext: {},
209+
},
210+
parentSampled: false,
211+
parentSpanId: '1121201211212012',
212+
traceId: '12312012123120121231201212312012',
213+
};
214+
215+
const result = continueTrace(
216+
{
217+
sentryTrace: '12312012123120121231201212312012-1121201211212012-0',
218+
baggage: undefined,
219+
},
220+
ctx => {
221+
expect(ctx).toEqual(expectedContext);
222+
return ctx;
223+
},
224+
);
225+
226+
expect(result).toEqual(expectedContext);
227+
228+
const scope = hub.getScope();
229+
230+
expect(scope.getPropagationContext()).toEqual({
231+
sampled: false,
232+
parentSpanId: '1121201211212012',
233+
spanId: expect.any(String),
234+
traceId: '12312012123120121231201212312012',
235+
});
236+
237+
expect(scope['_sdkProcessingMetadata']).toEqual({});
238+
});
239+
240+
it('works with trace & baggage data', () => {
241+
const expectedContext = {
242+
metadata: {
243+
dynamicSamplingContext: {
244+
environment: 'production',
245+
version: '1.0',
246+
},
247+
},
248+
parentSampled: true,
249+
parentSpanId: '1121201211212012',
250+
traceId: '12312012123120121231201212312012',
251+
};
252+
253+
const result = continueTrace(
254+
{
255+
sentryTrace: '12312012123120121231201212312012-1121201211212012-1',
256+
baggage: 'sentry-version=1.0,sentry-environment=production',
257+
},
258+
ctx => {
259+
expect(ctx).toEqual(expectedContext);
260+
return ctx;
261+
},
262+
);
263+
264+
expect(result).toEqual(expectedContext);
265+
266+
const scope = hub.getScope();
267+
268+
expect(scope.getPropagationContext()).toEqual({
269+
dsc: {
270+
environment: 'production',
271+
version: '1.0',
272+
},
273+
sampled: true,
274+
parentSpanId: '1121201211212012',
275+
spanId: expect.any(String),
276+
traceId: '12312012123120121231201212312012',
277+
});
278+
279+
expect(scope['_sdkProcessingMetadata']).toEqual({});
280+
});
281+
282+
it('works with trace & 3rd party baggage data', () => {
283+
const expectedContext = {
284+
metadata: {
285+
dynamicSamplingContext: {
286+
environment: 'production',
287+
version: '1.0',
288+
},
289+
},
290+
parentSampled: true,
291+
parentSpanId: '1121201211212012',
292+
traceId: '12312012123120121231201212312012',
293+
};
294+
295+
const result = continueTrace(
296+
{
297+
sentryTrace: '12312012123120121231201212312012-1121201211212012-1',
298+
baggage: 'sentry-version=1.0,sentry-environment=production,dogs=great,cats=boring',
299+
},
300+
ctx => {
301+
expect(ctx).toEqual(expectedContext);
302+
return ctx;
303+
},
304+
);
305+
306+
expect(result).toEqual(expectedContext);
307+
308+
const scope = hub.getScope();
309+
310+
expect(scope.getPropagationContext()).toEqual({
311+
dsc: {
312+
environment: 'production',
313+
version: '1.0',
314+
},
315+
sampled: true,
316+
parentSpanId: '1121201211212012',
317+
spanId: expect.any(String),
318+
traceId: '12312012123120121231201212312012',
319+
});
320+
321+
expect(scope['_sdkProcessingMetadata']).toEqual({});
322+
});
323+
});

packages/node/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export {
6161
startActiveSpan,
6262
startInactiveSpan,
6363
startSpanManual,
64+
continueTrace,
6465
} from '@sentry/core';
6566
export type { SpanStatusType } from '@sentry/core';
6667
export { autoDiscoverNodePerformanceMonitoringIntegrations } from './tracing';

packages/serverless/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,5 @@ export {
5656
startActiveSpan,
5757
startInactiveSpan,
5858
startSpanManual,
59+
continueTrace,
5960
} from '@sentry/node';

packages/sveltekit/src/server/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export {
5151
startActiveSpan,
5252
startInactiveSpan,
5353
startSpanManual,
54+
continueTrace,
5455
} from '@sentry/node';
5556

5657
// We can still leave this for the carrier init and type exports

packages/vercel-edge/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export {
5858
startSpan,
5959
startInactiveSpan,
6060
startSpanManual,
61+
continueTrace,
6162
} from '@sentry/core';
6263
export type { SpanStatusType } from '@sentry/core';
6364

0 commit comments

Comments
 (0)