Skip to content

Commit cb2462b

Browse files
authored
ref(tracing): Move getBaggage() from Span to Transaction class (#5299)
Move the getBaggage() method from the Span class, to the Transaction class. This change is good for two reasons: * It conceptually makes more sense to store DSC on the transaction rather than the span because we're always talking about transactions in DS * We can get rid of a sketchy type cast and avoid a circular dependency situation (transaction -> span -> transaction) entirely
1 parent ea9ddaf commit cb2462b

File tree

8 files changed

+104
-112
lines changed

8 files changed

+104
-112
lines changed

packages/node/src/integrations/http.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,13 @@ function _createWrappedRequestMethodFactory(
123123
`[Tracing] Adding sentry-trace header ${sentryTraceHeader} to outgoing request to ${requestUrl}: `,
124124
);
125125

126+
const baggage = parentSpan.transaction && parentSpan.transaction.getBaggage();
126127
const headerBaggageString = requestOptions.headers && requestOptions.headers.baggage;
127128

128129
requestOptions.headers = {
129130
...requestOptions.headers,
130131
'sentry-trace': sentryTraceHeader,
131-
baggage: mergeAndSerializeBaggage(span.getBaggage(), headerBaggageString),
132+
baggage: mergeAndSerializeBaggage(baggage, headerBaggageString),
132133
};
133134
}
134135
}

packages/tracing/src/browser/request.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* eslint-disable max-lines */
2-
import type { Span } from '@sentry/types';
2+
import type { Baggage, Span } from '@sentry/types';
33
import {
44
addInstrumentationHandler,
55
BAGGAGE_HEADER_NAME,
@@ -198,12 +198,13 @@ export function fetchCallback(
198198
const request = (handlerData.args[0] = handlerData.args[0] as string | Request);
199199
// eslint-disable-next-line @typescript-eslint/no-explicit-any
200200
const options = (handlerData.args[1] = (handlerData.args[1] as { [key: string]: any }) || {});
201-
options.headers = addTracingHeaders(request, span, options);
201+
options.headers = addTracingHeaders(request, activeTransaction.getBaggage(), span, options);
202202
}
203203
}
204204

