Skip to content

Commit 13be067

Browse files
authored
feat(core): Add trace envelope header to span envelope (#11699)
adds a `trace` envelope header to our standalone span envelope creation function.
1 parent 84e9c97 commit 13be067

File tree

4 files changed

+115
-5
lines changed

4 files changed

+115
-5
lines changed

dev-packages/browser-integration-tests/suites/public-api/startSpan/standalone-mixed-transaction/test.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,16 @@ sentryTest(
3838
expect(traceId).toMatch(/[a-f0-9]{32}/);
3939
expect(parentSpanId).toMatch(/[a-f0-9]{16}/);
4040

41-
// TODO: the span envelope also needs to contain the `trace` header (follow-up PR)
4241
expect(spanEnvelopeHeader).toEqual({
4342
sent_at: expect.any(String),
43+
trace: {
44+
environment: 'production',
45+
public_key: 'public',
46+
sample_rate: '1',
47+
sampled: 'true',
48+
trace_id: traceId,
49+
transaction: 'outer',
50+
},
4451
});
4552

4653
expect(transactionEnvelopeHeader).toEqual({

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

+10
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,18 @@ sentryTest('sends a segment span envelope', async ({ getLocalTestPath, page }) =
2222
const itemHeader = item[0];
2323
const spanJson = item[1];
2424

25+
const traceId = spanJson.trace_id;
26+
2527
expect(headers).toEqual({
2628
sent_at: expect.any(String),
29+
trace: {
30+
environment: 'production',
31+
public_key: 'public',
32+
sample_rate: '1',
33+
sampled: 'true',
34+
trace_id: traceId,
35+
transaction: 'standalone_segment_span',
36+
},
2737
});
2838

2939
expect(itemHeader).toEqual({

packages/core/src/envelope.ts

+12-2
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,10 +127,19 @@ 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
};
132-
133143
const items = spans.map(span => createSpanEnvelopeItem(spanToJSON(span)));
134144
return createEnvelope<SpanEnvelope>(headers, items);
135145
}

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

+85-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,77 @@ 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+
startTimestamp: 1,
105+
endTimestamp: 2,
106+
sampled: true,
107+
attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom' },
108+
});
109+
110+
const spanEnvelope = createSpanEnvelope([span]);
111+
112+
const spanItem = spanEnvelope[1][0][1];
113+
expect(spanItem).toEqual({
114+
data: {
115+
'sentry.origin': 'manual',
116+
'sentry.source': 'custom',
117+
},
118+
description: 'test',
119+
is_segment: true,
120+
origin: 'manual',
121+
span_id: expect.stringMatching(/^[0-9a-f]{16}$/),
122+
segment_id: spanItem.segment_id,
123+
start_timestamp: 1,
124+
timestamp: 2,
125+
trace_id: expect.stringMatching(/^[0-9a-f]{32}$/),
126+
});
127+
});
128+
129+
it('adds `trace` and `sent_at` envelope headers', () => {
130+
const spanEnvelope = createSpanEnvelope([
131+
new SentrySpan({ name: 'test', attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom' } }),
132+
]);
133+
134+
const spanEnvelopeHeaders = spanEnvelope[0];
135+
expect(spanEnvelopeHeaders).toEqual({
136+
sent_at: expect.any(String),
137+
trace: {
138+
environment: 'production',
139+
public_key: 'username',
140+
sampled: 'false',
141+
trace_id: expect.stringMatching(/^[0-9a-f]{32}$/),
142+
transaction: 'test',
143+
},
144+
});
145+
});
146+
147+
it("doesn't add a `trace` envelope header if there's no public key", () => {
148+
const options = getDefaultTestClientOptions({ tracesSampleRate: 1, dsn: 'https://domain/123' });
149+
client = new TestClient(options);
150+
setCurrentClient(client);
151+
client.init();
152+
153+
const spanEnvelope = createSpanEnvelope([new SentrySpan()]);
154+
155+
const spanEnvelopeHeaders = spanEnvelope[0];
156+
expect(spanEnvelopeHeaders).toEqual({
157+
sent_at: expect.any(String),
158+
});
159+
});
160+
});

0 commit comments

Comments
 (0)