Skip to content

Commit 0403078

Browse files
committed
feat(core): Add continueTrace method
1 parent af788e9 commit 0403078

File tree

9 files changed

+307
-5
lines changed

9 files changed

+307
-5
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: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { TransactionContext } from '@sentry/types';
2-
import { isThenable } from '@sentry/utils';
1+
import type { PolymorphicRequest, TransactionContext } from '@sentry/types';
2+
import { dropUndefinedKeys, isThenable, logger, tracingContextFromHeaders } from '@sentry/utils';
33

44
import type { Hub } from '../hub';
55
import { getCurrentHub } from '../hub';
@@ -203,6 +203,55 @@ 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+
request,
219+
}: {
220+
sentryTrace: Parameters<typeof tracingContextFromHeaders>[0];
221+
baggage: Parameters<typeof tracingContextFromHeaders>[1];
222+
request?: PolymorphicRequest;
223+
},
224+
callback: (transactionContext: Partial<TransactionContext>) => V,
225+
): V {
226+
const hub = getCurrentHub();
227+
const currentScope = hub.getScope();
228+
229+
if (request) {
230+
currentScope.setSDKProcessingMetadata({ request });
231+
}
232+
233+
const { traceparentData, dynamicSamplingContext, propagationContext } = tracingContextFromHeaders(
234+
sentryTrace,
235+
baggage,
236+
);
237+
238+
currentScope.setPropagationContext(propagationContext);
239+
240+
if (__DEBUG_BUILD__ && traceparentData) {
241+
logger.log(`[Tracing] Continuing trace ${traceparentData.traceId}.`);
242+
}
243+
244+
const transactionContext: Partial<TransactionContext> = {
245+
...traceparentData,
246+
metadata: dropUndefinedKeys({
247+
dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext,
248+
request,
249+
}),
250+
};
251+
252+
return callback(transactionContext);
253+
}
254+
206255
function createChildSpanOrTransaction(
207256
hub: Hub,
208257
parentSpan: Span | undefined,

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

Lines changed: 240 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import type { PolymorphicRequest } from '@sentry/types';
2+
13
import { addTracingExtensions, Hub, makeMain } from '../../../src';
2-
import { startSpan } from '../../../src/tracing';
4+
import { continueTrace, startSpan } from '../../../src/tracing';
35
import { getDefaultTestClientOptions, TestClient } from '../../mocks/client';
46

57
beforeAll(() => {
@@ -170,3 +172,240 @@ describe('startSpan', () => {
170172
});
171173
});
172174
});
175+
176+
describe('continueTrace', () => {
177+
beforeEach(() => {
178+
const options = getDefaultTestClientOptions({ tracesSampleRate: 0.0 });
179+
client = new TestClient(options);
180+
hub = new Hub(client);
181+
makeMain(hub);
182+
});
183+
184+
it('works without trace & baggage data', () => {
185+
const expectedContext = {
186+
metadata: {},
187+
};
188+
189+
const result = continueTrace({ sentryTrace: undefined, baggage: undefined }, ctx => {
190+
expect(ctx).toEqual(expectedContext);
191+
return ctx;
192+
});
193+
194+
expect(result).toEqual(expectedContext);
195+
196+
const scope = hub.getScope();
197+
198+
expect(scope.getPropagationContext()).toEqual({
199+
sampled: undefined,
200+
spanId: expect.any(String),
201+
traceId: expect.any(String),
202+
});
203+
204+
expect(scope['_sdkProcessingMetadata']).toEqual({});
205+
});
206+
207+
it('works with trace data', () => {
208+
const expectedContext = {
209+
metadata: {
210+
dynamicSamplingContext: {},
211+
},
212+
parentSampled: false,
213+
parentSpanId: '1121201211212012',
214+
traceId: '12312012123120121231201212312012',
215+
};
216+
217+
const result = continueTrace(
218+
{
219+
sentryTrace: '12312012123120121231201212312012-1121201211212012-0',
220+
baggage: undefined,
221+
},
222+
ctx => {
223+
expect(ctx).toEqual(expectedContext);
224+
return ctx;
225+
},
226+
);
227+
228+
expect(result).toEqual(expectedContext);
229+
230+
const scope = hub.getScope();
231+
232+
expect(scope.getPropagationContext()).toEqual({
233+
sampled: false,
234+
parentSpanId: '1121201211212012',
235+
spanId: expect.any(String),
236+
traceId: '12312012123120121231201212312012',
237+
});
238+
239+
expect(scope['_sdkProcessingMetadata']).toEqual({});
240+
});
241+
242+
it('works with trace & baggage data', () => {
243+
const expectedContext = {
244+
metadata: {
245+
dynamicSamplingContext: {
246+
environment: 'production',
247+
version: '1.0',
248+
},
249+
},
250+
parentSampled: true,
251+
parentSpanId: '1121201211212012',
252+
traceId: '12312012123120121231201212312012',
253+
};
254+
255+
const result = continueTrace(
256+
{
257+
sentryTrace: '12312012123120121231201212312012-1121201211212012-1',
258+
baggage: 'sentry-version=1.0,sentry-environment=production',
259+
},
260+
ctx => {
261+
expect(ctx).toEqual(expectedContext);
262+
return ctx;
263+
},
264+
);
265+
266+
expect(result).toEqual(expectedContext);
267+
268+
const scope = hub.getScope();
269+
270+
expect(scope.getPropagationContext()).toEqual({
271+
dsc: {
272+
environment: 'production',
273+
version: '1.0',
274+
},
275+
sampled: true,
276+
parentSpanId: '1121201211212012',
277+
spanId: expect.any(String),
278+
traceId: '12312012123120121231201212312012',
279+
});
280+
281+
expect(scope['_sdkProcessingMetadata']).toEqual({});
282+
});
283+
284+
it('works with trace & 3rd party baggage data', () => {
285+
const expectedContext = {
286+
metadata: {
287+
dynamicSamplingContext: {
288+
environment: 'production',
289+
version: '1.0',
290+
},
291+
},
292+
parentSampled: true,
293+
parentSpanId: '1121201211212012',
294+
traceId: '12312012123120121231201212312012',
295+
};
296+
297+
const result = continueTrace(
298+
{
299+
sentryTrace: '12312012123120121231201212312012-1121201211212012-1',
300+
baggage: 'sentry-version=1.0,sentry-environment=production,dogs=great,cats=boring',
301+
},
302+
ctx => {
303+
expect(ctx).toEqual(expectedContext);
304+
return ctx;
305+
},
306+
);
307+
308+
expect(result).toEqual(expectedContext);
309+
310+
const scope = hub.getScope();
311+
312+
expect(scope.getPropagationContext()).toEqual({
313+
dsc: {
314+
environment: 'production',
315+
version: '1.0',
316+
},
317+
sampled: true,
318+
parentSpanId: '1121201211212012',
319+
spanId: expect.any(String),
320+
traceId: '12312012123120121231201212312012',
321+
});
322+
323+
expect(scope['_sdkProcessingMetadata']).toEqual({});
324+
});
325+
326+
it('adds request data when set', () => {
327+
const request: PolymorphicRequest = {
328+
method: 'GET',
329+
url: 'https://example.com/test',
330+
};
331+
332+
const expectedContext = {
333+
metadata: {
334+
dynamicSamplingContext: {
335+
environment: 'production',
336+
version: '1.0',
337+
},
338+
request,
339+
},
340+
parentSampled: true,
341+
parentSpanId: '1121201211212012',
342+
traceId: '12312012123120121231201212312012',
343+
};
344+
345+
const result = continueTrace(
346+
{
347+
sentryTrace: '12312012123120121231201212312012-1121201211212012-1',
348+
baggage: 'sentry-version=1.0,sentry-environment=production',
349+
request,
350+
},
351+
ctx => {
352+
expect(ctx).toEqual(expectedContext);
353+
return ctx;
354+
},
355+
);
356+
357+
expect(result).toEqual(expectedContext);
358+
359+
const scope = hub.getScope();
360+
361+
expect(scope.getPropagationContext()).toEqual({
362+
dsc: {
363+
environment: 'production',
364+
version: '1.0',
365+
},
366+
sampled: true,
367+
parentSpanId: '1121201211212012',
368+
spanId: expect.any(String),
369+
traceId: '12312012123120121231201212312012',
370+
});
371+
372+
expect(scope['_sdkProcessingMetadata']).toEqual({ request });
373+
});
374+
375+
it('adds request data when set, even without trace & baggage data', () => {
376+
const request: PolymorphicRequest = {
377+
method: 'GET',
378+
url: 'https://example.com/test',
379+
};
380+
381+
const expectedContext = {
382+
metadata: {
383+
request,
384+
},
385+
};
386+
387+
const result = continueTrace(
388+
{
389+
sentryTrace: undefined,
390+
baggage: undefined,
391+
request,
392+
},
393+
ctx => {
394+
expect(ctx).toEqual(expectedContext);
395+
return ctx;
396+
},
397+
);
398+
399+
expect(result).toEqual(expectedContext);
400+
401+
const scope = hub.getScope();
402+
403+
expect(scope.getPropagationContext()).toEqual({
404+
sampled: undefined,
405+
spanId: expect.any(String),
406+
traceId: expect.any(String),
407+
});
408+
409+
expect(scope['_sdkProcessingMetadata']).toEqual({ request });
410+
});
411+
});

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)