205205
function addTracingHeaders(
206206
request: string | Request,
207+
incomingBaggage: Baggage | undefined,
207208
span: Span,
208209
options: { [key: string]: any },
209210
): PolymorphicRequestHeaders {
@@ -212,7 +213,6 @@ function addTracingHeaders(
212213
if (isInstanceOf(request, Request)) {
213214
headers = (request as Request).headers;
214215
}
215-
const incomingBaggage = span.getBaggage();
216216

217217
if (headers) {
218218
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
@@ -302,7 +302,7 @@ export function xhrCallback(
302302

303303
handlerData.xhr.setRequestHeader(
304304
BAGGAGE_HEADER_NAME,
305-
mergeAndSerializeBaggage(span.getBaggage(), headerBaggageString),
305+
mergeAndSerializeBaggage(activeTransaction.getBaggage(), headerBaggageString),
306306
);
307307
} catch (_) {
308308
// Error: InvalidStateError: Failed to execute 'setRequestHeader' on 'XMLHttpRequest': The object's state must be OPENED.

packages/tracing/src/span.ts

+2-94
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,6 @@
11
/* eslint-disable max-lines */
2-
import { getCurrentHub } from '@sentry/hub';
3-
import { Baggage, Hub, Primitive, Span as SpanInterface, SpanContext, Transaction } from '@sentry/types';
4-
import {
5-
createBaggage,
6-
dropUndefinedKeys,
7-
isBaggageMutable,
8-
isSentryBaggageEmpty,
9-
setBaggageImmutable,
10-
setBaggageValue,
11-
timestampWithMs,
12-
uuid4,
13-
} from '@sentry/utils';
2+
import { Primitive, Span as SpanInterface, SpanContext, Transaction } from '@sentry/types';
3+
import { dropUndefinedKeys, timestampWithMs, uuid4 } from '@sentry/utils';
144

155
/**
166
* Keeps track of finished spans for a given transaction
@@ -308,29 +298,6 @@ export class Span implements SpanInterface {
308298
});
309299
}
310300

311-
/**
312-
* @inheritdoc
313-
*/
314-
public getBaggage(): Baggage {
315-
const existingBaggage = this.transaction && this.transaction.metadata.baggage;
316-
317-
// Only add Sentry baggage items to baggage, if baggage does not exist yet or it is still
318-
// empty and mutable
319-
// TODO: we might want to ditch the isSentryBaggageEmpty condition because it prevents
320-
// custom sentry-values in DSC (added by users in the future)
321-
const finalBaggage =
322-
!existingBaggage || (isBaggageMutable(existingBaggage) && isSentryBaggageEmpty(existingBaggage))
323-
? this._populateBaggageWithSentryValues(existingBaggage)
324-
: existingBaggage;
325-
326-
// In case, we poulated the DSC, we have update the stored one on the transaction.
327-
if (existingBaggage !== finalBaggage && this.transaction) {
328-
this.transaction.metadata.baggage = finalBaggage;
329-
}
330-
331-
return finalBaggage;
332-
}
333-
334301
/**
335302
* @inheritDoc
336303
*/
@@ -360,65 +327,6 @@ export class Span implements SpanInterface {
360327
trace_id: this.traceId,
361328
});
362329
}
363-
364-
/**
365-
* Collects and adds data to the passed baggage object.
366-
*
367-
* Note: This function does not explicitly check if the passed baggage object is allowed
368-
* to be modified. Implicitly, `setBaggageValue` will not make modification to the object
369-
* if it was already set immutable.
370-
*
371-
* After adding the data, the baggage object is set immutable to prevent further modifications.
372-
*
373-
* @param baggage
374-
*
375-
* @returns modified and immutable baggage object
376-
*/
377-
private _populateBaggageWithSentryValues(baggage: Baggage = createBaggage({})): Baggage {
378-
// Because of a cicular dependency, we cannot import the Transaction class here, hence the type casts
379-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
380-
const hub: Hub = ((this.transaction as any) && (this.transaction as any)._hub) || getCurrentHub();
381-
const client = hub && hub.getClient();
382-
383-
const { environment, release } = (client && client.getOptions()) || {};
384-
const { publicKey } = (client && client.getDsn()) || {};
385-
386-
const metadata = this.transaction && this.transaction.metadata;
387-
const sampleRate = metadata && metadata.transactionSampling && metadata.transactionSampling.rate;
388-
389-
const traceId = this.traceId;
390-
391-
const transactionName = this.transaction && this.transaction.name;
392-
393-
let userId, userSegment;
394-
hub.withScope(scope => {
395-
const user = scope.getUser();
396-
userId = user && user.id;
397-
userSegment = user && user.segment;
398-
});
399-
400-
environment && setBaggageValue(baggage, 'environment', environment);
401-
release && setBaggageValue(baggage, 'release', release);
402-
transactionName && setBaggageValue(baggage, 'transaction', transactionName);
403-
userId && setBaggageValue(baggage, 'userid', userId);
404-
userSegment && setBaggageValue(baggage, 'usersegment', userSegment);
405-
sampleRate &&
406-
setBaggageValue(
407-
baggage,
408-
'samplerate',
409-
// This will make sure that expnent notation (e.g. 1.45e-14) is converted to simple decimal representation
410-
// Another edge case would be something like Number.NEGATIVE_INFINITY in which case we could still
411-
// add something like .replace(/-?∞/, '0'). For the sake of saving bytes, I'll not add this until
412-
// it becomes a problem
413-
sampleRate.toLocaleString('fullwide', { useGrouping: false, maximumFractionDigits: 16 }),
414-
);
415-
publicKey && setBaggageValue(baggage, 'publickey', publicKey);
416-
traceId && setBaggageValue(baggage, 'traceid', traceId);
417-
418-
setBaggageImmutable(baggage);
419-
420-
return baggage;
421-
}
422330
}
423331

424332
export type SpanStatusType =

packages/tracing/src/transaction.ts

+89-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
import { getCurrentHub, Hub } from '@sentry/hub';
22
import {
3+
Baggage,
34
Event,
45
Measurements,
56
Transaction as TransactionInterface,
67
TransactionContext,
78
TransactionMetadata,
89
} from '@sentry/types';
9-
import { dropUndefinedKeys, logger } from '@sentry/utils';
10+
import {
11+
createBaggage,
12+
dropUndefinedKeys,
13+
isBaggageMutable,
14+
isSentryBaggageEmpty,
15+
logger,
16+
setBaggageImmutable,
17+
setBaggageValue,
18+
} from '@sentry/utils';
1019

1120
import { Span as SpanClass, SpanRecorder } from './span';
1221

@@ -176,4 +185,83 @@ export class Transaction extends SpanClass implements TransactionInterface {
176185

177186
return this;
178187
}
188+
189+
/**
190+
* @inheritdoc
191+
*
192+
* @experimental
193+
*/
194+
public getBaggage(): Baggage {
195+
const existingBaggage = this.metadata.baggage;
196+
197+
// Only add Sentry baggage items to baggage, if baggage does not exist yet or it is still
198+
// empty and mutable
199+
// TODO: we might want to ditch the isSentryBaggageEmpty condition because it prevents
200+
// custom sentry-values in DSC (added by users in the future)
201+
const finalBaggage =
202+
!existingBaggage || (isBaggageMutable(existingBaggage) && isSentryBaggageEmpty(existingBaggage))
203+
? this._populateBaggageWithSentryValues(existingBaggage)
204+
: existingBaggage;
205+
206+
// In case, we poulated the DSC, we have update the stored one on the transaction.
207+
if (existingBaggage !== finalBaggage) {
208+
this.metadata.baggage = finalBaggage;
209+
}
210+
211+
return finalBaggage;
212+
}
213+
214+
/**
215+
* Collects and adds data to the passed baggage object.
216+
*
217+
* Note: This function does not explicitly check if the passed baggage object is allowed
218+
* to be modified. Implicitly, `setBaggageValue` will not make modification to the object
219+
* if it was already set immutable.
220+
*
221+
* After adding the data, the baggage object is set immutable to prevent further modifications.
222+
*
223+
* @param baggage
224+
*
225+
* @returns modified and immutable baggage object
226+
*/
227+
private _populateBaggageWithSentryValues(baggage: Baggage = createBaggage({})): Baggage {
228+
const hub: Hub = this._hub || getCurrentHub();
229+
const client = hub && hub.getClient();
230+
231+
const { environment, release } = (client && client.getOptions()) || {};
232+
const { publicKey } = (client && client.getDsn()) || {};
233+
234+
const sampleRate = this.metadata && this.metadata.transactionSampling && this.metadata.transactionSampling.rate;
235+
const traceId = this.traceId;
236+
const transactionName = this.name;
237+
238+
let userId, userSegment;
239+
hub.configureScope(scope => {
240+
const { id, segment } = scope.getUser() || {};
241+
userId = id;
242+
userSegment = segment;
243+
});
244+
245+
environment && setBaggageValue(baggage, 'environment', environment);
246+
release && setBaggageValue(baggage, 'release', release);
247+
transactionName && setBaggageValue(baggage, 'transaction', transactionName);
248+
userId && setBaggageValue(baggage, 'userid', userId);
249+
userSegment && setBaggageValue(baggage, 'usersegment', userSegment);
250+
sampleRate &&
251+
setBaggageValue(
252+
baggage,
253+
'samplerate',
254+
// This will make sure that expnent notation (e.g. 1.45e-14) is converted to simple decimal representation
255+
// Another edge case would be something like Number.NEGATIVE_INFINITY in which case we could still
256+
// add something like .replace(/-?∞/, '0'). For the sake of saving bytes, I'll not add this until
257+
// it becomes a problem
258+
sampleRate.toLocaleString('fullwide', { useGrouping: false, maximumFractionDigits: 16 }),
259+
);
260+
publicKey && setBaggageValue(baggage, 'publickey', publicKey);
261+
traceId && setBaggageValue(baggage, 'traceid', traceId);
262+
263+
setBaggageImmutable(baggage);
264+
265+
return baggage;
266+
}
179267
}

