Skip to content

Commit 9006287

Browse files
AbhiPrasadmydea
andauthored
ref(tracing): Add necessary helpers for using propagation context on outgoing headers (#8434)
This PR adds: 1. A getter for `PropagationContext` on the scope 2. `generateSentryTraceHeader`, which will be used to dynamically create `sentry-trace` headers, regardless of if there is a span or not 3. `getDynamicSamplingContextFromClient`, which is used to generate dynamic sampling context from a client directly (instead of having to go through a transaction) This PR also updates `extractTraceparentData` and `dynamicSamplingContextToSentryBaggageHeader` to be more liberal about the values it takes so we can better accommodate the new flows we are going to add. Co-authored-by: Francesco Novy <[email protected]>
1 parent 3625fb1 commit 9006287

File tree

18 files changed

+120
-63
lines changed

18 files changed

+120
-63
lines changed

packages/core/src/baseclient.ts

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import {
3030
addItemToEnvelope,
3131
checkOrSetAlreadyCaught,
3232
createAttachmentEnvelopeItem,
33-
dropUndefinedKeys,
3433
isPlainObject,
3534
isPrimitive,
3635
isThenable,
@@ -43,12 +42,12 @@ import {
4342
} from '@sentry/utils';
4443

4544
import { getEnvelopeEndpointWithUrlEncodedAuth } from './api';
46-
import { DEFAULT_ENVIRONMENT } from './constants';
4745
import { createEventEnvelope, createSessionEnvelope } from './envelope';
4846
import type { IntegrationIndex } from './integration';
4947
import { setupIntegration, setupIntegrations } from './integration';
5048
import type { Scope } from './scope';
5149
import { updateSession } from './session';
50+
import { getDynamicSamplingContextFromClient } from './tracing/dynamicSamplingContext';
5251
import { prepareEvent } from './utils/prepareEvent';
5352

5453
const ALREADY_SEEN_ERROR = "Not capturing exception because it's already been captured.";
@@ -531,20 +530,7 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
531530
...evt.contexts,
532531
};
533532

534-
const { publicKey: public_key } = this.getDsn() || {};
535-
const { segment: user_segment } = (scope && scope.getUser()) || {};
536-
537-
let dynamicSamplingContext = dsc;
538-
if (!dsc) {
539-
dynamicSamplingContext = dropUndefinedKeys({
540-
environment: options.environment || DEFAULT_ENVIRONMENT,
541-
release: options.release,
542-
user_segment,
543-
public_key,
544-
trace_id,
545-
});
546-
this.emit && this.emit('createDsc', dynamicSamplingContext);
547-
}
533+
const dynamicSamplingContext = dsc ? dsc : getDynamicSamplingContextFromClient(trace_id, this, scope);
548534

549535
evt.sdkProcessingMetadata = {
550536
dynamicSamplingContext,

packages/core/src/scope.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -533,13 +533,20 @@ export class Scope implements ScopeInterface {
533533
}
534534

535535
/**
536-
* @inheritdoc
536+
* @inheritDoc
537537
*/
538538
public setPropagationContext(context: PropagationContext): this {
539539
this._propagationContext = context;
540540
return this;
541541
}
542542

543+
/**
544+
* @inheritDoc
545+
*/
546+
public getPropagationContext(): PropagationContext {
547+
return this._propagationContext;
548+
}
549+
543550
/**
544551
* This will be called after {@link applyToEvent} is finished.
545552
*/
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import type { Client, DynamicSamplingContext, Scope } from '@sentry/types';
2+
import { dropUndefinedKeys } from '@sentry/utils';
3+
4+
import { DEFAULT_ENVIRONMENT } from '../constants';
5+
6+
/**
7+
* Creates a dynamic sampling context from a client.
8+
*
9+
* Dispatchs the `createDsc` lifecycle hook as a side effect.
10+
*/
11+
export function getDynamicSamplingContextFromClient(
12+
trace_id: string,
13+
client: Client,
14+
scope?: Scope,
15+
): DynamicSamplingContext {
16+
const options = client.getOptions();
17+
18+
const { publicKey: public_key } = client.getDsn() || {};
19+
const { segment: user_segment } = (scope && scope.getUser()) || {};
20+
21+
const dsc = dropUndefinedKeys({
22+
environment: options.environment || DEFAULT_ENVIRONMENT,
23+
release: options.release,
24+
user_segment,
25+
public_key,
26+
trace_id,
27+
}) as DynamicSamplingContext;
28+
29+
client.emit && client.emit('createDsc', dsc);
30+
31+
return dsc;
32+
}

packages/core/src/tracing/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ export { extractTraceparentData, getActiveTransaction } from './utils';
77
export { SpanStatus } from './spanstatus';
88
export type { SpanStatusType } from './span';
99
export { trace } from './trace';
10+
export { getDynamicSamplingContextFromClient } from './dynamicSamplingContext';

packages/core/src/tracing/span.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type {
77
TraceContext,
88
Transaction,
99
} from '@sentry/types';
10-
import { dropUndefinedKeys, logger, timestampInSeconds, uuid4 } from '@sentry/utils';
10+
import { dropUndefinedKeys, generateSentryTraceHeader, logger, timestampInSeconds, uuid4 } from '@sentry/utils';
1111

1212
/**
1313
* Keeps track of finished spans for a given transaction
@@ -265,11 +265,7 @@ export class Span implements SpanInterface {
265265
* @inheritDoc
266266
*/
267267
public toTraceparent(): string {
268-
let sampledString = '';
269-
if (this.sampled !== undefined) {
270-
sampledString = this.sampled ? '-1' : '-0';
271-
}
272-
return `${this.traceId}-${this.spanId}${sampledString}`;
268+
return generateSentryTraceHeader(this.traceId, this.spanId, this.sampled);
273269
}
274270

275271
/**

packages/core/src/tracing/transaction.ts

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ import type {
1111
} from '@sentry/types';
1212
import { dropUndefinedKeys, logger } from '@sentry/utils';
1313

14-
import { DEFAULT_ENVIRONMENT } from '../constants';
1514
import type { Hub } from '../hub';
1615
import { getCurrentHub } from '../hub';
16+
import { getDynamicSamplingContextFromClient } from './dynamicSamplingContext';
1717
import { Span as SpanClass, SpanRecorder } from './span';
1818

1919
/** JSDoc */
@@ -245,33 +245,24 @@ export class Transaction extends SpanClass implements TransactionInterface {
245245
return this._frozenDynamicSamplingContext;
246246
}
247247

248-
const hub: Hub = this._hub || getCurrentHub();
249-
const client = hub && hub.getClient();
248+
const hub = this._hub || getCurrentHub();
249+
const client = hub.getClient();
250250

251251
if (!client) return {};
252252

253-
const { environment, release } = client.getOptions() || {};
254-
const { publicKey: public_key } = client.getDsn() || {};
253+
const scope = hub.getScope();
254+
const dsc = getDynamicSamplingContextFromClient(this.traceId, client, scope);
255255

256256
const maybeSampleRate = this.metadata.sampleRate;
257-
const sample_rate = maybeSampleRate !== undefined ? maybeSampleRate.toString() : undefined;
258-
259-
const { segment: user_segment } = hub.getScope().getUser() || {};
260-
261-
const source = this.metadata.source;
257+
if (maybeSampleRate !== undefined) {
258+
dsc.sample_rate = `${maybeSampleRate}`;
259+
}
262260

263261
// We don't want to have a transaction name in the DSC if the source is "url" because URLs might contain PII
264-
const transaction = source && source !== 'url' ? this.name : undefined;
265-
266-
const dsc = dropUndefinedKeys({
267-
environment: environment || DEFAULT_ENVIRONMENT,
268-
release,
269-
transaction,
270-
user_segment,
271-
public_key,
272-
trace_id: this.traceId,
273-
sample_rate,
274-
});
262+
const source = this.metadata.source;
263+
if (source && source !== 'url') {
264+
dsc.transaction = this.name;
265+
}
275266

276267
// Uncomment if we want to make DSC immutable
277268
// this._frozenDynamicSamplingContext = dsc;

packages/hub/test/scope.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,22 @@ describe('Scope', () => {
152152
expect((scope as any)._sdkProcessingMetadata.dogs).toEqual('are great!');
153153
});
154154

155+
test('set and get propagation context', () => {
156+
const scope = new Scope();
157+
const oldPropagationContext = scope.getPropagationContext();
158+
scope.setPropagationContext({
159+
traceId: '86f39e84263a4de99c326acab3bfe3bd',
160+
spanId: '6e0c63257de34c92',
161+
sampled: true,
162+
});
163+
expect(scope.getPropagationContext()).not.toEqual(oldPropagationContext);
164+
expect(scope.getPropagationContext()).toEqual({
165+
traceId: '86f39e84263a4de99c326acab3bfe3bd',
166+
spanId: '6e0c63257de34c92',
167+
sampled: true,
168+
});
169+
});
170+
155171
test('chaining', () => {
156172
const scope = new Scope();
157173
scope.setLevel('fatal').setUser({ id: '1' });

packages/node-integration-tests/suites/express/sentry-trace/baggage-header-assign/test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ test('Should populate and propagate sentry baggage if sentry-trace header does n
7676
test_data: {
7777
host: 'somewhere.not.sentry',
7878
// TraceId changes, hence we only expect that the string contains the traceid key
79-
baggage: expect.stringContaining(
80-
'sentry-environment=prod,sentry-release=1.0,sentry-transaction=GET%20%2Ftest%2Fexpress,sentry-public_key=public,sentry-trace_id=',
79+
baggage: expect.stringMatching(
80+
/sentry-environment=prod,sentry-release=1.0,sentry-public_key=public,sentry-trace_id=[\S]*,sentry-sample_rate=1,sentry-transaction=GET%20%2Ftest%2Fexpress/,
8181
),
8282
},
8383
});
@@ -95,8 +95,8 @@ test('Should populate Sentry and ignore 3rd party content if sentry-trace header
9595
test_data: {
9696
host: 'somewhere.not.sentry',
9797
// TraceId changes, hence we only expect that the string contains the traceid key
98-
baggage: expect.stringContaining(
99-
'sentry-environment=prod,sentry-release=1.0,sentry-transaction=GET%20%2Ftest%2Fexpress,sentry-public_key=public,sentry-trace_id=',
98+
baggage: expect.stringMatching(
99+
/sentry-environment=prod,sentry-release=1.0,sentry-public_key=public,sentry-trace_id=[\S]*,sentry-sample_rate=1,sentry-transaction=GET%20%2Ftest%2Fexpress/,
100100
),
101101
},
102102
});

packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ test('should attach a `baggage` header to an outgoing request.', async () => {
1313
test_data: {
1414
host: 'somewhere.not.sentry',
1515
baggage:
16-
'sentry-environment=prod,sentry-release=1.0,sentry-transaction=GET%20%2Ftest%2Fexpress,sentry-user_segment=SegmentA' +
17-
',sentry-public_key=public,sentry-trace_id=86f39e84263a4de99c326acab3bfe3bd,sentry-sample_rate=1',
16+
'sentry-environment=prod,sentry-release=1.0,sentry-user_segment=SegmentA,sentry-public_key=public' +
17+
',sentry-trace_id=86f39e84263a4de99c326acab3bfe3bd,sentry-sample_rate=1,sentry-transaction=GET%20%2Ftest%2Fexpress',
1818
},
1919
});
2020
});

packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors-with-sentry-entries/test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ test('should ignore sentry-values in `baggage` header of a third party vendor an
3535
baggage: [
3636
'other=vendor,foo=bar,third=party,sentry-release=9.9.9,sentry-environment=staging,sentry-sample_rate=0.54,last=item',
3737
expect.stringMatching(
38-
/sentry-environment=prod,sentry-release=1\.0,sentry-transaction=GET%20%2Ftest%2Fexpress,sentry-public_key=public,sentry-trace_id=[0-9a-f]{32},sentry-sample_rate=1/,
38+
/sentry-environment=prod,sentry-release=1\.0,sentry-public_key=public,sentry-trace_id=[0-9a-f]{32},sentry-sample_rate=1,sentry-transaction=GET%20%2Ftest%2Fexpress/,
3939
),
4040
],
4141
},

0 commit comments

Comments
 (0)