Skip to content

Commit 73a8314

Browse files
authored
feat(core): Add span.spanContext() (#10037)
This will replace `spanId`, `traceId` and `sampled` lookups. Because the bitwise format of OTEL is a bit weird IMHO to check if a span is sampled, I also added a utility `spanIsSampled(span)` method to abstract this away.
1 parent b6c369d commit 73a8314

File tree

29 files changed

+356
-113
lines changed

29 files changed

+356
-113
lines changed

MIGRATION.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ In v8, the Span class is heavily reworked. The following properties & methods ar
6969
* `span.toTraceparent()`: use `spanToTraceHeader(span)` util instead.
7070
* `span.getTraceContext()`: Use `spanToTraceContext(span)` utility function instead.
7171
* `span.sampled`: Use `span.isRecording()` instead.
72+
* `span.spanId`: Use `span.spanContext().spanId` instead.
73+
* `span.traceId`: Use `span.spanContext().traceId` instead.
7274

7375
## Deprecate `pushScope` & `popScope` in favor of `withScope`
7476

dev-packages/browser-integration-tests/suites/public-api/startTransaction/basic_usage/test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,6 @@ sentryTest('should report finished spans as children of the root transaction', a
3939

4040
const span_5 = transaction.spans?.[2];
4141
expect(span_5?.op).toBe('span_5');
42+
// eslint-disable-next-line deprecation/deprecation
4243
expect(span_5?.parentSpanId).toEqual(span_3?.spanId);
4344
});

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ app.use(cors());
2828
app.get('/test/express', (_req, res) => {
2929
const transaction = Sentry.getCurrentHub().getScope().getTransaction();
3030
if (transaction) {
31+
// eslint-disable-next-line deprecation/deprecation
3132
transaction.traceId = '86f39e84263a4de99c326acab3bfe3bd';
3233
}
3334
const headers = http.get('http://somewhere.not.sentry/').getHeaders();

dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-transaction-name/server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ app.use(cors());
3131
app.get('/test/express', (_req, res) => {
3232
const transaction = Sentry.getCurrentHub().getScope().getTransaction();
3333
if (transaction) {
34+
// eslint-disable-next-line deprecation/deprecation
3435
transaction.traceId = '86f39e84263a4de99c326acab3bfe3bd';
3536
transaction.setMetadata({ source: 'route' });
3637
}

packages/astro/test/server/meta.test.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,17 @@ import { vi } from 'vitest';
33

44
import { getTracingMetaTags, isValidBaggageString } from '../../src/server/meta';
55

6+
const TRACE_FLAG_SAMPLED = 0x1;
7+
68
const mockedSpan = {
79
isRecording: () => true,
8-
traceId: '12345678901234567890123456789012',
9-
spanId: '1234567890123456',
10+
spanContext: () => {
11+
return {
12+
traceId: '12345678901234567890123456789012',
13+
spanId: '1234567890123456',
14+
traceFlags: TRACE_FLAG_SAMPLED,
15+
};
16+
},
1017
transaction: {
1118
getDynamicSamplingContext: () => ({
1219
environment: 'production',
@@ -71,8 +78,13 @@ describe('getTracingMetaTags', () => {
7178
// @ts-expect-error - only passing a partial span object
7279
{
7380
isRecording: () => true,
74-
traceId: '12345678901234567890123456789012',
75-
spanId: '1234567890123456',
81+
spanContext: () => {
82+
return {
83+
traceId: '12345678901234567890123456789012',
84+
spanId: '1234567890123456',
85+
traceFlags: TRACE_FLAG_SAMPLED,
86+
};
87+
},
7688
transaction: undefined,
7789
},
7890
mockedScope,
@@ -84,7 +96,7 @@ describe('getTracingMetaTags', () => {
8496
});
8597
});
8698

87-
it('returns only the `sentry-trace` tag if no DSC is available', () => {
99+
it('returns only the `sentry-trace` tag if no DSC is available without a client', () => {
88100
vi.spyOn(SentryCore, 'getDynamicSamplingContextFromClient').mockReturnValueOnce({
89101
trace_id: '',
90102
public_key: undefined,
@@ -94,8 +106,13 @@ describe('getTracingMetaTags', () => {
94106
// @ts-expect-error - only passing a partial span object
95107
{
96108
isRecording: () => true,
97-
traceId: '12345678901234567890123456789012',
98-
spanId: '1234567890123456',
109+
spanContext: () => {
110+
return {
111+
traceId: '12345678901234567890123456789012',
112+
spanId: '1234567890123456',
113+
traceFlags: TRACE_FLAG_SAMPLED,
114+
};
115+
},
99116
transaction: undefined,
100117
},
101118
mockedScope,

packages/browser/test/unit/profiling/hubextensions.test.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import { JSDOM } from 'jsdom';
1010

1111
import { onProfilingStartRouteTransaction } from '../../../src';
1212

13+
// eslint-disable-next-line no-bitwise
14+
const TraceFlagSampled = 0x1 << 0;
15+
1316
// @ts-expect-error store a reference so we can reset it later
1417
const globalDocument = global.document;
1518
// @ts-expect-error store a reference so we can reset it later
@@ -67,9 +70,17 @@ describe('BrowserProfilingIntegration', () => {
6770
// @ts-expect-error force api to be undefined
6871
global.window.Profiler = undefined;
6972
// set sampled to true so that profiling does not early return
70-
const mockTransaction = { isRecording: () => true } as Transaction;
73+
const mockTransaction = {
74+
isRecording: () => true,
75+
spanContext: () => ({
76+
traceId: '12345678901234567890123456789012',
77+
spanId: '1234567890123456',
78+
traceFlags: TraceFlagSampled,
79+
}),
80+
} as Transaction;
7181
expect(() => onProfilingStartRouteTransaction(mockTransaction)).not.toThrow();
7282
});
83+
7384
it('does not throw if constructor throws', () => {
7485
const spy = jest.fn();
7586

@@ -80,8 +91,15 @@ describe('BrowserProfilingIntegration', () => {
8091
}
8192
}
8293

83-
// set isRecording to true so that profiling does not early return
84-
const mockTransaction = { isRecording: () => true } as Transaction;
94+
// set sampled to true so that profiling does not early return
95+
const mockTransaction = {
96+
isRecording: () => true,
97+
spanContext: () => ({
98+
traceId: '12345678901234567890123456789012',
99+
spanId: '1234567890123456',
100+
traceFlags: TraceFlagSampled,
101+
}),
102+
} as Transaction;
85103

86104
// @ts-expect-error override with our own constructor
87105
global.window.Profiler = Profiler;

packages/bun/test/integrations/bunserver.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ describe('Bun Serve Integration', () => {
7878
const SENTRY_BAGGAGE_HEADER = 'sentry-version=1.0,sentry-environment=production';
7979

8080
client.on('finishTransaction', transaction => {
81-
expect(transaction.traceId).toBe(TRACE_ID);
81+
expect(transaction.spanContext().traceId).toBe(TRACE_ID);
8282
expect(transaction.parentSpanId).toBe(PARENT_SPAN_ID);
8383
expect(transaction.isRecording()).toBe(true);
8484

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export { handleCallbackErrors } from './utils/handleCallbackErrors';
7878
export {
7979
spanToTraceHeader,
8080
spanToJSON,
81+
spanIsSampled,
8182
} from './utils/spanUtils';
8283
export { DEFAULT_ENVIRONMENT } from './constants';
8384
export { ModuleMetadata } from './integrations/metadata';

packages/core/src/tracing/idletransaction.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,18 @@ export class IdleTransactionSpanRecorder extends SpanRecorder {
4545
public add(span: Span): void {
4646
// We should make sure we do not push and pop activities for
4747
// the transaction that this span recorder belongs to.
48-
if (span.spanId !== this.transactionSpanId) {
48+
if (span.spanContext().spanId !== this.transactionSpanId) {
4949
// We patch span.end() to pop an activity after setting an endTimestamp.
5050
// eslint-disable-next-line @typescript-eslint/unbound-method
5151
const originalEnd = span.end;
5252
span.end = (...rest: unknown[]) => {
53-
this._popActivity(span.spanId);
53+
this._popActivity(span.spanContext().spanId);
5454
return originalEnd.apply(span, rest);
5555
};
5656

5757
// We should only push new activities if the span does not have an end timestamp.
5858
if (span.endTimestamp === undefined) {
59-
this._pushActivity(span.spanId);
59+
this._pushActivity(span.spanContext().spanId);
6060
}
6161
}
6262

@@ -123,7 +123,7 @@ export class IdleTransaction extends Transaction {
123123
if (_onScope) {
124124
// We set the transaction here on the scope so error events pick up the trace
125125
// context and attach it to the error.
126-
DEBUG_BUILD && logger.log(`Setting idle transaction on scope. Span ID: ${this.spanId}`);
126+
DEBUG_BUILD && logger.log(`Setting idle transaction on scope. Span ID: ${this.spanContext().spanId}`);
127127
_idleHub.getScope().setSpan(this);
128128
}
129129

@@ -158,7 +158,7 @@ export class IdleTransaction extends Transaction {
158158

159159
this.spanRecorder.spans = this.spanRecorder.spans.filter((span: Span) => {
160160
// If we are dealing with the transaction itself, we just return it
161-
if (span.spanId === this.spanId) {
161+
if (span.spanContext().spanId === this.spanContext().spanId) {
162162
return true;
163163
}
164164

@@ -233,7 +233,7 @@ export class IdleTransaction extends Transaction {
233233
this._popActivity(id);
234234
};
235235

236-
this.spanRecorder = new IdleTransactionSpanRecorder(pushActivity, popActivity, this.spanId, maxlen);
236+
this.spanRecorder = new IdleTransactionSpanRecorder(pushActivity, popActivity, this.spanContext().spanId, maxlen);
237237

238238
// Start heartbeat so that transactions do not run forever.
239239
DEBUG_BUILD && logger.log('Starting heartbeat');

0 commit comments

Comments
 (0)