Skip to content

Commit 3420308

Browse files
committed
add trace envelope header
1 parent 8b3627a commit 3420308

File tree

8 files changed

+136
-14
lines changed

8 files changed

+136
-14
lines changed
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
Sentry.startSpan({ name: 'standalone_segment_span', experimental: { standalone: true, segment: false } }, () => {});
1+
Sentry.startSpan(
2+
{ name: 'standalone_none_segment_span', experimental: { standalone: true, segment: false } },
3+
() => {},
4+
);

dev-packages/browser-integration-tests/suites/public-api/startSpan/standalone/segment-false/test.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,22 @@ sentryTest('sends a span envelope with is_segment: false', async ({ getLocalTest
1616
const url = await getLocalTestPath({ testDir: __dirname });
1717
const spanEnvelope = await getFirstSentryEnvelopeRequest<SpanEnvelope>(page, url, properFullEnvelopeRequestParser);
1818

19-
const headers = spanEnvelope[0];
19+
const envelopeHeaders = spanEnvelope[0];
2020
const item = spanEnvelope[1][0];
2121

2222
const itemHeader = item[0];
2323
const spanJson = item[1];
2424

25-
expect(headers).toEqual({
25+
expect(envelopeHeaders).toEqual({
2626
sent_at: expect.any(String),
27+
trace: {
28+
environment: 'production',
29+
public_key: 'public',
30+
sample_rate: '1',
31+
sampled: 'true',
32+
trace_id: spanJson.trace_id,
33+
transaction: 'standalone_none_segment_span',
34+
},
2735
});
2836

2937
expect(itemHeader).toEqual({
@@ -36,7 +44,7 @@ sentryTest('sends a span envelope with is_segment: false', async ({ getLocalTest
3644
'sentry.sample_rate': 1,
3745
'sentry.source': 'custom',
3846
},
39-
description: 'standalone_segment_span',
47+
description: 'standalone_none_segment_span',
4048
origin: 'manual',
4149
span_id: expect.stringMatching(/^[0-9a-f]{16}$/),
4250
start_timestamp: expect.any(Number),

dev-packages/browser-integration-tests/suites/public-api/startSpan/standalone/segment-true/test.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,22 @@ sentryTest('sends a segment span envelope', async ({ getLocalTestPath, page }) =
1616
const url = await getLocalTestPath({ testDir: __dirname });
1717
const spanEnvelope = await getFirstSentryEnvelopeRequest<SpanEnvelope>(page, url, properFullEnvelopeRequestParser);
1818

19-
const headers = spanEnvelope[0];
19+
const envelopeHeaders = spanEnvelope[0];
2020
const item = spanEnvelope[1][0];
2121

2222
const itemHeader = item[0];
2323
const spanJson = item[1];
2424

25-
expect(headers).toEqual({
25+
expect(envelopeHeaders).toEqual({
2626
sent_at: expect.any(String),
27+
trace: {
28+
environment: 'production',
29+
public_key: 'public',
30+
sample_rate: '1',
31+
sampled: 'true',
32+
trace_id: spanJson.trace_id,
33+
transaction: 'standalone_segment_span',
34+
},
2735
});
2836

2937
expect(itemHeader).toEqual({
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Sentry.startSpan({ name: 'standalone_segment_span', experimental: { standalone: true } }, () => {});
1+
Sentry.startSpan({ name: 'standalone_span', experimental: { standalone: true } }, () => {});

dev-packages/browser-integration-tests/suites/public-api/startSpan/standalone/segment-undefined/test.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,22 @@ sentryTest('sends a segment-less span envelope', async ({ getLocalTestPath, page
1616
const url = await getLocalTestPath({ testDir: __dirname });
1717
const spanEnvelope = await getFirstSentryEnvelopeRequest<SpanEnvelope>(page, url, properFullEnvelopeRequestParser);
1818

19-
const headers = spanEnvelope[0];
19+
const envelopeHeaders = spanEnvelope[0];
2020
const item = spanEnvelope[1][0];
2121

2222
const itemHeader = item[0];
2323
const spanJson = item[1];
2424

25-
expect(headers).toEqual({
25+
expect(envelopeHeaders).toEqual({
2626
sent_at: expect.any(String),
27+
trace: {
28+
environment: 'production',
29+
public_key: 'public',
30+
sample_rate: '1',
31+
sampled: 'true',
32+
trace_id: spanJson.trace_id,
33+
transaction: 'standalone_span',
34+
},
2735
});
2836

2937
expect(itemHeader).toEqual({
@@ -36,7 +44,7 @@ sentryTest('sends a segment-less span envelope', async ({ getLocalTestPath, page
3644
'sentry.sample_rate': 1,
3745
'sentry.source': 'custom',
3846
},
39-
description: 'standalone_segment_span',
47+
description: 'standalone_span',
4048
origin: 'manual',
4149
span_id: expect.stringMatching(/^[0-9a-f]{16}$/),
4250
start_timestamp: expect.any(Number),

packages/core/src/envelope.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type {
22
Attachment,
33
AttachmentItem,
44
DsnComponents,
5+
DynamicSamplingContext,
56
Event,
67
EventEnvelope,
78
EventItem,
@@ -21,7 +22,7 @@ import {
2122
getSdkMetadataForEnvelopeHeader,
2223
} from '@sentry/utils';
2324
import { createSpanEnvelopeItem } from '@sentry/utils';
24-
import type { SentrySpan } from './tracing';
25+
import { type SentrySpan, getDynamicSamplingContextFromSpan } from './tracing';
2526
import { spanToJSON } from './utils/spanUtils';
2627

2728
/**
@@ -126,8 +127,18 @@ export function createAttachmentEnvelope(
126127
* Create envelope from Span item.
127128
*/
128129
export function createSpanEnvelope(spans: SentrySpan[]): SpanEnvelope {
130+
function dscHasRequiredProps(dsc: Partial<DynamicSamplingContext>): dsc is DynamicSamplingContext {
131+
return !!dsc.trace_id && !!dsc.public_key;
132+
}
133+
134+
// For the moment we'll obtain the DSC from the first span in the array
135+
// This might need to be changed if we permit sending multiple spans from
136+
// different segments in one envelope
137+
const dsc = getDynamicSamplingContextFromSpan(spans[0]);
138+
129139
const headers: SpanEnvelope[0] = {
130140
sent_at: new Date().toISOString(),
141+
...(dscHasRequiredProps(dsc) && { trace: dsc }),
131142
};
132143

133144
const items = spans.map(span => createSpanEnvelopeItem(spanToJSON(span)));

packages/core/test/lib/envelope.test.ts

+86-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
1-
import type { DsnComponents, DynamicSamplingContext, Event } from '@sentry/types';
1+
import type { Client, DsnComponents, DynamicSamplingContext, Event } from '@sentry/types';
22

3-
import { createEventEnvelope } from '../../src/envelope';
3+
import {
4+
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
5+
SentrySpan,
6+
getCurrentScope,
7+
getIsolationScope,
8+
setAsyncContextStrategy,
9+
setCurrentClient,
10+
} from '../../src';
11+
import { createEventEnvelope, createSpanEnvelope } from '../../src/envelope';
12+
import { TestClient, getDefaultTestClientOptions } from '../mocks/client';
413

514
const testDsn: DsnComponents = { protocol: 'https', projectId: 'abc', host: 'testry.io', publicKey: 'pubKey123' };
615

@@ -75,3 +84,78 @@ describe('createEventEnvelope', () => {
7584
});
7685
});
7786
});
87+
88+
describe('createSpanEnvelope', () => {
89+
let client: Client | undefined;
90+
beforeEach(() => {
91+
getCurrentScope().clear();
92+
getIsolationScope().clear();
93+
setAsyncContextStrategy(undefined);
94+
const options = getDefaultTestClientOptions({ tracesSampleRate: 1, dsn: 'https://username@domain/123' });
95+
client = new TestClient(options);
96+
setCurrentClient(client);
97+
client.init();
98+
});
99+
100+
it('creates a span envelope', () => {
101+
const span = new SentrySpan({
102+
name: 'test',
103+
isStandalone: true,
104+
isSegment: true,
105+
startTimestamp: 1,
106+
endTimestamp: 2,
107+
sampled: true,
108+
attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom' },
109+
});
110+
111+
const spanEnvelope = createSpanEnvelope([span]);
112+
113+
const spanItem = spanEnvelope[1][0][1];
114+
expect(spanItem).toEqual({
115+
data: {
116+
'sentry.origin': 'manual',
117+
'sentry.source': 'custom',
118+
},
119+
description: 'test',
120+
is_segment: true,
121+
origin: 'manual',
122+
span_id: expect.stringMatching(/^[0-9a-f]{16}$/),
123+
segment_id: spanItem.segment_id,
124+
start_timestamp: 1,
125+
timestamp: 2,
126+
trace_id: expect.stringMatching(/^[0-9a-f]{32}$/),
127+
});
128+
});
129+
130+
it('adds `trace` and `sent_at` envelope headers', () => {
131+
const spanEnvelope = createSpanEnvelope([
132+
new SentrySpan({ name: 'test', attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom' } }),
133+
]);
134+
135+
const spanEnvelopeHeaders = spanEnvelope[0];
136+
expect(spanEnvelopeHeaders).toEqual({
137+
sent_at: expect.any(String),
138+
trace: {
139+
environment: 'production',
140+
public_key: 'username',
141+
sampled: 'false',
142+
trace_id: expect.stringMatching(/^[0-9a-f]{32}$/),
143+
transaction: 'test',
144+
},
145+
});
146+
});
147+
148+
it("doesn't add a `trace` envelope header if there's no public key", () => {
149+
const options = getDefaultTestClientOptions({ tracesSampleRate: 1, dsn: 'https://domain/123' });
150+
client = new TestClient(options);
151+
setCurrentClient(client);
152+
client.init();
153+
154+
const spanEnvelope = createSpanEnvelope([new SentrySpan()]);
155+
156+
const spanEnvelopeHeaders = spanEnvelope[0];
157+
expect(spanEnvelopeHeaders).toEqual({
158+
sent_at: expect.any(String),
159+
});
160+
});
161+
});

packages/types/src/envelope.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ type CheckInEnvelopeHeaders = { trace?: DynamicSamplingContext };
106106
type ClientReportEnvelopeHeaders = BaseEnvelopeHeaders;
107107
type ReplayEnvelopeHeaders = BaseEnvelopeHeaders;
108108
type StatsdEnvelopeHeaders = BaseEnvelopeHeaders;
109-
type SpanEnvelopeHeaders = BaseEnvelopeHeaders;
109+
type SpanEnvelopeHeaders = BaseEnvelopeHeaders & { trace?: DynamicSamplingContext };
110110

111111
export type EventEnvelope = BaseEnvelope<
112112
EventEnvelopeHeaders,

0 commit comments

Comments
 (0)