Skip to content

Commit 324e5bf

Browse files
authored
feat(core): Deprecate Span.transaction in favor of getRootSpan (#10134)
Deprecate the `transaction` field on the `Span` interface and class. Instead, we'll store the span hierarchy in a different format external to the span class. In node-experimental, we use a WeakMap referencing the parent span as a data structure which we might need to generally do in v8. This, however, is a breaking change, so for now we deprecate the field but use it in the replacement utility function for v7.
1 parent 98979d8 commit 324e5bf

File tree

19 files changed

+100
-27
lines changed

19 files changed

+100
-27
lines changed

MIGRATION.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ In v8, the Span class is heavily reworked. The following properties & methods ar
152152
- `span.setTag()`: Use `span.setAttribute()` instead or set tags on the surrounding scope.
153153
- `span.setData()`: Use `span.setAttribute()` instead.
154154
- `span.instrumenter` This field was removed and will be replaced internally.
155+
- `span.transaction`: Use `getRootSpan` utility function instead.
155156
- `transaction.setContext()`: Set context on the surrounding scope instead.
156157

157158
## Deprecate `pushScope` & `popScope` in favor of `withScope`

packages/astro/src/server/meta.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
getDynamicSamplingContextFromClient,
33
getDynamicSamplingContextFromSpan,
4+
getRootSpan,
45
spanToTraceHeader,
56
} from '@sentry/core';
67
import type { Client, Scope, Span } from '@sentry/types';
@@ -32,12 +33,12 @@ export function getTracingMetaTags(
3233
client: Client | undefined,
3334
): { sentryTrace: string; baggage?: string } {
3435
const { dsc, sampled, traceId } = scope.getPropagationContext();
35-
const transaction = span?.transaction;
36+
const rootSpan = span && getRootSpan(span);
3637

3738
const sentryTrace = span ? spanToTraceHeader(span) : generateSentryTraceHeader(traceId, undefined, sampled);
3839

39-
const dynamicSamplingContext = transaction
40-
? getDynamicSamplingContextFromSpan(transaction)
40+
const dynamicSamplingContext = rootSpan
41+
? getDynamicSamplingContextFromSpan(rootSpan)
4142
: dsc
4243
? dsc
4344
: client

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export {
8181
spanToJSON,
8282
spanIsSampled,
8383
} from './utils/spanUtils';
84+
export { getRootSpan } from './utils/getRootSpan';
8485
export { DEFAULT_ENVIRONMENT } from './constants';
8586
export { ModuleMetadata } from './integrations/metadata';
8687
export { RequestData } from './integrations/requestdata';

packages/core/src/scope.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,9 @@ export class Scope implements ScopeInterface {
334334
// Often, this span (if it exists at all) will be a transaction, but it's not guaranteed to be. Regardless, it will
335335
// have a pointer to the currently-active transaction.
336336
const span = this._span;
337+
// Cannot replace with getRootSpan because getRootSpan returns a span, not a transaction
338+
// Also, this method will be removed anyway.
339+
// eslint-disable-next-line deprecation/deprecation
337340
return span && span.transaction;
338341
}
339342

packages/core/src/server-runtime-client.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
getDynamicSamplingContextFromClient,
2727
getDynamicSamplingContextFromSpan,
2828
} from './tracing';
29+
import { getRootSpan } from './utils/getRootSpan';
2930
import { spanToTraceContext } from './utils/spanUtils';
3031