packages/tracing/test/span.test.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -414,9 +414,7 @@ describe('Span', () => {
414414

415415
const hubSpy = jest.spyOn(hub.getClient()!, 'getOptions');
416416

417-
const span = transaction.startChild();
418-
419-
const baggage = span.getBaggage();
417+
const baggage = transaction.getBaggage();
420418

421419
expect(hubSpy).toHaveBeenCalledTimes(0);
422420
expect(baggage && isSentryBaggageEmpty(baggage)).toBe(false);
@@ -438,9 +436,7 @@ describe('Span', () => {
438436

439437
const hubSpy = jest.spyOn(hub.getClient()!, 'getOptions');
440438

441-
const span = transaction.startChild();
442-
443-
const baggage = span.getBaggage();
439+
const baggage = transaction.getBaggage();
444440

445441
expect(hubSpy).toHaveBeenCalledTimes(1);
446442
expect(baggage && isSentryBaggageEmpty(baggage)).toBe(false);

packages/types/src/span.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { Baggage } from './baggage';
21
import { Primitive } from './misc';
32
import { Transaction } from './transaction';
43

@@ -162,6 +161,7 @@ export interface Span extends SpanContext {
162161
tags?: { [key: string]: Primitive };
163162
trace_id: string;
164163
};
164+
165165
/** Convert the object to JSON */
166166
toJSON(): {
167167
data?: { [key: string]: any };
@@ -175,7 +175,4 @@ export interface Span extends SpanContext {
175175
timestamp?: number;
176176
trace_id: string;
177177
};
178-
179-
/** return the baggage for dynamic sampling and trace propagation */
180-
getBaggage(): Baggage | undefined;
181178
}

packages/types/src/transaction.ts

+3
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ export interface Transaction extends TransactionContext, Span {
8787

8888
/** Updates the current transaction with a new `TransactionContext` */
8989
updateWithContext(transactionContext: TransactionContext): this;
90+
91+
/** return the baggage for dynamic sampling and trace propagation */
92+
getBaggage(): Baggage;
9093
}
9194

9295
/**

packages/utils/src/baggage.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { Baggage, BaggageObj, TraceparentData } from '@sentry/types';
2-
import { HttpHeaderValue } from '@sentry/types';
1+
import { Baggage, BaggageObj, HttpHeaderValue, TraceparentData } from '@sentry/types';
32

43
import { isString } from './is';
54
import { logger } from './logger';

0 commit comments

Comments
 (0)