Skip to content

Commit de1e0c2

Browse files
committed
review suggestions, more tests
1 parent 75d687b commit de1e0c2

4 files changed

Lines changed: 60 additions & 9 deletions

File tree

packages/core/src/tracing/sentrySpan.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,8 @@ export class SentrySpan implements Span {
264264
end_timestamp: this._endTime ?? this._startTime,
265265
is_segment: this._isStandaloneSpan || this === getRootSpan(this),
266266
status: getSimpleStatusMessage(this._status),
267-
attributes: this._attributes,
267+
// spread to avoid mutating the original object when later processing the span
268+
attributes: { ...this._attributes },
268269
links: getStreamedSpanLinks(this._links),
269270
};
270271
}

packages/core/src/tracing/spans/captureSpan.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,11 @@ export function captureSpan(span: Span, client: Client): SerializedStreamedSpanW
5454

5555
if (span === segmentSpan) {
5656
applyScopeToSegmentSpan(spanJSON, finalScopeData);
57-
// Allow hook subscribers to add additional data to the segment span JSON
57+
// Allow hook subscribers to mutate the segment span JSON
5858
client.emit('processSegmentSpan', spanJSON);
5959
}
6060

61-
// Allow hook subscribers to add additional data to the span JSON
61+
// Allow hook subscribers to mutate the span JSON
6262
client.emit('processSpan', spanJSON);
6363

6464
const { beforeSendSpan } = client.getOptions();
@@ -67,11 +67,12 @@ export function captureSpan(span: Span, client: Client): SerializedStreamedSpanW
6767
? applyBeforeSendSpanCallback(spanJSON, beforeSendSpan)
6868
: spanJSON;
6969

70-
// Backfill sentry.span.source from sentry.source for the PoC
71-
// TODO(v11): Stop sending `sentry.source` attribute and only send `sentry.span.source`
72-
if (processedSpan.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]) {
70+
// Backfill sentry.span.source from sentry.source. Only `sentry.span.source` is respected by Sentry.
71+
// TODO(v11): Ensure we always only send `sentry.span.source` and remove this backfill.
72+
const spanNameSource = processedSpan.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE];
73+
if (spanNameSource) {
7374
safeSetSpanJSONAttributes(processedSpan, {
74-
[SEMANTIC_ATTRIBUTE_SENTRY_SPAN_SOURCE]: processedSpan.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE],
75+
[SEMANTIC_ATTRIBUTE_SENTRY_SPAN_SOURCE]: spanNameSource,
7576
});
7677
delete processedSpan.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE];
7778
}
@@ -142,7 +143,7 @@ export function safeSetSpanJSONAttributes(
142143
const originalAttributes = spanJSON.attributes ?? (spanJSON.attributes = {});
143144

144145
Object.keys(newAttributes).forEach(key => {
145-
if (!originalAttributes?.[key]) {
146+
if (!(key in originalAttributes)) {
146147
originalAttributes[key] = newAttributes[key];
147148
}
148149
});

packages/core/src/utils/spanUtils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,8 @@ export function spanToStreamedSpanJSON(span: Span): StreamedSpanJSON {
230230
end_timestamp: spanTimeInputToSeconds(endTime),
231231
is_segment: span === INTERNAL_getSegmentSpan(span),
232232
status: getSimpleStatusMessage(status),
233-
attributes,
233+
// spread to avoid mutating the original object when later processing the span
234+
attributes: { ...attributes },
234235
links: getStreamedSpanLinks(links),
235236
};
236237
}

packages/core/test/lib/tracing/spans/captureSpan.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { describe, expect, it, vi } from 'vitest';
2+
import type { StreamedSpanJSON } from '../../../../src';
23
import {
34
captureSpan,
45
SEMANTIC_ATTRIBUTE_SENTRY_ENVIRONMENT,
@@ -20,6 +21,7 @@ import {
2021
withScope,
2122
withStreamedSpan,
2223
} from '../../../../src';
24+
import { safeSetSpanJSONAttributes } from '../../../../src/tracing/spans/captureSpan';
2325
import { getDefaultTestClientOptions, TestClient } from '../../../mocks/client';
2426

2527
describe('captureSpan', () => {
@@ -414,3 +416,49 @@ describe('captureSpan', () => {
414416
});
415417
});
416418
});
419+
420+
describe('safeSetSpanJSONAttributes', () => {
421+
it('sets attributes that do not exist', () => {
422+
const spanJSON = { attributes: { a: 1, b: 2 } };
423+
424+
// @ts-expect-error - only passing a partial object for this test
425+
safeSetSpanJSONAttributes(spanJSON, { c: 3 });
426+
427+
expect(spanJSON.attributes).toEqual({ a: 1, b: 2, c: 3 });
428+
});
429+
430+
it("doesn't set attributes that already exist", () => {
431+
const spanJSON = { attributes: { a: 1, b: 2 } };
432+
// @ts-expect-error - only passing a partial object for this test
433+
safeSetSpanJSONAttributes(spanJSON, { a: 3 });
434+
435+
expect(spanJSON.attributes).toEqual({ a: 1, b: 2 });
436+
});
437+
438+
it.each([null, undefined])("doesn't overwrite attributes previously set to %s", val => {
439+
const spanJSON = { attributes: { a: val, b: 2 } };
440+
441+
// @ts-expect-error - only passing a partial object for this test
442+
safeSetSpanJSONAttributes(spanJSON, { a: 1 });
443+
444+
expect(spanJSON.attributes).toEqual({ a: val, b: 2 });
445+
});
446+
447+
it("doesn't overwrite falsy attribute values (%s)", () => {
448+
const spanJSON = { attributes: { a: false, b: '', c: 0 } };
449+
450+
// @ts-expect-error - only passing a partial object for this test
451+
safeSetSpanJSONAttributes(spanJSON, { a: 1, b: 'test', c: 1 });
452+
453+
expect(spanJSON.attributes).toEqual({ a: false, b: '', c: 0 });
454+
});
455+
456+
it('handles an undefined attributes property', () => {
457+
const spanJSON: Partial<StreamedSpanJSON> = {};
458+
459+
// @ts-expect-error - only passing a partial object for this test
460+
safeSetSpanJSONAttributes(spanJSON, { a: 1 });
461+
462+
expect(spanJSON.attributes).toEqual({ a: 1 });
463+
});
464+
});

0 commit comments

Comments
 (0)