3132
export interface ServerRuntimeClientOptions extends ClientOptions<BaseTransportOptions> {
@@ -262,7 +263,7 @@ export class ServerRuntimeClient<
262263
// eslint-disable-next-line deprecation/deprecation
263264
const span = scope.getSpan();
264265
if (span) {
265-
const samplingContext = span.transaction ? getDynamicSamplingContextFromSpan(span) : undefined;
266+
const samplingContext = getRootSpan(span) ? getDynamicSamplingContextFromSpan(span) : undefined;
266267
return [samplingContext, spanToTraceContext(span)];
267268
}
268269

packages/core/src/tracing/dynamicSamplingContext.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { dropUndefinedKeys } from '@sentry/utils';
33

44
import { DEFAULT_ENVIRONMENT } from '../constants';
55
import { getClient, getCurrentScope } from '../exports';
6+
import { getRootSpan } from '../utils/getRootSpan';
67
import { spanIsSampled, spanToJSON } from '../utils/spanUtils';
78

89
/**
@@ -54,9 +55,8 @@ export function getDynamicSamplingContextFromSpan(span: Span): Readonly<Partial<
5455
// passing emit=false here to only emit later once the DSC is actually populated
5556
const dsc = getDynamicSamplingContextFromClient(spanToJSON(span).trace_id || '', client, getCurrentScope());
5657

57-
// As long as we use `Transaction`s internally, this should be fine.
58-
// TODO: We need to replace this with a `getRootSpan(span)` function though
59-
const txn = span.transaction as TransactionWithV7FrozenDsc | undefined;
58+
// TODO (v8): Remove v7FrozenDsc as a Transaction will no longer have _frozenDynamicSamplingContext
59+
const txn = getRootSpan(span) as TransactionWithV7FrozenDsc | undefined;
6060
if (!txn) {
6161
return dsc;
6262
}

packages/core/src/tracing/span.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type {
1616
import { dropUndefinedKeys, logger, timestampInSeconds, uuid4 } from '@sentry/utils';
1717

1818
import { DEBUG_BUILD } from '../debug-build';
19+
import { getRootSpan } from '../utils/getRootSpan';
1920
import {
2021
TRACE_FLAG_NONE,
2122
TRACE_FLAG_SAMPLED,
@@ -105,6 +106,7 @@ export class Span implements SpanInterface {
105106

106107
/**
107108
* @inheritDoc
109+
* @deprecated Use top level `Sentry.getRootSpan()` instead
108110
*/
109111
public transaction?: Transaction;
110112

@@ -304,12 +306,16 @@ export class Span implements SpanInterface {
304306
childSpan.spanRecorder.add(childSpan);
305307
}
306308

307-
childSpan.transaction = this.transaction;
309+
const rootSpan = getRootSpan(this);
310+
// TODO: still set span.transaction here until we have a more permanent solution
311+
// Probably similarly to the weakmap we hold in node-experimental
312+
// eslint-disable-next-line deprecation/deprecation
313+
childSpan.transaction = rootSpan as Transaction;
308314

309-
if (DEBUG_BUILD && childSpan.transaction) {
315+
if (DEBUG_BUILD && rootSpan) {
310316
const opStr = (spanContext && spanContext.op) || '< unknown op >';
311317
const nameStr = spanToJSON(childSpan).description || '< unknown name >';
312-
const idStr = childSpan.transaction.spanContext().spanId;
318+
const idStr = rootSpan.spanContext().spanId;
313319

314320
const logMessage = `[Tracing] Starting '${opStr}' span on transaction '${nameStr}' (${idStr}).`;
315321
logger.log(logMessage);
@@ -416,11 +422,12 @@ export class Span implements SpanInterface {
416422

417423
/** @inheritdoc */
418424
public end(endTimestamp?: SpanTimeInput): void {
425+
const rootSpan = getRootSpan(this);
419426
if (
420427
DEBUG_BUILD &&
421428
// Don't call this for transactions
422-
this.transaction &&
423-
this.transaction.spanContext().spanId !== this._spanId
429+
rootSpan &&
430+
rootSpan.spanContext().spanId !== this._spanId
424431
) {
425432
const logMessage = this._logMessage;
426433
if (logMessage) {

packages/core/src/tracing/transaction.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ export class Transaction extends SpanClass implements TransactionInterface {
6666
this._trimEnd = transactionContext.trimEnd;
6767

6868
// this is because transactions are also spans, and spans have a transaction pointer
69+
// TODO (v8): Replace this with another way to set the root span
70+
// eslint-disable-next-line deprecation/deprecation
6971
this.transaction = this;
7072

7173
// If Dynamic Sampling Context is provided during the creation of the transaction, we freeze it as it usually means

packages/core/src/utils/applyScopeDataToEvent.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Breadcrumb, Event, PropagationContext, ScopeData, Span } from '@sentry/types';
22
import { arrayify } from '@sentry/utils';
33
import { getDynamicSamplingContextFromSpan } from '../tracing/dynamicSamplingContext';
4+
import { getRootSpan } from './getRootSpan';
45
import { spanToJSON, spanToTraceContext } from './spanUtils';
56

67
/**
@@ -174,13 +175,13 @@ function applySdkMetadataToEvent(
174175

175176
function applySpanToEvent(event: Event, span: Span): void {
176177
event.contexts = { trace: spanToTraceContext(span), ...event.contexts };
177-
const transaction = span.transaction;
178-
if (transaction) {
178+
const rootSpan = getRootSpan(span);
179+
if (rootSpan) {
179180
event.sdkProcessingMetadata = {
180181
dynamicSamplingContext: getDynamicSamplingContextFromSpan(span),
181182
...event.sdkProcessingMetadata,
182183
};
183-
const transactionName = spanToJSON(transaction).description;
184+
const transactionName = spanToJSON(rootSpan).description;
184185
if (transactionName) {
185186
event.tags = { transaction: transactionName, ...event.tags };
186187
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { Span } from '@sentry/types';
2+
3+
/**
4+
* Returns the root span of a given span.
5+
*
6+
* As long as we use `Transaction`s internally, the returned root span
7+
* will be a `Transaction` but be aware that this might change in the future.
8+
*
9+
* If the given span has no root span or transaction, `undefined` is returned.
10+
*/
11+
export function getRootSpan(span: Span): Span | undefined {
12+
// TODO (v8): Remove this check and just return span
13+
// eslint-disable-next-line deprecation/deprecation
14+
return span.transaction;
15+
}

0 commit comments

Comments
 (0)