Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MIGRATION.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -152,6 +152,7 @@ In v8, the Span class is heavily reworked. The following properties & methods ar
- `span.setTag()`: Use `span.setAttribute()` instead or set tags on the surrounding scope.
- `span.setData()`: Use `span.setAttribute()` instead.
- `span.instrumenter` This field was removed and will be replaced internally.
- `span.transaction`: Use `getRootSpan` utility function instead.
- `transaction.setContext()`: Set context on the surrounding scope instead.

## Deprecate `pushScope` & `popScope` in favor of `withScope`
Expand Down
7 changes: 4 additions & 3 deletions packages/astro/src/server/meta.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import {
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
getRootSpan,
spanToTraceHeader,
} from '@sentry/core';
import type { Client, Scope, Span } from '@sentry/types';
Expand DownExpand Up@@ -32,12 +33,12 @@ export function getTracingMetaTags(
client: Client | undefined,
): { sentryTrace: string; baggage?: string } {
const { dsc, sampled, traceId } = scope.getPropagationContext();
const transaction = span?.transaction;
const rootSpan = span && getRootSpan(span);

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

const dynamicSamplingContext = transaction
? getDynamicSamplingContextFromSpan(transaction)
const dynamicSamplingContext = rootSpan
? getDynamicSamplingContextFromSpan(rootSpan)
: dsc
? dsc
: client
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -81,6 +81,7 @@ export {
spanToJSON,
spanIsSampled,
} from './utils/spanUtils';
export { getRootSpan } from './utils/getRootSpan';
export { DEFAULT_ENVIRONMENT } from './constants';
export { ModuleMetadata } from './integrations/metadata';
export { RequestData } from './integrations/requestdata';
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/scope.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -334,6 +334,9 @@ export class Scope implements ScopeInterface {
// Often, this span (if it exists at all) will be a transaction, but it's not guaranteed to be. Regardless, it will
// have a pointer to the currently-active transaction.
const span = this._span;
// Cannot replace with getRootSpan because getRootSpan returns a span, not a transaction
// Also, this method will be removed anyway.
// eslint-disable-next-line deprecation/deprecation
return span && span.transaction;
}

Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/server-runtime-client.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@ import {
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
} from './tracing';
import { getRootSpan } from './utils/getRootSpan';
import { spanToTraceContext } from './utils/spanUtils';

export interface ServerRuntimeClientOptions extends ClientOptions<BaseTransportOptions> {
Expand DownExpand Up@@ -262,7 +263,7 @@ export class ServerRuntimeClient<
// eslint-disable-next-line deprecation/deprecation
const span = scope.getSpan();
if (span) {
const samplingContext = span.transaction ? getDynamicSamplingContextFromSpan(span) : undefined;
const samplingContext = getRootSpan(span) ? getDynamicSamplingContextFromSpan(span) : undefined;
return [samplingContext, spanToTraceContext(span)];
}

Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/tracing/dynamicSamplingContext.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import { dropUndefinedKeys } from '@sentry/utils';

import { DEFAULT_ENVIRONMENT } from '../constants';
import { getClient, getCurrentScope } from '../exports';
import { getRootSpan } from '../utils/getRootSpan';
import { spanIsSampled, spanToJSON } from '../utils/spanUtils';

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

// As long as we use `Transaction`s internally, this should be fine.
// TODO: We need to replace this with a `getRootSpan(span)` function though
const txn = span.transaction as TransactionWithV7FrozenDsc | undefined;
// TODO (v8): Remove v7FrozenDsc as a Transaction will no longer have _frozenDynamicSamplingContext
const txn = getRootSpan(span) as TransactionWithV7FrozenDsc | undefined;
if (!txn) {
return dsc;
}
Expand Down
17 changes: 12 additions & 5 deletions packages/core/src/tracing/span.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,6 +16,7 @@ import type {
import { dropUndefinedKeys, logger, timestampInSeconds, uuid4 } from '@sentry/utils';

import { DEBUG_BUILD } from '../debug-build';
import { getRootSpan } from '../utils/getRootSpan';
import {
TRACE_FLAG_NONE,
TRACE_FLAG_SAMPLED,
Expand DownExpand Up@@ -105,6 +106,7 @@ export class Span implements SpanInterface {

/**
* @inheritDoc
* @deprecated Use top level `Sentry.getRootSpan()` instead
*/
public transaction?: Transaction;

Expand DownExpand Up@@ -304,12 +306,16 @@ export class Span implements SpanInterface {
childSpan.spanRecorder.add(childSpan);
}

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

if (DEBUG_BUILD && childSpan.transaction) {
if (DEBUG_BUILD && rootSpan) {
const opStr = (spanContext && spanContext.op) || '< unknown op >';
const nameStr = spanToJSON(childSpan).description || '< unknown name >';
const idStr = childSpan.transaction.spanContext().spanId;
const idStr = rootSpan.spanContext().spanId;

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

/** @inheritdoc */
public end(endTimestamp?: SpanTimeInput): void {
const rootSpan = getRootSpan(this);
if (
DEBUG_BUILD &&
// Don't call this for transactions
this.transaction &&
this.transaction.spanContext().spanId !== this._spanId
rootSpan &&
rootSpan.spanContext().spanId !== this._spanId
) {
const logMessage = this._logMessage;
if (logMessage) {
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/tracing/transaction.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -66,6 +66,8 @@ export class Transaction extends SpanClass implements TransactionInterface {
this._trimEnd = transactionContext.trimEnd;

// this is because transactions are also spans, and spans have a transaction pointer
// TODO (v8): Replace this with another way to set the root span

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there even a todo for v8 here? Won't we get rid of the transaction class?

@Lms24Lms24Jan 10, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I mean we somehow still need to construct a transaction for the event payload so I guess it's not gonna vanish completely. We'll just need some way to set and find the root span/txn of a span in v8.

// eslint-disable-next-line deprecation/deprecation
this.transaction = this;

// If Dynamic Sampling Context is provided during the creation of the transaction, we freeze it as it usually means
Expand Down
7 changes: 4 additions & 3 deletions packages/core/src/utils/applyScopeDataToEvent.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import type { Breadcrumb, Event, PropagationContext, ScopeData, Span } from '@sentry/types';
import { arrayify } from '@sentry/utils';
import { getDynamicSamplingContextFromSpan } from '../tracing/dynamicSamplingContext';
import { getRootSpan } from './getRootSpan';
import { spanToJSON, spanToTraceContext } from './spanUtils';

/**
Expand DownExpand Up@@ -174,13 +175,13 @@ function applySdkMetadataToEvent(

function applySpanToEvent(event: Event, span: Span): void {
event.contexts = { trace: spanToTraceContext(span), ...event.contexts };
const transaction = span.transaction;
if (transaction) {
const rootSpan = getRootSpan(span);
if (rootSpan) {
event.sdkProcessingMetadata = {
dynamicSamplingContext: getDynamicSamplingContextFromSpan(span),
...event.sdkProcessingMetadata,
};
const transactionName = spanToJSON(transaction).description;
const transactionName = spanToJSON(rootSpan).description;
if (transactionName) {
event.tags = { transaction: transactionName, ...event.tags };
}
Expand Down
15 changes: 15 additions & 0 deletions packages/core/src/utils/getRootSpan.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
import type { Span } from '@sentry/types';

/**
* Returns the root span of a given span.
*
* As long as we use `Transaction`s internally, the returned root span
* will be a `Transaction` but be aware that this might change in the future.
*
* If the given span has no root span or transaction, `undefined` is returned.
*/
export function getRootSpan(span: Span): Span | undefined {
// TODO (v8): Remove this check and just return span
// eslint-disable-next-line deprecation/deprecation
return span.transaction;
}
11 changes: 5 additions & 6 deletions packages/core/test/lib/tracing/dynamicSamplingContext.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
});

test('returns the DSC provided during transaction creation', () => {
// eslint-disable-next-line deprecation/deprecation
// eslint-disable-next-line deprecation/deprecation -- using old API on purpose
const transaction = new Transaction({
name: 'tx',
metadata: { dynamicSamplingContext: { environment: 'myEnv' } },
Expand DownExpand Up@@ -68,7 +68,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
});

test('returns a new DSC, if no DSC was provided during transaction creation (via new Txn and deprecated metadata)', () => {
// eslint-disable-next-line deprecation/deprecation
// eslint-disable-next-line deprecation/deprecation -- using old API on purpose
const transaction = new Transaction({
name: 'tx',
metadata: {
Expand All@@ -92,7 +92,7 @@ describe('getDynamicSamplingContextFromSpan', () => {

describe('Including transaction name in DSC', () => {
test('is not included if transaction source is url', () => {
// eslint-disable-next-line deprecation/deprecation
// eslint-disable-next-line deprecation/deprecation -- using old API on purpose
const transaction = new Transaction({
name: 'tx',
metadata: {
Expand All@@ -109,8 +109,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
['is included if transaction source is parameterized route/url', 'route'],
['is included if transaction source is a custom name', 'custom'],
])('%s', (_: string, source) => {
// eslint-disable-next-line deprecation/deprecation
const transaction = new Transaction({
const transaction = startInactiveSpan({
name: 'tx',
metadata: {
...(source && { source: source as TransactionSource }),
Expand All@@ -120,7 +119,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
// Only setting the attribute manually because we're directly calling new Transaction()
transaction?.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);

const dsc = getDynamicSamplingContextFromSpan(transaction);
const dsc = getDynamicSamplingContextFromSpan(transaction!);

expect(dsc.transaction).toEqual('tx');
});
Expand Down
36 changes: 36 additions & 0 deletions packages/core/test/lib/utils/getRootSpan.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
import { Span, Transaction, getRootSpan } from '../../../src';

describe('getRootSpan', () => {
it('returns the root span of a span (Span)', () => {
const root = new Span({ name: 'test' });
// @ts-expect-error this is highly illegal and shouldn't happen IRL
// eslint-disable-next-line deprecation/deprecation
root.transaction = root;

// eslint-disable-next-line deprecation/deprecation
const childSpan = root.startChild({ name: 'child' });
expect(getRootSpan(childSpan)).toBe(root);
});

it('returns the root span of a span (Transaction)', () => {
// eslint-disable-next-line deprecation/deprecation
const root = new Transaction({ name: 'test' });

// eslint-disable-next-line deprecation/deprecation
const childSpan = root.startChild({ name: 'child' });
expect(getRootSpan(childSpan)).toBe(root);
});

it('returns the span itself if it is a root span', () => {
// eslint-disable-next-line deprecation/deprecation
const span = new Transaction({ name: 'test' });

expect(getRootSpan(span)).toBe(span);
});

it('returns undefined if span has no root span', () => {
const span = new Span({ name: 'test' });

expect(getRootSpan(span)).toBe(undefined);
});
});
4 changes: 2 additions & 2 deletions packages/opentelemetry-node/src/propagator.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import type { Baggage, Context, TextMapGetter, TextMapSetter } from '@opentelemetry/api';
import { TraceFlags, isSpanContextValid, propagation, trace } from '@opentelemetry/api';
import { W3CBaggagePropagator, isTracingSuppressed } from '@opentelemetry/core';
import { getDynamicSamplingContextFromSpan, spanToTraceHeader } from '@sentry/core';
import { getDynamicSamplingContextFromSpan, getRootSpan, spanToTraceHeader } from '@sentry/core';
import {
SENTRY_BAGGAGE_KEY_PREFIX,
baggageHeaderToDynamicSamplingContext,
Expand DownExpand Up@@ -35,7 +35,7 @@ export class SentryPropagator extends W3CBaggagePropagator {
if (span) {
setter.set(carrier, SENTRY_TRACE_HEADER, spanToTraceHeader(span));

if (span.transaction) {
if (getRootSpan(span)) {
const dynamicSamplingContext = getDynamicSamplingContextFromSpan(span);
baggage = Object.entries(dynamicSamplingContext).reduce<Baggage>((b, [dscKey, dscValue]) => {
if (dscValue) {
Expand Down
3 changes: 2 additions & 1 deletion packages/opentelemetry-node/src/utils/spanMap.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import { getRootSpan } from '@sentry/core';
import type { Span as SentrySpan } from '@sentry/types';

interface SpanMapEntry {
Expand DownExpand Up@@ -31,7 +32,7 @@ export function getSentrySpan(spanId: string): SentrySpan | undefined {
export function setSentrySpan(spanId: string, sentrySpan: SentrySpan): void {
let ref: SpanRefType = SPAN_REF_ROOT;

const rootSpanId = sentrySpan.transaction?.spanContext().spanId;
const rootSpanId = getRootSpan(sentrySpan)?.spanContext().spanId;

if (rootSpanId && rootSpanId !== spanId) {
const root = SPAN_MAP.get(rootSpanId);
Expand Down
1 change: 1 addition & 0 deletions packages/svelte/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,7 @@
},
"dependencies": {
"@sentry/browser": "7.93.0",
"@sentry/core": "7.93.0",
"@sentry/types": "7.93.0",
"@sentry/utils": "7.93.0",
"magic-string": "^0.30.0"
Expand Down
3 changes: 2 additions & 1 deletion packages/svelte/src/performance.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import type { Span, Transaction } from '@sentry/types';
import { afterUpdate, beforeUpdate, onMount } from 'svelte';
import { current_component } from 'svelte/internal';

import { getRootSpan } from '@sentry/core';
import { DEFAULT_COMPONENT_NAME, UI_SVELTE_INIT, UI_SVELTE_UPDATE } from './constants';
import type { TrackComponentOptions } from './types';

Expand DownExpand Up@@ -74,7 +75,7 @@ function recordUpdateSpans(componentName: string, initSpan?: Span): void {
// If we are initializing the component when the update span is started, we start it as child
// of the init span. Else, we start it as a child of the transaction.
const parentSpan =
initSpan && !initSpan.endTimestamp && initSpan.transaction === transaction ? initSpan : transaction;
initSpan && !initSpan.endTimestamp && getRootSpan(initSpan) === transaction ? initSpan : transaction;

// eslint-disable-next-line deprecation/deprecation
updateSpan = parentSpan.startChild({
Expand Down
3 changes: 2 additions & 1 deletion packages/tracing-internal/src/browser/request.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@ import {
getCurrentScope,
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
getRootSpan,
hasTracingEnabled,
spanToJSON,
spanToTraceHeader,
Expand DownExpand Up@@ -298,7 +299,7 @@ export function xhrCallback(

if (xhr.setRequestHeader && shouldAttachHeaders(sentryXhrData.url)) {
if (span) {
const transaction = span && span.transaction;
const transaction = span && getRootSpan(span);
const dynamicSamplingContext = transaction && getDynamicSamplingContextFromSpan(transaction);
const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
setHeaderOnXhr(xhr, spanToTraceHeader(span), sentryBaggageHeader);
Expand Down
3 changes: 2 additions & 1 deletion packages/tracing-internal/src/common/fetch.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ import {
getCurrentScope,
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
getRootSpan,
hasTracingEnabled,
spanToTraceHeader,
} from '@sentry/core';
Expand DownExpand Up@@ -134,7 +135,7 @@ export function addTracingHeadersToFetchRequest(
// eslint-disable-next-line deprecation/deprecation
const span = requestSpan || scope.getSpan();

const transaction = span && span.transaction;
const transaction = span && getRootSpan(span);

const { traceId, sampled, dsc } = scope.getPropagationContext();

Expand Down
1 change: 1 addition & 0 deletions packages/types/src/span.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -216,6 +216,7 @@ export interface Span extends SpanContext {

/**
* The transaction containing this span
* @deprecated Use top level `Sentry.getRootSpan()` instead
*/
transaction?: Transaction;

Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MIGRATION.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -152,6 +152,7 @@ In v8, the Span class is heavily reworked. The following properties & methods ar
- `span.setTag()`: Use `span.setAttribute()` instead or set tags on the surrounding scope.
- `span.setData()`: Use `span.setAttribute()` instead.
- `span.instrumenter` This field was removed and will be replaced internally.
- `span.transaction`: Use `getRootSpan` utility function instead.
- `transaction.setContext()`: Set context on the surrounding scope instead.

## Deprecate `pushScope` & `popScope` in favor of `withScope`
Expand Down
7 changes: 4 additions & 3 deletions packages/astro/src/server/meta.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import {
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
getRootSpan,
spanToTraceHeader,
} from '@sentry/core';
import type { Client, Scope, Span } from '@sentry/types';
Expand DownExpand Up@@ -32,12 +33,12 @@ export function getTracingMetaTags(
client: Client | undefined,
): { sentryTrace: string; baggage?: string } {
const { dsc, sampled, traceId } = scope.getPropagationContext();
const transaction = span?.transaction;
const rootSpan = span && getRootSpan(span);

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

const dynamicSamplingContext = transaction
? getDynamicSamplingContextFromSpan(transaction)
const dynamicSamplingContext = rootSpan
? getDynamicSamplingContextFromSpan(rootSpan)
: dsc
? dsc
: client
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -81,6 +81,7 @@ export {
spanToJSON,
spanIsSampled,
} from './utils/spanUtils';
export { getRootSpan } from './utils/getRootSpan';
export { DEFAULT_ENVIRONMENT } from './constants';
export { ModuleMetadata } from './integrations/metadata';
export { RequestData } from './integrations/requestdata';
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/scope.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -334,6 +334,9 @@ export class Scope implements ScopeInterface {
// Often, this span (if it exists at all) will be a transaction, but it's not guaranteed to be. Regardless, it will
// have a pointer to the currently-active transaction.
const span = this._span;
// Cannot replace with getRootSpan because getRootSpan returns a span, not a transaction
// Also, this method will be removed anyway.
// eslint-disable-next-line deprecation/deprecation
return span && span.transaction;
}

Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/server-runtime-client.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@ import {
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
} from './tracing';
import { getRootSpan } from './utils/getRootSpan';
import { spanToTraceContext } from './utils/spanUtils';

export interface ServerRuntimeClientOptions extends ClientOptions<BaseTransportOptions> {
Expand DownExpand Up@@ -262,7 +263,7 @@ export class ServerRuntimeClient<
// eslint-disable-next-line deprecation/deprecation
const span = scope.getSpan();
if (span) {
const samplingContext = span.transaction ? getDynamicSamplingContextFromSpan(span) : undefined;
const samplingContext = getRootSpan(span) ? getDynamicSamplingContextFromSpan(span) : undefined;
return [samplingContext, spanToTraceContext(span)];
}

Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/tracing/dynamicSamplingContext.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import { dropUndefinedKeys } from '@sentry/utils';

import { DEFAULT_ENVIRONMENT } from '../constants';
import { getClient, getCurrentScope } from '../exports';
import { getRootSpan } from '../utils/getRootSpan';
import { spanIsSampled, spanToJSON } from '../utils/spanUtils';

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

// As long as we use `Transaction`s internally, this should be fine.
// TODO: We need to replace this with a `getRootSpan(span)` function though
const txn = span.transaction as TransactionWithV7FrozenDsc | undefined;
// TODO (v8): Remove v7FrozenDsc as a Transaction will no longer have _frozenDynamicSamplingContext
const txn = getRootSpan(span) as TransactionWithV7FrozenDsc | undefined;
if (!txn) {
return dsc;
}
Expand Down
17 changes: 12 additions & 5 deletions packages/core/src/tracing/span.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,6 +16,7 @@ import type {
import { dropUndefinedKeys, logger, timestampInSeconds, uuid4 } from '@sentry/utils';

import { DEBUG_BUILD } from '../debug-build';
import { getRootSpan } from '../utils/getRootSpan';
import {
TRACE_FLAG_NONE,
TRACE_FLAG_SAMPLED,
Expand DownExpand Up@@ -105,6 +106,7 @@ export class Span implements SpanInterface {

/**
* @inheritDoc
* @deprecated Use top level `Sentry.getRootSpan()` instead
*/
public transaction?: Transaction;

Expand DownExpand Up@@ -304,12 +306,16 @@ export class Span implements SpanInterface {
childSpan.spanRecorder.add(childSpan);
}

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

if (DEBUG_BUILD && childSpan.transaction) {
if (DEBUG_BUILD && rootSpan) {
const opStr = (spanContext && spanContext.op) || '< unknown op >';
const nameStr = spanToJSON(childSpan).description || '< unknown name >';
const idStr = childSpan.transaction.spanContext().spanId;
const idStr = rootSpan.spanContext().spanId;

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

/** @inheritdoc */
public end(endTimestamp?: SpanTimeInput): void {
const rootSpan = getRootSpan(this);
if (
DEBUG_BUILD &&
// Don't call this for transactions
this.transaction &&
this.transaction.spanContext().spanId !== this._spanId
rootSpan &&
rootSpan.spanContext().spanId !== this._spanId
) {
const logMessage = this._logMessage;
if (logMessage) {
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/tracing/transaction.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -66,6 +66,8 @@ export class Transaction extends SpanClass implements TransactionInterface {
this._trimEnd = transactionContext.trimEnd;

// this is because transactions are also spans, and spans have a transaction pointer
// TODO (v8): Replace this with another way to set the root span

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there even a todo for v8 here? Won't we get rid of the transaction class?

@Lms24Lms24Jan 10, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I mean we somehow still need to construct a transaction for the event payload so I guess it's not gonna vanish completely. We'll just need some way to set and find the root span/txn of a span in v8.

// eslint-disable-next-line deprecation/deprecation
this.transaction = this;

// If Dynamic Sampling Context is provided during the creation of the transaction, we freeze it as it usually means
Expand Down
7 changes: 4 additions & 3 deletions packages/core/src/utils/applyScopeDataToEvent.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import type { Breadcrumb, Event, PropagationContext, ScopeData, Span } from '@sentry/types';
import { arrayify } from '@sentry/utils';
import { getDynamicSamplingContextFromSpan } from '../tracing/dynamicSamplingContext';
import { getRootSpan } from './getRootSpan';
import { spanToJSON, spanToTraceContext } from './spanUtils';

/**
Expand DownExpand Up@@ -174,13 +175,13 @@ function applySdkMetadataToEvent(

function applySpanToEvent(event: Event, span: Span): void {
event.contexts = { trace: spanToTraceContext(span), ...event.contexts };
const transaction = span.transaction;
if (transaction) {
const rootSpan = getRootSpan(span);
if (rootSpan) {
event.sdkProcessingMetadata = {
dynamicSamplingContext: getDynamicSamplingContextFromSpan(span),
...event.sdkProcessingMetadata,
};
const transactionName = spanToJSON(transaction).description;
const transactionName = spanToJSON(rootSpan).description;
if (transactionName) {
event.tags = { transaction: transactionName, ...event.tags };
}
Expand Down
15 changes: 15 additions & 0 deletions packages/core/src/utils/getRootSpan.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
import type { Span } from '@sentry/types';

/**
* Returns the root span of a given span.
*
* As long as we use `Transaction`s internally, the returned root span
* will be a `Transaction` but be aware that this might change in the future.
*
* If the given span has no root span or transaction, `undefined` is returned.
*/
export function getRootSpan(span: Span): Span | undefined {
// TODO (v8): Remove this check and just return span
// eslint-disable-next-line deprecation/deprecation
return span.transaction;
}
11 changes: 5 additions & 6 deletions packages/core/test/lib/tracing/dynamicSamplingContext.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
});

test('returns the DSC provided during transaction creation', () => {
// eslint-disable-next-line deprecation/deprecation
// eslint-disable-next-line deprecation/deprecation -- using old API on purpose
const transaction = new Transaction({
name: 'tx',
metadata: { dynamicSamplingContext: { environment: 'myEnv' } },
Expand DownExpand Up@@ -68,7 +68,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
});

test('returns a new DSC, if no DSC was provided during transaction creation (via new Txn and deprecated metadata)', () => {
// eslint-disable-next-line deprecation/deprecation
// eslint-disable-next-line deprecation/deprecation -- using old API on purpose
const transaction = new Transaction({
name: 'tx',
metadata: {
Expand All@@ -92,7 +92,7 @@ describe('getDynamicSamplingContextFromSpan', () => {

describe('Including transaction name in DSC', () => {
test('is not included if transaction source is url', () => {
// eslint-disable-next-line deprecation/deprecation
// eslint-disable-next-line deprecation/deprecation -- using old API on purpose
const transaction = new Transaction({
name: 'tx',
metadata: {
Expand All@@ -109,8 +109,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
['is included if transaction source is parameterized route/url', 'route'],
['is included if transaction source is a custom name', 'custom'],
])('%s', (_: string, source) => {
// eslint-disable-next-line deprecation/deprecation
const transaction = new Transaction({
const transaction = startInactiveSpan({
name: 'tx',
metadata: {
...(source && { source: source as TransactionSource }),
Expand All@@ -120,7 +119,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
// Only setting the attribute manually because we're directly calling new Transaction()
transaction?.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);

const dsc = getDynamicSamplingContextFromSpan(transaction);
const dsc = getDynamicSamplingContextFromSpan(transaction!);

expect(dsc.transaction).toEqual('tx');
});
Expand Down
36 changes: 36 additions & 0 deletions packages/core/test/lib/utils/getRootSpan.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
import { Span, Transaction, getRootSpan } from '../../../src';

describe('getRootSpan', () => {
it('returns the root span of a span (Span)', () => {
const root = new Span({ name: 'test' });
// @ts-expect-error this is highly illegal and shouldn't happen IRL
// eslint-disable-next-line deprecation/deprecation
root.transaction = root;

// eslint-disable-next-line deprecation/deprecation
const childSpan = root.startChild({ name: 'child' });
expect(getRootSpan(childSpan)).toBe(root);
});

it('returns the root span of a span (Transaction)', () => {
// eslint-disable-next-line deprecation/deprecation
const root = new Transaction({ name: 'test' });

// eslint-disable-next-line deprecation/deprecation
const childSpan = root.startChild({ name: 'child' });
expect(getRootSpan(childSpan)).toBe(root);
});

it('returns the span itself if it is a root span', () => {
// eslint-disable-next-line deprecation/deprecation
const span = new Transaction({ name: 'test' });

expect(getRootSpan(span)).toBe(span);
});

it('returns undefined if span has no root span', () => {
const span = new Span({ name: 'test' });

expect(getRootSpan(span)).toBe(undefined);
});
});
4 changes: 2 additions & 2 deletions packages/opentelemetry-node/src/propagator.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import type { Baggage, Context, TextMapGetter, TextMapSetter } from '@opentelemetry/api';
import { TraceFlags, isSpanContextValid, propagation, trace } from '@opentelemetry/api';
import { W3CBaggagePropagator, isTracingSuppressed } from '@opentelemetry/core';
import { getDynamicSamplingContextFromSpan, spanToTraceHeader } from '@sentry/core';
import { getDynamicSamplingContextFromSpan, getRootSpan, spanToTraceHeader } from '@sentry/core';
import {
SENTRY_BAGGAGE_KEY_PREFIX,
baggageHeaderToDynamicSamplingContext,
Expand DownExpand Up@@ -35,7 +35,7 @@ export class SentryPropagator extends W3CBaggagePropagator {
if (span) {
setter.set(carrier, SENTRY_TRACE_HEADER, spanToTraceHeader(span));

if (span.transaction) {
if (getRootSpan(span)) {
const dynamicSamplingContext = getDynamicSamplingContextFromSpan(span);
baggage = Object.entries(dynamicSamplingContext).reduce<Baggage>((b, [dscKey, dscValue]) => {
if (dscValue) {
Expand Down
3 changes: 2 additions & 1 deletion packages/opentelemetry-node/src/utils/spanMap.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import { getRootSpan } from '@sentry/core';
import type { Span as SentrySpan } from '@sentry/types';

interface SpanMapEntry {
Expand DownExpand Up@@ -31,7 +32,7 @@ export function getSentrySpan(spanId: string): SentrySpan | undefined {
export function setSentrySpan(spanId: string, sentrySpan: SentrySpan): void {
let ref: SpanRefType = SPAN_REF_ROOT;

const rootSpanId = sentrySpan.transaction?.spanContext().spanId;
const rootSpanId = getRootSpan(sentrySpan)?.spanContext().spanId;

if (rootSpanId && rootSpanId !== spanId) {
const root = SPAN_MAP.get(rootSpanId);
Expand Down
1 change: 1 addition & 0 deletions packages/svelte/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,7 @@
},
"dependencies": {
"@sentry/browser": "7.93.0",
"@sentry/core": "7.93.0",
"@sentry/types": "7.93.0",
"@sentry/utils": "7.93.0",
"magic-string": "^0.30.0"
Expand Down
3 changes: 2 additions & 1 deletion packages/svelte/src/performance.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import type { Span, Transaction } from '@sentry/types';
import { afterUpdate, beforeUpdate, onMount } from 'svelte';
import { current_component } from 'svelte/internal';

import { getRootSpan } from '@sentry/core';
import { DEFAULT_COMPONENT_NAME, UI_SVELTE_INIT, UI_SVELTE_UPDATE } from './constants';
import type { TrackComponentOptions } from './types';

Expand DownExpand Up@@ -74,7 +75,7 @@ function recordUpdateSpans(componentName: string, initSpan?: Span): void {
// If we are initializing the component when the update span is started, we start it as child
// of the init span. Else, we start it as a child of the transaction.
const parentSpan =
initSpan && !initSpan.endTimestamp && initSpan.transaction === transaction ? initSpan : transaction;
initSpan && !initSpan.endTimestamp && getRootSpan(initSpan) === transaction ? initSpan : transaction;

// eslint-disable-next-line deprecation/deprecation
updateSpan = parentSpan.startChild({
Expand Down
3 changes: 2 additions & 1 deletion packages/tracing-internal/src/browser/request.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@ import {
getCurrentScope,
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
getRootSpan,
hasTracingEnabled,
spanToJSON,
spanToTraceHeader,
Expand DownExpand Up@@ -298,7 +299,7 @@ export function xhrCallback(

if (xhr.setRequestHeader && shouldAttachHeaders(sentryXhrData.url)) {
if (span) {
const transaction = span && span.transaction;
const transaction = span && getRootSpan(span);
const dynamicSamplingContext = transaction && getDynamicSamplingContextFromSpan(transaction);
const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
setHeaderOnXhr(xhr, spanToTraceHeader(span), sentryBaggageHeader);
Expand Down
3 changes: 2 additions & 1 deletion packages/tracing-internal/src/common/fetch.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ import {
getCurrentScope,
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
getRootSpan,
hasTracingEnabled,
spanToTraceHeader,
} from '@sentry/core';
Expand DownExpand Up@@ -134,7 +135,7 @@ export function addTracingHeadersToFetchRequest(
// eslint-disable-next-line deprecation/deprecation
const span = requestSpan || scope.getSpan();

const transaction = span && span.transaction;
const transaction = span && getRootSpan(span);

const { traceId, sampled, dsc } = scope.getPropagationContext();

Expand Down
1 change: 1 addition & 0 deletions packages/types/src/span.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -216,6 +216,7 @@ export interface Span extends SpanContext {

/**
* The transaction containing this span
* @deprecated Use top level `Sentry.getRootSpan()` instead
*/
transaction?: Transaction;

Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MIGRATION.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -152,6 +152,7 @@ In v8, the Span class is heavily reworked. The following properties & methods ar
- `span.setTag()`: Use `span.setAttribute()` instead or set tags on the surrounding scope.
- `span.setData()`: Use `span.setAttribute()` instead.
- `span.instrumenter` This field was removed and will be replaced internally.
- `span.transaction`: Use `getRootSpan` utility function instead.
- `transaction.setContext()`: Set context on the surrounding scope instead.

## Deprecate `pushScope` & `popScope` in favor of `withScope`
Expand Down
7 changes: 4 additions & 3 deletions packages/astro/src/server/meta.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import {
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
getRootSpan,
spanToTraceHeader,
} from '@sentry/core';
import type { Client, Scope, Span } from '@sentry/types';
Expand DownExpand Up@@ -32,12 +33,12 @@ export function getTracingMetaTags(
client: Client | undefined,
): { sentryTrace: string; baggage?: string } {
const { dsc, sampled, traceId } = scope.getPropagationContext();
const transaction = span?.transaction;
const rootSpan = span && getRootSpan(span);

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

const dynamicSamplingContext = transaction
? getDynamicSamplingContextFromSpan(transaction)
const dynamicSamplingContext = rootSpan
? getDynamicSamplingContextFromSpan(rootSpan)
: dsc
? dsc
: client
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -81,6 +81,7 @@ export {
spanToJSON,
spanIsSampled,
} from './utils/spanUtils';
export { getRootSpan } from './utils/getRootSpan';
export { DEFAULT_ENVIRONMENT } from './constants';
export { ModuleMetadata } from './integrations/metadata';
export { RequestData } from './integrations/requestdata';
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/scope.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -334,6 +334,9 @@ export class Scope implements ScopeInterface {
// Often, this span (if it exists at all) will be a transaction, but it's not guaranteed to be. Regardless, it will
// have a pointer to the currently-active transaction.
const span = this._span;
// Cannot replace with getRootSpan because getRootSpan returns a span, not a transaction
// Also, this method will be removed anyway.
// eslint-disable-next-line deprecation/deprecation
return span && span.transaction;
}

Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/server-runtime-client.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@ import {
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
} from './tracing';
import { getRootSpan } from './utils/getRootSpan';
import { spanToTraceContext } from './utils/spanUtils';

export interface ServerRuntimeClientOptions extends ClientOptions<BaseTransportOptions> {
Expand DownExpand Up@@ -262,7 +263,7 @@ export class ServerRuntimeClient<
// eslint-disable-next-line deprecation/deprecation
const span = scope.getSpan();
if (span) {
const samplingContext = span.transaction ? getDynamicSamplingContextFromSpan(span) : undefined;
const samplingContext = getRootSpan(span) ? getDynamicSamplingContextFromSpan(span) : undefined;
return [samplingContext, spanToTraceContext(span)];
}

Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/tracing/dynamicSamplingContext.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import { dropUndefinedKeys } from '@sentry/utils';

import { DEFAULT_ENVIRONMENT } from '../constants';
import { getClient, getCurrentScope } from '../exports';
import { getRootSpan } from '../utils/getRootSpan';
import { spanIsSampled, spanToJSON } from '../utils/spanUtils';

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

// As long as we use `Transaction`s internally, this should be fine.
// TODO: We need to replace this with a `getRootSpan(span)` function though
const txn = span.transaction as TransactionWithV7FrozenDsc | undefined;
// TODO (v8): Remove v7FrozenDsc as a Transaction will no longer have _frozenDynamicSamplingContext
const txn = getRootSpan(span) as TransactionWithV7FrozenDsc | undefined;
if (!txn) {
return dsc;
}
Expand Down
17 changes: 12 additions & 5 deletions packages/core/src/tracing/span.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,6 +16,7 @@ import type {
import { dropUndefinedKeys, logger, timestampInSeconds, uuid4 } from '@sentry/utils';

import { DEBUG_BUILD } from '../debug-build';
import { getRootSpan } from '../utils/getRootSpan';
import {
TRACE_FLAG_NONE,
TRACE_FLAG_SAMPLED,
Expand DownExpand Up@@ -105,6 +106,7 @@ export class Span implements SpanInterface {

/**
* @inheritDoc
* @deprecated Use top level `Sentry.getRootSpan()` instead
*/
public transaction?: Transaction;

Expand DownExpand Up@@ -304,12 +306,16 @@ export class Span implements SpanInterface {
childSpan.spanRecorder.add(childSpan);
}

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

if (DEBUG_BUILD && childSpan.transaction) {
if (DEBUG_BUILD && rootSpan) {
const opStr = (spanContext && spanContext.op) || '< unknown op >';
const nameStr = spanToJSON(childSpan).description || '< unknown name >';
const idStr = childSpan.transaction.spanContext().spanId;
const idStr = rootSpan.spanContext().spanId;

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

/** @inheritdoc */
public end(endTimestamp?: SpanTimeInput): void {
const rootSpan = getRootSpan(this);
if (
DEBUG_BUILD &&
// Don't call this for transactions
this.transaction &&
this.transaction.spanContext().spanId !== this._spanId
rootSpan &&
rootSpan.spanContext().spanId !== this._spanId
) {
const logMessage = this._logMessage;
if (logMessage) {
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/tracing/transaction.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -66,6 +66,8 @@ export class Transaction extends SpanClass implements TransactionInterface {
this._trimEnd = transactionContext.trimEnd;

// this is because transactions are also spans, and spans have a transaction pointer
// TODO (v8): Replace this with another way to set the root span

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there even a todo for v8 here? Won't we get rid of the transaction class?

@Lms24Lms24Jan 10, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I mean we somehow still need to construct a transaction for the event payload so I guess it's not gonna vanish completely. We'll just need some way to set and find the root span/txn of a span in v8.

// eslint-disable-next-line deprecation/deprecation
this.transaction = this;

// If Dynamic Sampling Context is provided during the creation of the transaction, we freeze it as it usually means
Expand Down
7 changes: 4 additions & 3 deletions packages/core/src/utils/applyScopeDataToEvent.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import type { Breadcrumb, Event, PropagationContext, ScopeData, Span } from '@sentry/types';
import { arrayify } from '@sentry/utils';
import { getDynamicSamplingContextFromSpan } from '../tracing/dynamicSamplingContext';
import { getRootSpan } from './getRootSpan';
import { spanToJSON, spanToTraceContext } from './spanUtils';

/**
Expand DownExpand Up@@ -174,13 +175,13 @@ function applySdkMetadataToEvent(

function applySpanToEvent(event: Event, span: Span): void {
event.contexts = { trace: spanToTraceContext(span), ...event.contexts };
const transaction = span.transaction;
if (transaction) {
const rootSpan = getRootSpan(span);
if (rootSpan) {
event.sdkProcessingMetadata = {
dynamicSamplingContext: getDynamicSamplingContextFromSpan(span),
...event.sdkProcessingMetadata,
};
const transactionName = spanToJSON(transaction).description;
const transactionName = spanToJSON(rootSpan).description;
if (transactionName) {
event.tags = { transaction: transactionName, ...event.tags };
}
Expand Down
15 changes: 15 additions & 0 deletions packages/core/src/utils/getRootSpan.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
import type { Span } from '@sentry/types';

/**
* Returns the root span of a given span.
*
* As long as we use `Transaction`s internally, the returned root span
* will be a `Transaction` but be aware that this might change in the future.
*
* If the given span has no root span or transaction, `undefined` is returned.
*/
export function getRootSpan(span: Span): Span | undefined {
// TODO (v8): Remove this check and just return span
// eslint-disable-next-line deprecation/deprecation
return span.transaction;
}
11 changes: 5 additions & 6 deletions packages/core/test/lib/tracing/dynamicSamplingContext.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
});

test('returns the DSC provided during transaction creation', () => {
// eslint-disable-next-line deprecation/deprecation
// eslint-disable-next-line deprecation/deprecation -- using old API on purpose
const transaction = new Transaction({
name: 'tx',
metadata: { dynamicSamplingContext: { environment: 'myEnv' } },
Expand DownExpand Up@@ -68,7 +68,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
});

test('returns a new DSC, if no DSC was provided during transaction creation (via new Txn and deprecated metadata)', () => {
// eslint-disable-next-line deprecation/deprecation
// eslint-disable-next-line deprecation/deprecation -- using old API on purpose
const transaction = new Transaction({
name: 'tx',
metadata: {
Expand All@@ -92,7 +92,7 @@ describe('getDynamicSamplingContextFromSpan', () => {

describe('Including transaction name in DSC', () => {
test('is not included if transaction source is url', () => {
// eslint-disable-next-line deprecation/deprecation
// eslint-disable-next-line deprecation/deprecation -- using old API on purpose
const transaction = new Transaction({
name: 'tx',
metadata: {
Expand All@@ -109,8 +109,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
['is included if transaction source is parameterized route/url', 'route'],
['is included if transaction source is a custom name', 'custom'],
])('%s', (_: string, source) => {
// eslint-disable-next-line deprecation/deprecation
const transaction = new Transaction({
const transaction = startInactiveSpan({
name: 'tx',
metadata: {
...(source && { source: source as TransactionSource }),
Expand All@@ -120,7 +119,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
// Only setting the attribute manually because we're directly calling new Transaction()
transaction?.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);

const dsc = getDynamicSamplingContextFromSpan(transaction);
const dsc = getDynamicSamplingContextFromSpan(transaction!);

expect(dsc.transaction).toEqual('tx');
});
Expand Down
36 changes: 36 additions & 0 deletions packages/core/test/lib/utils/getRootSpan.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
import { Span, Transaction, getRootSpan } from '../../../src';

describe('getRootSpan', () => {
it('returns the root span of a span (Span)', () => {
const root = new Span({ name: 'test' });
// @ts-expect-error this is highly illegal and shouldn't happen IRL
// eslint-disable-next-line deprecation/deprecation
root.transaction = root;

// eslint-disable-next-line deprecation/deprecation
const childSpan = root.startChild({ name: 'child' });
expect(getRootSpan(childSpan)).toBe(root);
});

it('returns the root span of a span (Transaction)', () => {
// eslint-disable-next-line deprecation/deprecation
const root = new Transaction({ name: 'test' });

// eslint-disable-next-line deprecation/deprecation
const childSpan = root.startChild({ name: 'child' });
expect(getRootSpan(childSpan)).toBe(root);
});

it('returns the span itself if it is a root span', () => {
// eslint-disable-next-line deprecation/deprecation
const span = new Transaction({ name: 'test' });

expect(getRootSpan(span)).toBe(span);
});

it('returns undefined if span has no root span', () => {
const span = new Span({ name: 'test' });

expect(getRootSpan(span)).toBe(undefined);
});
});
4 changes: 2 additions & 2 deletions packages/opentelemetry-node/src/propagator.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import type { Baggage, Context, TextMapGetter, TextMapSetter } from '@opentelemetry/api';
import { TraceFlags, isSpanContextValid, propagation, trace } from '@opentelemetry/api';
import { W3CBaggagePropagator, isTracingSuppressed } from '@opentelemetry/core';
import { getDynamicSamplingContextFromSpan, spanToTraceHeader } from '@sentry/core';
import { getDynamicSamplingContextFromSpan, getRootSpan, spanToTraceHeader } from '@sentry/core';
import {
SENTRY_BAGGAGE_KEY_PREFIX,
baggageHeaderToDynamicSamplingContext,
Expand DownExpand Up@@ -35,7 +35,7 @@ export class SentryPropagator extends W3CBaggagePropagator {
if (span) {
setter.set(carrier, SENTRY_TRACE_HEADER, spanToTraceHeader(span));

if (span.transaction) {
if (getRootSpan(span)) {
const dynamicSamplingContext = getDynamicSamplingContextFromSpan(span);
baggage = Object.entries(dynamicSamplingContext).reduce<Baggage>((b, [dscKey, dscValue]) => {
if (dscValue) {
Expand Down
3 changes: 2 additions & 1 deletion packages/opentelemetry-node/src/utils/spanMap.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import { getRootSpan } from '@sentry/core';
import type { Span as SentrySpan } from '@sentry/types';

interface SpanMapEntry {
Expand DownExpand Up@@ -31,7 +32,7 @@ export function getSentrySpan(spanId: string): SentrySpan | undefined {
export function setSentrySpan(spanId: string, sentrySpan: SentrySpan): void {
let ref: SpanRefType = SPAN_REF_ROOT;

const rootSpanId = sentrySpan.transaction?.spanContext().spanId;
const rootSpanId = getRootSpan(sentrySpan)?.spanContext().spanId;

if (rootSpanId && rootSpanId !== spanId) {
const root = SPAN_MAP.get(rootSpanId);
Expand Down
1 change: 1 addition & 0 deletions packages/svelte/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,7 @@
},
"dependencies": {
"@sentry/browser": "7.93.0",
"@sentry/core": "7.93.0",
"@sentry/types": "7.93.0",
"@sentry/utils": "7.93.0",
"magic-string": "^0.30.0"
Expand Down
3 changes: 2 additions & 1 deletion packages/svelte/src/performance.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import type { Span, Transaction } from '@sentry/types';
import { afterUpdate, beforeUpdate, onMount } from 'svelte';
import { current_component } from 'svelte/internal';

import { getRootSpan } from '@sentry/core';
import { DEFAULT_COMPONENT_NAME, UI_SVELTE_INIT, UI_SVELTE_UPDATE } from './constants';
import type { TrackComponentOptions } from './types';

Expand DownExpand Up@@ -74,7 +75,7 @@ function recordUpdateSpans(componentName: string, initSpan?: Span): void {
// If we are initializing the component when the update span is started, we start it as child
// of the init span. Else, we start it as a child of the transaction.
const parentSpan =
initSpan && !initSpan.endTimestamp && initSpan.transaction === transaction ? initSpan : transaction;
initSpan && !initSpan.endTimestamp && getRootSpan(initSpan) === transaction ? initSpan : transaction;

// eslint-disable-next-line deprecation/deprecation
updateSpan = parentSpan.startChild({
Expand Down
3 changes: 2 additions & 1 deletion packages/tracing-internal/src/browser/request.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@ import {
getCurrentScope,
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
getRootSpan,
hasTracingEnabled,
spanToJSON,
spanToTraceHeader,
Expand DownExpand Up@@ -298,7 +299,7 @@ export function xhrCallback(

if (xhr.setRequestHeader && shouldAttachHeaders(sentryXhrData.url)) {
if (span) {
const transaction = span && span.transaction;
const transaction = span && getRootSpan(span);
const dynamicSamplingContext = transaction && getDynamicSamplingContextFromSpan(transaction);
const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
setHeaderOnXhr(xhr, spanToTraceHeader(span), sentryBaggageHeader);
Expand Down
3 changes: 2 additions & 1 deletion packages/tracing-internal/src/common/fetch.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ import {
getCurrentScope,
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
getRootSpan,
hasTracingEnabled,
spanToTraceHeader,
} from '@sentry/core';
Expand DownExpand Up@@ -134,7 +135,7 @@ export function addTracingHeadersToFetchRequest(
// eslint-disable-next-line deprecation/deprecation
const span = requestSpan || scope.getSpan();

const transaction = span && span.transaction;
const transaction = span && getRootSpan(span);

const { traceId, sampled, dsc } = scope.getPropagationContext();

Expand Down
1 change: 1 addition & 0 deletions packages/types/src/span.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -216,6 +216,7 @@ export interface Span extends SpanContext {

/**
* The transaction containing this span
* @deprecated Use top level `Sentry.getRootSpan()` instead
*/
transaction?: Transaction;

Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MIGRATION.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -152,6 +152,7 @@ In v8, the Span class is heavily reworked. The following properties & methods ar
- `span.setTag()`: Use `span.setAttribute()` instead or set tags on the surrounding scope.
- `span.setData()`: Use `span.setAttribute()` instead.
- `span.instrumenter` This field was removed and will be replaced internally.
- `span.transaction`: Use `getRootSpan` utility function instead.
- `transaction.setContext()`: Set context on the surrounding scope instead.

## Deprecate `pushScope` & `popScope` in favor of `withScope`
Expand Down
7 changes: 4 additions & 3 deletions packages/astro/src/server/meta.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import {
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
getRootSpan,
spanToTraceHeader,
} from '@sentry/core';
import type { Client, Scope, Span } from '@sentry/types';
Expand DownExpand Up@@ -32,12 +33,12 @@ export function getTracingMetaTags(
client: Client | undefined,
): { sentryTrace: string; baggage?: string } {
const { dsc, sampled, traceId } = scope.getPropagationContext();
const transaction = span?.transaction;
const rootSpan = span && getRootSpan(span);

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

const dynamicSamplingContext = transaction
? getDynamicSamplingContextFromSpan(transaction)
const dynamicSamplingContext = rootSpan
? getDynamicSamplingContextFromSpan(rootSpan)
: dsc
? dsc
: client
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -81,6 +81,7 @@ export {
spanToJSON,
spanIsSampled,
} from './utils/spanUtils';
export { getRootSpan } from './utils/getRootSpan';
export { DEFAULT_ENVIRONMENT } from './constants';
export { ModuleMetadata } from './integrations/metadata';
export { RequestData } from './integrations/requestdata';
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/scope.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -334,6 +334,9 @@ export class Scope implements ScopeInterface {
// Often, this span (if it exists at all) will be a transaction, but it's not guaranteed to be. Regardless, it will
// have a pointer to the currently-active transaction.
const span = this._span;
// Cannot replace with getRootSpan because getRootSpan returns a span, not a transaction
// Also, this method will be removed anyway.
// eslint-disable-next-line deprecation/deprecation
return span && span.transaction;
}

Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/server-runtime-client.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@ import {
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
} from './tracing';
import { getRootSpan } from './utils/getRootSpan';
import { spanToTraceContext } from './utils/spanUtils';

export interface ServerRuntimeClientOptions extends ClientOptions<BaseTransportOptions> {
Expand DownExpand Up@@ -262,7 +263,7 @@ export class ServerRuntimeClient<
// eslint-disable-next-line deprecation/deprecation
const span = scope.getSpan();
if (span) {
const samplingContext = span.transaction ? getDynamicSamplingContextFromSpan(span) : undefined;
const samplingContext = getRootSpan(span) ? getDynamicSamplingContextFromSpan(span) : undefined;
return [samplingContext, spanToTraceContext(span)];
}

Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/tracing/dynamicSamplingContext.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import { dropUndefinedKeys } from '@sentry/utils';

import { DEFAULT_ENVIRONMENT } from '../constants';
import { getClient, getCurrentScope } from '../exports';
import { getRootSpan } from '../utils/getRootSpan';
import { spanIsSampled, spanToJSON } from '../utils/spanUtils';

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

// As long as we use `Transaction`s internally, this should be fine.
// TODO: We need to replace this with a `getRootSpan(span)` function though
const txn = span.transaction as TransactionWithV7FrozenDsc | undefined;
// TODO (v8): Remove v7FrozenDsc as a Transaction will no longer have _frozenDynamicSamplingContext
const txn = getRootSpan(span) as TransactionWithV7FrozenDsc | undefined;
if (!txn) {
return dsc;
}
Expand Down
17 changes: 12 additions & 5 deletions packages/core/src/tracing/span.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,6 +16,7 @@ import type {
import { dropUndefinedKeys, logger, timestampInSeconds, uuid4 } from '@sentry/utils';

import { DEBUG_BUILD } from '../debug-build';
import { getRootSpan } from '../utils/getRootSpan';
import {
TRACE_FLAG_NONE,
TRACE_FLAG_SAMPLED,
Expand DownExpand Up@@ -105,6 +106,7 @@ export class Span implements SpanInterface {

/**
* @inheritDoc
* @deprecated Use top level `Sentry.getRootSpan()` instead
*/
public transaction?: Transaction;

Expand DownExpand Up@@ -304,12 +306,16 @@ export class Span implements SpanInterface {
childSpan.spanRecorder.add(childSpan);
}

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

if (DEBUG_BUILD && childSpan.transaction) {
if (DEBUG_BUILD && rootSpan) {
const opStr = (spanContext && spanContext.op) || '< unknown op >';
const nameStr = spanToJSON(childSpan).description || '< unknown name >';
const idStr = childSpan.transaction.spanContext().spanId;
const idStr = rootSpan.spanContext().spanId;

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

/** @inheritdoc */
public end(endTimestamp?: SpanTimeInput): void {
const rootSpan = getRootSpan(this);
if (
DEBUG_BUILD &&
// Don't call this for transactions
this.transaction &&
this.transaction.spanContext().spanId !== this._spanId
rootSpan &&
rootSpan.spanContext().spanId !== this._spanId
) {
const logMessage = this._logMessage;
if (logMessage) {
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/tracing/transaction.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -66,6 +66,8 @@ export class Transaction extends SpanClass implements TransactionInterface {
this._trimEnd = transactionContext.trimEnd;

// this is because transactions are also spans, and spans have a transaction pointer
// TODO (v8): Replace this with another way to set the root span

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there even a todo for v8 here? Won't we get rid of the transaction class?

@Lms24Lms24Jan 10, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I mean we somehow still need to construct a transaction for the event payload so I guess it's not gonna vanish completely. We'll just need some way to set and find the root span/txn of a span in v8.

// eslint-disable-next-line deprecation/deprecation
this.transaction = this;

// If Dynamic Sampling Context is provided during the creation of the transaction, we freeze it as it usually means
Expand Down
7 changes: 4 additions & 3 deletions packages/core/src/utils/applyScopeDataToEvent.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import type { Breadcrumb, Event, PropagationContext, ScopeData, Span } from '@sentry/types';
import { arrayify } from '@sentry/utils';
import { getDynamicSamplingContextFromSpan } from '../tracing/dynamicSamplingContext';
import { getRootSpan } from './getRootSpan';
import { spanToJSON, spanToTraceContext } from './spanUtils';

/**
Expand DownExpand Up@@ -174,13 +175,13 @@ function applySdkMetadataToEvent(

function applySpanToEvent(event: Event, span: Span): void {
event.contexts = { trace: spanToTraceContext(span), ...event.contexts };
const transaction = span.transaction;
if (transaction) {
const rootSpan = getRootSpan(span);
if (rootSpan) {
event.sdkProcessingMetadata = {
dynamicSamplingContext: getDynamicSamplingContextFromSpan(span),
...event.sdkProcessingMetadata,
};
const transactionName = spanToJSON(transaction).description;
const transactionName = spanToJSON(rootSpan).description;
if (transactionName) {
event.tags = { transaction: transactionName, ...event.tags };
}
Expand Down
15 changes: 15 additions & 0 deletions packages/core/src/utils/getRootSpan.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
import type { Span } from '@sentry/types';

/**
* Returns the root span of a given span.
*
* As long as we use `Transaction`s internally, the returned root span
* will be a `Transaction` but be aware that this might change in the future.
*
* If the given span has no root span or transaction, `undefined` is returned.
*/
export function getRootSpan(span: Span): Span | undefined {
// TODO (v8): Remove this check and just return span
// eslint-disable-next-line deprecation/deprecation
return span.transaction;
}
11 changes: 5 additions & 6 deletions packages/core/test/lib/tracing/dynamicSamplingContext.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
});

test('returns the DSC provided during transaction creation', () => {
// eslint-disable-next-line deprecation/deprecation
// eslint-disable-next-line deprecation/deprecation -- using old API on purpose
const transaction = new Transaction({
name: 'tx',
metadata: { dynamicSamplingContext: { environment: 'myEnv' } },
Expand DownExpand Up@@ -68,7 +68,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
});

test('returns a new DSC, if no DSC was provided during transaction creation (via new Txn and deprecated metadata)', () => {
// eslint-disable-next-line deprecation/deprecation
// eslint-disable-next-line deprecation/deprecation -- using old API on purpose
const transaction = new Transaction({
name: 'tx',
metadata: {
Expand All@@ -92,7 +92,7 @@ describe('getDynamicSamplingContextFromSpan', () => {

describe('Including transaction name in DSC', () => {
test('is not included if transaction source is url', () => {
// eslint-disable-next-line deprecation/deprecation
// eslint-disable-next-line deprecation/deprecation -- using old API on purpose
const transaction = new Transaction({
name: 'tx',
metadata: {
Expand All@@ -109,8 +109,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
['is included if transaction source is parameterized route/url', 'route'],
['is included if transaction source is a custom name', 'custom'],
])('%s', (_: string, source) => {
// eslint-disable-next-line deprecation/deprecation
const transaction = new Transaction({
const transaction = startInactiveSpan({
name: 'tx',
metadata: {
...(source && { source: source as TransactionSource }),
Expand All@@ -120,7 +119,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
// Only setting the attribute manually because we're directly calling new Transaction()
transaction?.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);

const dsc = getDynamicSamplingContextFromSpan(transaction);
const dsc = getDynamicSamplingContextFromSpan(transaction!);

expect(dsc.transaction).toEqual('tx');
});
Expand Down
36 changes: 36 additions & 0 deletions packages/core/test/lib/utils/getRootSpan.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
import { Span, Transaction, getRootSpan } from '../../../src';

describe('getRootSpan', () => {
it('returns the root span of a span (Span)', () => {
const root = new Span({ name: 'test' });
// @ts-expect-error this is highly illegal and shouldn't happen IRL
// eslint-disable-next-line deprecation/deprecation
root.transaction = root;

// eslint-disable-next-line deprecation/deprecation
const childSpan = root.startChild({ name: 'child' });
expect(getRootSpan(childSpan)).toBe(root);
});

it('returns the root span of a span (Transaction)', () => {
// eslint-disable-next-line deprecation/deprecation
const root = new Transaction({ name: 'test' });

// eslint-disable-next-line deprecation/deprecation
const childSpan = root.startChild({ name: 'child' });
expect(getRootSpan(childSpan)).toBe(root);
});

it('returns the span itself if it is a root span', () => {
// eslint-disable-next-line deprecation/deprecation
const span = new Transaction({ name: 'test' });

expect(getRootSpan(span)).toBe(span);
});

it('returns undefined if span has no root span', () => {
const span = new Span({ name: 'test' });

expect(getRootSpan(span)).toBe(undefined);
});
});
4 changes: 2 additions & 2 deletions packages/opentelemetry-node/src/propagator.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import type { Baggage, Context, TextMapGetter, TextMapSetter } from '@opentelemetry/api';
import { TraceFlags, isSpanContextValid, propagation, trace } from '@opentelemetry/api';
import { W3CBaggagePropagator, isTracingSuppressed } from '@opentelemetry/core';
import { getDynamicSamplingContextFromSpan, spanToTraceHeader } from '@sentry/core';
import { getDynamicSamplingContextFromSpan, getRootSpan, spanToTraceHeader } from '@sentry/core';
import {
SENTRY_BAGGAGE_KEY_PREFIX,
baggageHeaderToDynamicSamplingContext,
Expand DownExpand Up@@ -35,7 +35,7 @@ export class SentryPropagator extends W3CBaggagePropagator {
if (span) {
setter.set(carrier, SENTRY_TRACE_HEADER, spanToTraceHeader(span));

if (span.transaction) {
if (getRootSpan(span)) {
const dynamicSamplingContext = getDynamicSamplingContextFromSpan(span);
baggage = Object.entries(dynamicSamplingContext).reduce<Baggage>((b, [dscKey, dscValue]) => {
if (dscValue) {
Expand Down
3 changes: 2 additions & 1 deletion packages/opentelemetry-node/src/utils/spanMap.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import { getRootSpan } from '@sentry/core';
import type { Span as SentrySpan } from '@sentry/types';

interface SpanMapEntry {
Expand DownExpand Up@@ -31,7 +32,7 @@ export function getSentrySpan(spanId: string): SentrySpan | undefined {
export function setSentrySpan(spanId: string, sentrySpan: SentrySpan): void {
let ref: SpanRefType = SPAN_REF_ROOT;

const rootSpanId = sentrySpan.transaction?.spanContext().spanId;
const rootSpanId = getRootSpan(sentrySpan)?.spanContext().spanId;

if (rootSpanId && rootSpanId !== spanId) {
const root = SPAN_MAP.get(rootSpanId);
Expand Down
1 change: 1 addition & 0 deletions packages/svelte/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,7 @@
},
"dependencies": {
"@sentry/browser": "7.93.0",
"@sentry/core": "7.93.0",
"@sentry/types": "7.93.0",
"@sentry/utils": "7.93.0",
"magic-string": "^0.30.0"
Expand Down
3 changes: 2 additions & 1 deletion packages/svelte/src/performance.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import type { Span, Transaction } from '@sentry/types';
import { afterUpdate, beforeUpdate, onMount } from 'svelte';
import { current_component } from 'svelte/internal';

import { getRootSpan } from '@sentry/core';
import { DEFAULT_COMPONENT_NAME, UI_SVELTE_INIT, UI_SVELTE_UPDATE } from './constants';
import type { TrackComponentOptions } from './types';

Expand DownExpand Up@@ -74,7 +75,7 @@ function recordUpdateSpans(componentName: string, initSpan?: Span): void {
// If we are initializing the component when the update span is started, we start it as child
// of the init span. Else, we start it as a child of the transaction.
const parentSpan =
initSpan && !initSpan.endTimestamp && initSpan.transaction === transaction ? initSpan : transaction;
initSpan && !initSpan.endTimestamp && getRootSpan(initSpan) === transaction ? initSpan : transaction;

// eslint-disable-next-line deprecation/deprecation
updateSpan = parentSpan.startChild({
Expand Down
3 changes: 2 additions & 1 deletion packages/tracing-internal/src/browser/request.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@ import {
getCurrentScope,
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
getRootSpan,
hasTracingEnabled,
spanToJSON,
spanToTraceHeader,
Expand DownExpand Up@@ -298,7 +299,7 @@ export function xhrCallback(

if (xhr.setRequestHeader && shouldAttachHeaders(sentryXhrData.url)) {
if (span) {
const transaction = span && span.transaction;
const transaction = span && getRootSpan(span);
const dynamicSamplingContext = transaction && getDynamicSamplingContextFromSpan(transaction);
const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
setHeaderOnXhr(xhr, spanToTraceHeader(span), sentryBaggageHeader);
Expand Down
3 changes: 2 additions & 1 deletion packages/tracing-internal/src/common/fetch.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ import {
getCurrentScope,
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
getRootSpan,
hasTracingEnabled,
spanToTraceHeader,
} from '@sentry/core';
Expand DownExpand Up@@ -134,7 +135,7 @@ export function addTracingHeadersToFetchRequest(
// eslint-disable-next-line deprecation/deprecation
const span = requestSpan || scope.getSpan();

const transaction = span && span.transaction;
const transaction = span && getRootSpan(span);

const { traceId, sampled, dsc } = scope.getPropagationContext();

Expand Down
1 change: 1 addition & 0 deletions packages/types/src/span.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -216,6 +216,7 @@ export interface Span extends SpanContext {

/**
* The transaction containing this span
* @deprecated Use top level `Sentry.getRootSpan()` instead
*/
transaction?: Transaction;

Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MIGRATION.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -152,6 +152,7 @@ In v8, the Span class is heavily reworked. The following properties & methods ar
- `span.setTag()`: Use `span.setAttribute()` instead or set tags on the surrounding scope.
- `span.setData()`: Use `span.setAttribute()` instead.
- `span.instrumenter` This field was removed and will be replaced internally.
- `span.transaction`: Use `getRootSpan` utility function instead.
- `transaction.setContext()`: Set context on the surrounding scope instead.

## Deprecate `pushScope` & `popScope` in favor of `withScope`
Expand Down
7 changes: 4 additions & 3 deletions packages/astro/src/server/meta.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import {
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
getRootSpan,
spanToTraceHeader,
} from '@sentry/core';
import type { Client, Scope, Span } from '@sentry/types';
Expand DownExpand Up@@ -32,12 +33,12 @@ export function getTracingMetaTags(
client: Client | undefined,
): { sentryTrace: string; baggage?: string } {
const { dsc, sampled, traceId } = scope.getPropagationContext();
const transaction = span?.transaction;
const rootSpan = span && getRootSpan(span);

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

const dynamicSamplingContext = transaction
? getDynamicSamplingContextFromSpan(transaction)
const dynamicSamplingContext = rootSpan
? getDynamicSamplingContextFromSpan(rootSpan)
: dsc
? dsc
: client
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -81,6 +81,7 @@ export {
spanToJSON,
spanIsSampled,
} from './utils/spanUtils';
export { getRootSpan } from './utils/getRootSpan';
export { DEFAULT_ENVIRONMENT } from './constants';
export { ModuleMetadata } from './integrations/metadata';
export { RequestData } from './integrations/requestdata';
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/scope.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -334,6 +334,9 @@ export class Scope implements ScopeInterface {
// Often, this span (if it exists at all) will be a transaction, but it's not guaranteed to be. Regardless, it will
// have a pointer to the currently-active transaction.
const span = this._span;
// Cannot replace with getRootSpan because getRootSpan returns a span, not a transaction
// Also, this method will be removed anyway.
// eslint-disable-next-line deprecation/deprecation
return span && span.transaction;
}

Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/server-runtime-client.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@ import {
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
} from './tracing';
import { getRootSpan } from './utils/getRootSpan';
import { spanToTraceContext } from './utils/spanUtils';

export interface ServerRuntimeClientOptions extends ClientOptions<BaseTransportOptions> {
Expand DownExpand Up@@ -262,7 +263,7 @@ export class ServerRuntimeClient<
// eslint-disable-next-line deprecation/deprecation
const span = scope.getSpan();
if (span) {
const samplingContext = span.transaction ? getDynamicSamplingContextFromSpan(span) : undefined;
const samplingContext = getRootSpan(span) ? getDynamicSamplingContextFromSpan(span) : undefined;
return [samplingContext, spanToTraceContext(span)];
}

Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/tracing/dynamicSamplingContext.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import { dropUndefinedKeys } from '@sentry/utils';

import { DEFAULT_ENVIRONMENT } from '../constants';
import { getClient, getCurrentScope } from '../exports';
import { getRootSpan } from '../utils/getRootSpan';
import { spanIsSampled, spanToJSON } from '../utils/spanUtils';

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

// As long as we use `Transaction`s internally, this should be fine.
// TODO: We need to replace this with a `getRootSpan(span)` function though
const txn = span.transaction as TransactionWithV7FrozenDsc | undefined;
// TODO (v8): Remove v7FrozenDsc as a Transaction will no longer have _frozenDynamicSamplingContext
const txn = getRootSpan(span) as TransactionWithV7FrozenDsc | undefined;
if (!txn) {
return dsc;
}
Expand Down
17 changes: 12 additions & 5 deletions packages/core/src/tracing/span.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,6 +16,7 @@ import type {
import { dropUndefinedKeys, logger, timestampInSeconds, uuid4 } from '@sentry/utils';

import { DEBUG_BUILD } from '../debug-build';
import { getRootSpan } from '../utils/getRootSpan';
import {
TRACE_FLAG_NONE,
TRACE_FLAG_SAMPLED,
Expand DownExpand Up@@ -105,6 +106,7 @@ export class Span implements SpanInterface {

/**
* @inheritDoc
* @deprecated Use top level `Sentry.getRootSpan()` instead
*/
public transaction?: Transaction;

Expand DownExpand Up@@ -304,12 +306,16 @@ export class Span implements SpanInterface {
childSpan.spanRecorder.add(childSpan);
}

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

if (DEBUG_BUILD && childSpan.transaction) {
if (DEBUG_BUILD && rootSpan) {
const opStr = (spanContext && spanContext.op) || '< unknown op >';
const nameStr = spanToJSON(childSpan).description || '< unknown name >';
const idStr = childSpan.transaction.spanContext().spanId;
const idStr = rootSpan.spanContext().spanId;

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

/** @inheritdoc */
public end(endTimestamp?: SpanTimeInput): void {
const rootSpan = getRootSpan(this);
if (
DEBUG_BUILD &&
// Don't call this for transactions
this.transaction &&
this.transaction.spanContext().spanId !== this._spanId
rootSpan &&
rootSpan.spanContext().spanId !== this._spanId
) {
const logMessage = this._logMessage;
if (logMessage) {
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/tracing/transaction.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -66,6 +66,8 @@ export class Transaction extends SpanClass implements TransactionInterface {
this._trimEnd = transactionContext.trimEnd;

// this is because transactions are also spans, and spans have a transaction pointer
// TODO (v8): Replace this with another way to set the root span

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there even a todo for v8 here? Won't we get rid of the transaction class?

@Lms24Lms24Jan 10, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I mean we somehow still need to construct a transaction for the event payload so I guess it's not gonna vanish completely. We'll just need some way to set and find the root span/txn of a span in v8.

// eslint-disable-next-line deprecation/deprecation
this.transaction = this;

// If Dynamic Sampling Context is provided during the creation of the transaction, we freeze it as it usually means
Expand Down
7 changes: 4 additions & 3 deletions packages/core/src/utils/applyScopeDataToEvent.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import type { Breadcrumb, Event, PropagationContext, ScopeData, Span } from '@sentry/types';
import { arrayify } from '@sentry/utils';
import { getDynamicSamplingContextFromSpan } from '../tracing/dynamicSamplingContext';
import { getRootSpan } from './getRootSpan';
import { spanToJSON, spanToTraceContext } from './spanUtils';

/**
Expand DownExpand Up@@ -174,13 +175,13 @@ function applySdkMetadataToEvent(

function applySpanToEvent(event: Event, span: Span): void {
event.contexts = { trace: spanToTraceContext(span), ...event.contexts };
const transaction = span.transaction;
if (transaction) {
const rootSpan = getRootSpan(span);
if (rootSpan) {
event.sdkProcessingMetadata = {
dynamicSamplingContext: getDynamicSamplingContextFromSpan(span),
...event.sdkProcessingMetadata,
};
const transactionName = spanToJSON(transaction).description;
const transactionName = spanToJSON(rootSpan).description;
if (transactionName) {
event.tags = { transaction: transactionName, ...event.tags };
}
Expand Down
15 changes: 15 additions & 0 deletions packages/core/src/utils/getRootSpan.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
import type { Span } from '@sentry/types';

/**
* Returns the root span of a given span.
*
* As long as we use `Transaction`s internally, the returned root span
* will be a `Transaction` but be aware that this might change in the future.
*
* If the given span has no root span or transaction, `undefined` is returned.
*/
export function getRootSpan(span: Span): Span | undefined {
// TODO (v8): Remove this check and just return span
// eslint-disable-next-line deprecation/deprecation
return span.transaction;
}
11 changes: 5 additions & 6 deletions packages/core/test/lib/tracing/dynamicSamplingContext.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
});

test('returns the DSC provided during transaction creation', () => {
// eslint-disable-next-line deprecation/deprecation
// eslint-disable-next-line deprecation/deprecation -- using old API on purpose
const transaction = new Transaction({
name: 'tx',
metadata: { dynamicSamplingContext: { environment: 'myEnv' } },
Expand DownExpand Up@@ -68,7 +68,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
});

test('returns a new DSC, if no DSC was provided during transaction creation (via new Txn and deprecated metadata)', () => {
// eslint-disable-next-line deprecation/deprecation
// eslint-disable-next-line deprecation/deprecation -- using old API on purpose
const transaction = new Transaction({
name: 'tx',
metadata: {
Expand All@@ -92,7 +92,7 @@ describe('getDynamicSamplingContextFromSpan', () => {

describe('Including transaction name in DSC', () => {
test('is not included if transaction source is url', () => {
// eslint-disable-next-line deprecation/deprecation
// eslint-disable-next-line deprecation/deprecation -- using old API on purpose
const transaction = new Transaction({
name: 'tx',
metadata: {
Expand All@@ -109,8 +109,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
['is included if transaction source is parameterized route/url', 'route'],
['is included if transaction source is a custom name', 'custom'],
])('%s', (_: string, source) => {
// eslint-disable-next-line deprecation/deprecation
const transaction = new Transaction({
const transaction = startInactiveSpan({
name: 'tx',
metadata: {
...(source && { source: source as TransactionSource }),
Expand All@@ -120,7 +119,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
// Only setting the attribute manually because we're directly calling new Transaction()
transaction?.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);

const dsc = getDynamicSamplingContextFromSpan(transaction);
const dsc = getDynamicSamplingContextFromSpan(transaction!);

expect(dsc.transaction).toEqual('tx');
});
Expand Down
36 changes: 36 additions & 0 deletions packages/core/test/lib/utils/getRootSpan.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
import { Span, Transaction, getRootSpan } from '../../../src';

describe('getRootSpan', () => {
it('returns the root span of a span (Span)', () => {
const root = new Span({ name: 'test' });
// @ts-expect-error this is highly illegal and shouldn't happen IRL
// eslint-disable-next-line deprecation/deprecation
root.transaction = root;

// eslint-disable-next-line deprecation/deprecation
const childSpan = root.startChild({ name: 'child' });
expect(getRootSpan(childSpan)).toBe(root);
});

it('returns the root span of a span (Transaction)', () => {
// eslint-disable-next-line deprecation/deprecation
const root = new Transaction({ name: 'test' });

// eslint-disable-next-line deprecation/deprecation
const childSpan = root.startChild({ name: 'child' });
expect(getRootSpan(childSpan)).toBe(root);
});

it('returns the span itself if it is a root span', () => {
// eslint-disable-next-line deprecation/deprecation
const span = new Transaction({ name: 'test' });

expect(getRootSpan(span)).toBe(span);
});

it('returns undefined if span has no root span', () => {
const span = new Span({ name: 'test' });

expect(getRootSpan(span)).toBe(undefined);
});
});
4 changes: 2 additions & 2 deletions packages/opentelemetry-node/src/propagator.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import type { Baggage, Context, TextMapGetter, TextMapSetter } from '@opentelemetry/api';
import { TraceFlags, isSpanContextValid, propagation, trace } from '@opentelemetry/api';
import { W3CBaggagePropagator, isTracingSuppressed } from '@opentelemetry/core';
import { getDynamicSamplingContextFromSpan, spanToTraceHeader } from '@sentry/core';
import { getDynamicSamplingContextFromSpan, getRootSpan, spanToTraceHeader } from '@sentry/core';
import {
SENTRY_BAGGAGE_KEY_PREFIX,
baggageHeaderToDynamicSamplingContext,
Expand DownExpand Up@@ -35,7 +35,7 @@ export class SentryPropagator extends W3CBaggagePropagator {
if (span) {
setter.set(carrier, SENTRY_TRACE_HEADER, spanToTraceHeader(span));

if (span.transaction) {
if (getRootSpan(span)) {
const dynamicSamplingContext = getDynamicSamplingContextFromSpan(span);
baggage = Object.entries(dynamicSamplingContext).reduce<Baggage>((b, [dscKey, dscValue]) => {
if (dscValue) {
Expand Down
3 changes: 2 additions & 1 deletion packages/opentelemetry-node/src/utils/spanMap.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import { getRootSpan } from '@sentry/core';
import type { Span as SentrySpan } from '@sentry/types';

interface SpanMapEntry {
Expand DownExpand Up@@ -31,7 +32,7 @@ export function getSentrySpan(spanId: string): SentrySpan | undefined {
export function setSentrySpan(spanId: string, sentrySpan: SentrySpan): void {
let ref: SpanRefType = SPAN_REF_ROOT;

const rootSpanId = sentrySpan.transaction?.spanContext().spanId;
const rootSpanId = getRootSpan(sentrySpan)?.spanContext().spanId;

if (rootSpanId && rootSpanId !== spanId) {
const root = SPAN_MAP.get(rootSpanId);
Expand Down
1 change: 1 addition & 0 deletions packages/svelte/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,7 @@
},
"dependencies": {
"@sentry/browser": "7.93.0",
"@sentry/core": "7.93.0",
"@sentry/types": "7.93.0",
"@sentry/utils": "7.93.0",
"magic-string": "^0.30.0"
Expand Down
3 changes: 2 additions & 1 deletion packages/svelte/src/performance.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import type { Span, Transaction } from '@sentry/types';
import { afterUpdate, beforeUpdate, onMount } from 'svelte';
import { current_component } from 'svelte/internal';

import { getRootSpan } from '@sentry/core';
import { DEFAULT_COMPONENT_NAME, UI_SVELTE_INIT, UI_SVELTE_UPDATE } from './constants';
import type { TrackComponentOptions } from './types';

Expand DownExpand Up@@ -74,7 +75,7 @@ function recordUpdateSpans(componentName: string, initSpan?: Span): void {
// If we are initializing the component when the update span is started, we start it as child
// of the init span. Else, we start it as a child of the transaction.
const parentSpan =
initSpan && !initSpan.endTimestamp && initSpan.transaction === transaction ? initSpan : transaction;
initSpan && !initSpan.endTimestamp && getRootSpan(initSpan) === transaction ? initSpan : transaction;

// eslint-disable-next-line deprecation/deprecation
updateSpan = parentSpan.startChild({
Expand Down
3 changes: 2 additions & 1 deletion packages/tracing-internal/src/browser/request.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@ import {
getCurrentScope,
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
getRootSpan,
hasTracingEnabled,
spanToJSON,
spanToTraceHeader,
Expand DownExpand Up@@ -298,7 +299,7 @@ export function xhrCallback(

if (xhr.setRequestHeader && shouldAttachHeaders(sentryXhrData.url)) {
if (span) {
const transaction = span && span.transaction;
const transaction = span && getRootSpan(span);
const dynamicSamplingContext = transaction && getDynamicSamplingContextFromSpan(transaction);
const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
setHeaderOnXhr(xhr, spanToTraceHeader(span), sentryBaggageHeader);
Expand Down
3 changes: 2 additions & 1 deletion packages/tracing-internal/src/common/fetch.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ import {
getCurrentScope,
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
getRootSpan,
hasTracingEnabled,
spanToTraceHeader,
} from '@sentry/core';
Expand DownExpand Up@@ -134,7 +135,7 @@ export function addTracingHeadersToFetchRequest(
// eslint-disable-next-line deprecation/deprecation
const span = requestSpan || scope.getSpan();

const transaction = span && span.transaction;
const transaction = span && getRootSpan(span);

const { traceId, sampled, dsc } = scope.getPropagationContext();

Expand Down
1 change: 1 addition & 0 deletions packages/types/src/span.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -216,6 +216,7 @@ export interface Span extends SpanContext {

/**
* The transaction containing this span
* @deprecated Use top level `Sentry.getRootSpan()` instead
*/
transaction?: Transaction;

Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MIGRATION.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -152,6 +152,7 @@ In v8, the Span class is heavily reworked. The following properties & methods ar
- `span.setTag()`: Use `span.setAttribute()` instead or set tags on the surrounding scope.
- `span.setData()`: Use `span.setAttribute()` instead.
- `span.instrumenter` This field was removed and will be replaced internally.
- `span.transaction`: Use `getRootSpan` utility function instead.
- `transaction.setContext()`: Set context on the surrounding scope instead.

## Deprecate `pushScope` & `popScope` in favor of `withScope`
Expand Down
7 changes: 4 additions & 3 deletions packages/astro/src/server/meta.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import {
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
getRootSpan,
spanToTraceHeader,
} from '@sentry/core';
import type { Client, Scope, Span } from '@sentry/types';
Expand DownExpand Up@@ -32,12 +33,12 @@ export function getTracingMetaTags(
client: Client | undefined,
): { sentryTrace: string; baggage?: string } {
const { dsc, sampled, traceId } = scope.getPropagationContext();
const transaction = span?.transaction;
const rootSpan = span && getRootSpan(span);

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

const dynamicSamplingContext = transaction
? getDynamicSamplingContextFromSpan(transaction)
const dynamicSamplingContext = rootSpan
? getDynamicSamplingContextFromSpan(rootSpan)
: dsc
? dsc
: client
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -81,6 +81,7 @@ export {
spanToJSON,
spanIsSampled,
} from './utils/spanUtils';
export { getRootSpan } from './utils/getRootSpan';
export { DEFAULT_ENVIRONMENT } from './constants';
export { ModuleMetadata } from './integrations/metadata';
export { RequestData } from './integrations/requestdata';
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/scope.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -334,6 +334,9 @@ export class Scope implements ScopeInterface {
// Often, this span (if it exists at all) will be a transaction, but it's not guaranteed to be. Regardless, it will
// have a pointer to the currently-active transaction.
const span = this._span;
// Cannot replace with getRootSpan because getRootSpan returns a span, not a transaction
// Also, this method will be removed anyway.
// eslint-disable-next-line deprecation/deprecation
return span && span.transaction;
}

Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/server-runtime-client.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@ import {
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
} from './tracing';
import { getRootSpan } from './utils/getRootSpan';
import { spanToTraceContext } from './utils/spanUtils';

export interface ServerRuntimeClientOptions extends ClientOptions<BaseTransportOptions> {
Expand DownExpand Up@@ -262,7 +263,7 @@ export class ServerRuntimeClient<
// eslint-disable-next-line deprecation/deprecation
const span = scope.getSpan();
if (span) {
const samplingContext = span.transaction ? getDynamicSamplingContextFromSpan(span) : undefined;
const samplingContext = getRootSpan(span) ? getDynamicSamplingContextFromSpan(span) : undefined;
return [samplingContext, spanToTraceContext(span)];
}

Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/tracing/dynamicSamplingContext.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import { dropUndefinedKeys } from '@sentry/utils';

import { DEFAULT_ENVIRONMENT } from '../constants';
import { getClient, getCurrentScope } from '../exports';
import { getRootSpan } from '../utils/getRootSpan';
import { spanIsSampled, spanToJSON } from '../utils/spanUtils';

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

// As long as we use `Transaction`s internally, this should be fine.
// TODO: We need to replace this with a `getRootSpan(span)` function though
const txn = span.transaction as TransactionWithV7FrozenDsc | undefined;
// TODO (v8): Remove v7FrozenDsc as a Transaction will no longer have _frozenDynamicSamplingContext
const txn = getRootSpan(span) as TransactionWithV7FrozenDsc | undefined;
if (!txn) {
return dsc;
}
Expand Down
17 changes: 12 additions & 5 deletions packages/core/src/tracing/span.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,6 +16,7 @@ import type {
import { dropUndefinedKeys, logger, timestampInSeconds, uuid4 } from '@sentry/utils';

import { DEBUG_BUILD } from '../debug-build';
import { getRootSpan } from '../utils/getRootSpan';
import {
TRACE_FLAG_NONE,
TRACE_FLAG_SAMPLED,
Expand DownExpand Up@@ -105,6 +106,7 @@ export class Span implements SpanInterface {

/**
* @inheritDoc
* @deprecated Use top level `Sentry.getRootSpan()` instead
*/
public transaction?: Transaction;

Expand DownExpand Up@@ -304,12 +306,16 @@ export class Span implements SpanInterface {
childSpan.spanRecorder.add(childSpan);
}

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

if (DEBUG_BUILD && childSpan.transaction) {
if (DEBUG_BUILD && rootSpan) {
const opStr = (spanContext && spanContext.op) || '< unknown op >';
const nameStr = spanToJSON(childSpan).description || '< unknown name >';
const idStr = childSpan.transaction.spanContext().spanId;
const idStr = rootSpan.spanContext().spanId;

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

/** @inheritdoc */
public end(endTimestamp?: SpanTimeInput): void {
const rootSpan = getRootSpan(this);
if (
DEBUG_BUILD &&
// Don't call this for transactions
this.transaction &&
this.transaction.spanContext().spanId !== this._spanId
rootSpan &&
rootSpan.spanContext().spanId !== this._spanId
) {
const logMessage = this._logMessage;
if (logMessage) {
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/tracing/transaction.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -66,6 +66,8 @@ export class Transaction extends SpanClass implements TransactionInterface {
this._trimEnd = transactionContext.trimEnd;

// this is because transactions are also spans, and spans have a transaction pointer
// TODO (v8): Replace this with another way to set the root span

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there even a todo for v8 here? Won't we get rid of the transaction class?

@Lms24Lms24Jan 10, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I mean we somehow still need to construct a transaction for the event payload so I guess it's not gonna vanish completely. We'll just need some way to set and find the root span/txn of a span in v8.

// eslint-disable-next-line deprecation/deprecation
this.transaction = this;

// If Dynamic Sampling Context is provided during the creation of the transaction, we freeze it as it usually means
Expand Down
7 changes: 4 additions & 3 deletions packages/core/src/utils/applyScopeDataToEvent.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import type { Breadcrumb, Event, PropagationContext, ScopeData, Span } from '@sentry/types';
import { arrayify } from '@sentry/utils';
import { getDynamicSamplingContextFromSpan } from '../tracing/dynamicSamplingContext';
import { getRootSpan } from './getRootSpan';
import { spanToJSON, spanToTraceContext } from './spanUtils';

/**
Expand DownExpand Up@@ -174,13 +175,13 @@ function applySdkMetadataToEvent(

function applySpanToEvent(event: Event, span: Span): void {
event.contexts = { trace: spanToTraceContext(span), ...event.contexts };
const transaction = span.transaction;
if (transaction) {
const rootSpan = getRootSpan(span);
if (rootSpan) {
event.sdkProcessingMetadata = {
dynamicSamplingContext: getDynamicSamplingContextFromSpan(span),
...event.sdkProcessingMetadata,
};
const transactionName = spanToJSON(transaction).description;
const transactionName = spanToJSON(rootSpan).description;
if (transactionName) {
event.tags = { transaction: transactionName, ...event.tags };
}
Expand Down
15 changes: 15 additions & 0 deletions packages/core/src/utils/getRootSpan.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
import type { Span } from '@sentry/types';

/**
* Returns the root span of a given span.
*
* As long as we use `Transaction`s internally, the returned root span
* will be a `Transaction` but be aware that this might change in the future.
*
* If the given span has no root span or transaction, `undefined` is returned.
*/
export function getRootSpan(span: Span): Span | undefined {
// TODO (v8): Remove this check and just return span
// eslint-disable-next-line deprecation/deprecation
return span.transaction;
}
11 changes: 5 additions & 6 deletions packages/core/test/lib/tracing/dynamicSamplingContext.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
});

test('returns the DSC provided during transaction creation', () => {
// eslint-disable-next-line deprecation/deprecation
// eslint-disable-next-line deprecation/deprecation -- using old API on purpose
const transaction = new Transaction({
name: 'tx',
metadata: { dynamicSamplingContext: { environment: 'myEnv' } },
Expand DownExpand Up@@ -68,7 +68,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
});

test('returns a new DSC, if no DSC was provided during transaction creation (via new Txn and deprecated metadata)', () => {
// eslint-disable-next-line deprecation/deprecation
// eslint-disable-next-line deprecation/deprecation -- using old API on purpose
const transaction = new Transaction({
name: 'tx',
metadata: {
Expand All@@ -92,7 +92,7 @@ describe('getDynamicSamplingContextFromSpan', () => {

describe('Including transaction name in DSC', () => {
test('is not included if transaction source is url', () => {
// eslint-disable-next-line deprecation/deprecation
// eslint-disable-next-line deprecation/deprecation -- using old API on purpose
const transaction = new Transaction({
name: 'tx',
metadata: {
Expand All@@ -109,8 +109,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
['is included if transaction source is parameterized route/url', 'route'],
['is included if transaction source is a custom name', 'custom'],
])('%s', (_: string, source) => {
// eslint-disable-next-line deprecation/deprecation
const transaction = new Transaction({
const transaction = startInactiveSpan({
name: 'tx',
metadata: {
...(source && { source: source as TransactionSource }),
Expand All@@ -120,7 +119,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
// Only setting the attribute manually because we're directly calling new Transaction()
transaction?.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);

const dsc = getDynamicSamplingContextFromSpan(transaction);
const dsc = getDynamicSamplingContextFromSpan(transaction!);

expect(dsc.transaction).toEqual('tx');
});
Expand Down
36 changes: 36 additions & 0 deletions packages/core/test/lib/utils/getRootSpan.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
import { Span, Transaction, getRootSpan } from '../../../src';

describe('getRootSpan', () => {
it('returns the root span of a span (Span)', () => {
const root = new Span({ name: 'test' });
// @ts-expect-error this is highly illegal and shouldn't happen IRL
// eslint-disable-next-line deprecation/deprecation
root.transaction = root;

// eslint-disable-next-line deprecation/deprecation
const childSpan = root.startChild({ name: 'child' });
expect(getRootSpan(childSpan)).toBe(root);
});

it('returns the root span of a span (Transaction)', () => {
// eslint-disable-next-line deprecation/deprecation
const root = new Transaction({ name: 'test' });

// eslint-disable-next-line deprecation/deprecation
const childSpan = root.startChild({ name: 'child' });
expect(getRootSpan(childSpan)).toBe(root);
});

it('returns the span itself if it is a root span', () => {
// eslint-disable-next-line deprecation/deprecation
const span = new Transaction({ name: 'test' });

expect(getRootSpan(span)).toBe(span);
});

it('returns undefined if span has no root span', () => {
const span = new Span({ name: 'test' });

expect(getRootSpan(span)).toBe(undefined);
});
});
4 changes: 2 additions & 2 deletions packages/opentelemetry-node/src/propagator.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import type { Baggage, Context, TextMapGetter, TextMapSetter } from '@opentelemetry/api';
import { TraceFlags, isSpanContextValid, propagation, trace } from '@opentelemetry/api';
import { W3CBaggagePropagator, isTracingSuppressed } from '@opentelemetry/core';
import { getDynamicSamplingContextFromSpan, spanToTraceHeader } from '@sentry/core';
import { getDynamicSamplingContextFromSpan, getRootSpan, spanToTraceHeader } from '@sentry/core';
import {
SENTRY_BAGGAGE_KEY_PREFIX,
baggageHeaderToDynamicSamplingContext,
Expand DownExpand Up@@ -35,7 +35,7 @@ export class SentryPropagator extends W3CBaggagePropagator {
if (span) {
setter.set(carrier, SENTRY_TRACE_HEADER, spanToTraceHeader(span));

if (span.transaction) {
if (getRootSpan(span)) {
const dynamicSamplingContext = getDynamicSamplingContextFromSpan(span);
baggage = Object.entries(dynamicSamplingContext).reduce<Baggage>((b, [dscKey, dscValue]) => {
if (dscValue) {
Expand Down
3 changes: 2 additions & 1 deletion packages/opentelemetry-node/src/utils/spanMap.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import { getRootSpan } from '@sentry/core';
import type { Span as SentrySpan } from '@sentry/types';

interface SpanMapEntry {
Expand DownExpand Up@@ -31,7 +32,7 @@ export function getSentrySpan(spanId: string): SentrySpan | undefined {
export function setSentrySpan(spanId: string, sentrySpan: SentrySpan): void {
let ref: SpanRefType = SPAN_REF_ROOT;

const rootSpanId = sentrySpan.transaction?.spanContext().spanId;
const rootSpanId = getRootSpan(sentrySpan)?.spanContext().spanId;

if (rootSpanId && rootSpanId !== spanId) {
const root = SPAN_MAP.get(rootSpanId);
Expand Down
1 change: 1 addition & 0 deletions packages/svelte/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,7 @@
},
"dependencies": {
"@sentry/browser": "7.93.0",
"@sentry/core": "7.93.0",
"@sentry/types": "7.93.0",
"@sentry/utils": "7.93.0",
"magic-string": "^0.30.0"
Expand Down
3 changes: 2 additions & 1 deletion packages/svelte/src/performance.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import type { Span, Transaction } from '@sentry/types';
import { afterUpdate, beforeUpdate, onMount } from 'svelte';
import { current_component } from 'svelte/internal';

import { getRootSpan } from '@sentry/core';
import { DEFAULT_COMPONENT_NAME, UI_SVELTE_INIT, UI_SVELTE_UPDATE } from './constants';
import type { TrackComponentOptions } from './types';

Expand DownExpand Up@@ -74,7 +75,7 @@ function recordUpdateSpans(componentName: string, initSpan?: Span): void {
// If we are initializing the component when the update span is started, we start it as child
// of the init span. Else, we start it as a child of the transaction.
const parentSpan =
initSpan && !initSpan.endTimestamp && initSpan.transaction === transaction ? initSpan : transaction;
initSpan && !initSpan.endTimestamp && getRootSpan(initSpan) === transaction ? initSpan : transaction;

// eslint-disable-next-line deprecation/deprecation
updateSpan = parentSpan.startChild({
Expand Down
3 changes: 2 additions & 1 deletion packages/tracing-internal/src/browser/request.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@ import {
getCurrentScope,
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
getRootSpan,
hasTracingEnabled,
spanToJSON,
spanToTraceHeader,
Expand DownExpand Up@@ -298,7 +299,7 @@ export function xhrCallback(

if (xhr.setRequestHeader && shouldAttachHeaders(sentryXhrData.url)) {
if (span) {
const transaction = span && span.transaction;
const transaction = span && getRootSpan(span);
const dynamicSamplingContext = transaction && getDynamicSamplingContextFromSpan(transaction);
const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
setHeaderOnXhr(xhr, spanToTraceHeader(span), sentryBaggageHeader);
Expand Down
3 changes: 2 additions & 1 deletion packages/tracing-internal/src/common/fetch.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ import {
getCurrentScope,
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
getRootSpan,
hasTracingEnabled,
spanToTraceHeader,
} from '@sentry/core';
Expand DownExpand Up@@ -134,7 +135,7 @@ export function addTracingHeadersToFetchRequest(
// eslint-disable-next-line deprecation/deprecation
const span = requestSpan || scope.getSpan();

const transaction = span && span.transaction;
const transaction = span && getRootSpan(span);

const { traceId, sampled, dsc } = scope.getPropagationContext();

Expand Down
1 change: 1 addition & 0 deletions packages/types/src/span.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -216,6 +216,7 @@ export interface Span extends SpanContext {

/**
* The transaction containing this span
* @deprecated Use top level `Sentry.getRootSpan()` instead
*/
transaction?: Transaction;

Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MIGRATION.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -152,6 +152,7 @@ In v8, the Span class is heavily reworked. The following properties & methods ar
- `span.setTag()`: Use `span.setAttribute()` instead or set tags on the surrounding scope.
- `span.setData()`: Use `span.setAttribute()` instead.
- `span.instrumenter` This field was removed and will be replaced internally.
- `span.transaction`: Use `getRootSpan` utility function instead.
- `transaction.setContext()`: Set context on the surrounding scope instead.

## Deprecate `pushScope` & `popScope` in favor of `withScope`
Expand Down
7 changes: 4 additions & 3 deletions packages/astro/src/server/meta.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import {
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
getRootSpan,
spanToTraceHeader,
} from '@sentry/core';
import type { Client, Scope, Span } from '@sentry/types';
Expand DownExpand Up@@ -32,12 +33,12 @@ export function getTracingMetaTags(
client: Client | undefined,
): { sentryTrace: string; baggage?: string } {
const { dsc, sampled, traceId } = scope.getPropagationContext();
const transaction = span?.transaction;
const rootSpan = span && getRootSpan(span);

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

const dynamicSamplingContext = transaction
? getDynamicSamplingContextFromSpan(transaction)
const dynamicSamplingContext = rootSpan
? getDynamicSamplingContextFromSpan(rootSpan)
: dsc
? dsc
: client
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -81,6 +81,7 @@ export {
spanToJSON,
spanIsSampled,
} from './utils/spanUtils';
export { getRootSpan } from './utils/getRootSpan';
export { DEFAULT_ENVIRONMENT } from './constants';
export { ModuleMetadata } from './integrations/metadata';
export { RequestData } from './integrations/requestdata';
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/scope.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -334,6 +334,9 @@ export class Scope implements ScopeInterface {
// Often, this span (if it exists at all) will be a transaction, but it's not guaranteed to be. Regardless, it will
// have a pointer to the currently-active transaction.
const span = this._span;
// Cannot replace with getRootSpan because getRootSpan returns a span, not a transaction
// Also, this method will be removed anyway.
// eslint-disable-next-line deprecation/deprecation
return span && span.transaction;
}

Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/server-runtime-client.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@ import {
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
} from './tracing';
import { getRootSpan } from './utils/getRootSpan';
import { spanToTraceContext } from './utils/spanUtils';

export interface ServerRuntimeClientOptions extends ClientOptions<BaseTransportOptions> {
Expand DownExpand Up@@ -262,7 +263,7 @@ export class ServerRuntimeClient<
// eslint-disable-next-line deprecation/deprecation
const span = scope.getSpan();
if (span) {
const samplingContext = span.transaction ? getDynamicSamplingContextFromSpan(span) : undefined;
const samplingContext = getRootSpan(span) ? getDynamicSamplingContextFromSpan(span) : undefined;
return [samplingContext, spanToTraceContext(span)];
}

Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/tracing/dynamicSamplingContext.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import { dropUndefinedKeys } from '@sentry/utils';

import { DEFAULT_ENVIRONMENT } from '../constants';
import { getClient, getCurrentScope } from '../exports';
import { getRootSpan } from '../utils/getRootSpan';
import { spanIsSampled, spanToJSON } from '../utils/spanUtils';

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

// As long as we use `Transaction`s internally, this should be fine.
// TODO: We need to replace this with a `getRootSpan(span)` function though
const txn = span.transaction as TransactionWithV7FrozenDsc | undefined;
// TODO (v8): Remove v7FrozenDsc as a Transaction will no longer have _frozenDynamicSamplingContext
const txn = getRootSpan(span) as TransactionWithV7FrozenDsc | undefined;
if (!txn) {
return dsc;
}
Expand Down
17 changes: 12 additions & 5 deletions packages/core/src/tracing/span.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,6 +16,7 @@ import type {
import { dropUndefinedKeys, logger, timestampInSeconds, uuid4 } from '@sentry/utils';

import { DEBUG_BUILD } from '../debug-build';
import { getRootSpan } from '../utils/getRootSpan';
import {
TRACE_FLAG_NONE,
TRACE_FLAG_SAMPLED,
Expand DownExpand Up@@ -105,6 +106,7 @@ export class Span implements SpanInterface {

/**
* @inheritDoc
* @deprecated Use top level `Sentry.getRootSpan()` instead
*/
public transaction?: Transaction;

Expand DownExpand Up@@ -304,12 +306,16 @@ export class Span implements SpanInterface {
childSpan.spanRecorder.add(childSpan);
}

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

if (DEBUG_BUILD && childSpan.transaction) {
if (DEBUG_BUILD && rootSpan) {
const opStr = (spanContext && spanContext.op) || '< unknown op >';
const nameStr = spanToJSON(childSpan).description || '< unknown name >';
const idStr = childSpan.transaction.spanContext().spanId;
const idStr = rootSpan.spanContext().spanId;

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

/** @inheritdoc */
public end(endTimestamp?: SpanTimeInput): void {
const rootSpan = getRootSpan(this);
if (
DEBUG_BUILD &&
// Don't call this for transactions
this.transaction &&
this.transaction.spanContext().spanId !== this._spanId
rootSpan &&
rootSpan.spanContext().spanId !== this._spanId
) {
const logMessage = this._logMessage;
if (logMessage) {
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/tracing/transaction.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -66,6 +66,8 @@ export class Transaction extends SpanClass implements TransactionInterface {
this._trimEnd = transactionContext.trimEnd;

// this is because transactions are also spans, and spans have a transaction pointer
// TODO (v8): Replace this with another way to set the root span

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there even a todo for v8 here? Won't we get rid of the transaction class?

@Lms24Lms24Jan 10, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I mean we somehow still need to construct a transaction for the event payload so I guess it's not gonna vanish completely. We'll just need some way to set and find the root span/txn of a span in v8.

// eslint-disable-next-line deprecation/deprecation
this.transaction = this;

// If Dynamic Sampling Context is provided during the creation of the transaction, we freeze it as it usually means
Expand Down
7 changes: 4 additions & 3 deletions packages/core/src/utils/applyScopeDataToEvent.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import type { Breadcrumb, Event, PropagationContext, ScopeData, Span } from '@sentry/types';
import { arrayify } from '@sentry/utils';
import { getDynamicSamplingContextFromSpan } from '../tracing/dynamicSamplingContext';
import { getRootSpan } from './getRootSpan';
import { spanToJSON, spanToTraceContext } from './spanUtils';

/**
Expand DownExpand Up@@ -174,13 +175,13 @@ function applySdkMetadataToEvent(

function applySpanToEvent(event: Event, span: Span): void {
event.contexts = { trace: spanToTraceContext(span), ...event.contexts };
const transaction = span.transaction;
if (transaction) {
const rootSpan = getRootSpan(span);
if (rootSpan) {
event.sdkProcessingMetadata = {
dynamicSamplingContext: getDynamicSamplingContextFromSpan(span),
...event.sdkProcessingMetadata,
};
const transactionName = spanToJSON(transaction).description;
const transactionName = spanToJSON(rootSpan).description;
if (transactionName) {
event.tags = { transaction: transactionName, ...event.tags };
}
Expand Down
15 changes: 15 additions & 0 deletions packages/core/src/utils/getRootSpan.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
import type { Span } from '@sentry/types';

/**
* Returns the root span of a given span.
*
* As long as we use `Transaction`s internally, the returned root span
* will be a `Transaction` but be aware that this might change in the future.
*
* If the given span has no root span or transaction, `undefined` is returned.
*/
export function getRootSpan(span: Span): Span | undefined {
// TODO (v8): Remove this check and just return span
// eslint-disable-next-line deprecation/deprecation
return span.transaction;
}
11 changes: 5 additions & 6 deletions packages/core/test/lib/tracing/dynamicSamplingContext.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
});

test('returns the DSC provided during transaction creation', () => {
// eslint-disable-next-line deprecation/deprecation
// eslint-disable-next-line deprecation/deprecation -- using old API on purpose
const transaction = new Transaction({
name: 'tx',
metadata: { dynamicSamplingContext: { environment: 'myEnv' } },
Expand DownExpand Up@@ -68,7 +68,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
});

test('returns a new DSC, if no DSC was provided during transaction creation (via new Txn and deprecated metadata)', () => {
// eslint-disable-next-line deprecation/deprecation
// eslint-disable-next-line deprecation/deprecation -- using old API on purpose
const transaction = new Transaction({
name: 'tx',
metadata: {
Expand All@@ -92,7 +92,7 @@ describe('getDynamicSamplingContextFromSpan', () => {

describe('Including transaction name in DSC', () => {
test('is not included if transaction source is url', () => {
// eslint-disable-next-line deprecation/deprecation
// eslint-disable-next-line deprecation/deprecation -- using old API on purpose
const transaction = new Transaction({
name: 'tx',
metadata: {
Expand All@@ -109,8 +109,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
['is included if transaction source is parameterized route/url', 'route'],
['is included if transaction source is a custom name', 'custom'],
])('%s', (_: string, source) => {
// eslint-disable-next-line deprecation/deprecation
const transaction = new Transaction({
const transaction = startInactiveSpan({
name: 'tx',
metadata: {
...(source && { source: source as TransactionSource }),
Expand All@@ -120,7 +119,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
// Only setting the attribute manually because we're directly calling new Transaction()
transaction?.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);

const dsc = getDynamicSamplingContextFromSpan(transaction);
const dsc = getDynamicSamplingContextFromSpan(transaction!);

expect(dsc.transaction).toEqual('tx');
});
Expand Down
36 changes: 36 additions & 0 deletions packages/core/test/lib/utils/getRootSpan.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
import { Span, Transaction, getRootSpan } from '../../../src';

describe('getRootSpan', () => {
it('returns the root span of a span (Span)', () => {
const root = new Span({ name: 'test' });
// @ts-expect-error this is highly illegal and shouldn't happen IRL
// eslint-disable-next-line deprecation/deprecation
root.transaction = root;

// eslint-disable-next-line deprecation/deprecation
const childSpan = root.startChild({ name: 'child' });
expect(getRootSpan(childSpan)).toBe(root);
});

it('returns the root span of a span (Transaction)', () => {
// eslint-disable-next-line deprecation/deprecation
const root = new Transaction({ name: 'test' });

// eslint-disable-next-line deprecation/deprecation
const childSpan = root.startChild({ name: 'child' });
expect(getRootSpan(childSpan)).toBe(root);
});

it('returns the span itself if it is a root span', () => {
// eslint-disable-next-line deprecation/deprecation
const span = new Transaction({ name: 'test' });

expect(getRootSpan(span)).toBe(span);
});

it('returns undefined if span has no root span', () => {
const span = new Span({ name: 'test' });

expect(getRootSpan(span)).toBe(undefined);
});
});
4 changes: 2 additions & 2 deletions packages/opentelemetry-node/src/propagator.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import type { Baggage, Context, TextMapGetter, TextMapSetter } from '@opentelemetry/api';
import { TraceFlags, isSpanContextValid, propagation, trace } from '@opentelemetry/api';
import { W3CBaggagePropagator, isTracingSuppressed } from '@opentelemetry/core';
import { getDynamicSamplingContextFromSpan, spanToTraceHeader } from '@sentry/core';
import { getDynamicSamplingContextFromSpan, getRootSpan, spanToTraceHeader } from '@sentry/core';
import {
SENTRY_BAGGAGE_KEY_PREFIX,
baggageHeaderToDynamicSamplingContext,
Expand DownExpand Up@@ -35,7 +35,7 @@ export class SentryPropagator extends W3CBaggagePropagator {
if (span) {
setter.set(carrier, SENTRY_TRACE_HEADER, spanToTraceHeader(span));

if (span.transaction) {
if (getRootSpan(span)) {
const dynamicSamplingContext = getDynamicSamplingContextFromSpan(span);
baggage = Object.entries(dynamicSamplingContext).reduce<Baggage>((b, [dscKey, dscValue]) => {
if (dscValue) {
Expand Down
3 changes: 2 additions & 1 deletion packages/opentelemetry-node/src/utils/spanMap.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import { getRootSpan } from '@sentry/core';
import type { Span as SentrySpan } from '@sentry/types';

interface SpanMapEntry {
Expand DownExpand Up@@ -31,7 +32,7 @@ export function getSentrySpan(spanId: string): SentrySpan | undefined {
export function setSentrySpan(spanId: string, sentrySpan: SentrySpan): void {
let ref: SpanRefType = SPAN_REF_ROOT;

const rootSpanId = sentrySpan.transaction?.spanContext().spanId;
const rootSpanId = getRootSpan(sentrySpan)?.spanContext().spanId;

if (rootSpanId && rootSpanId !== spanId) {
const root = SPAN_MAP.get(rootSpanId);
Expand Down
1 change: 1 addition & 0 deletions packages/svelte/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,7 @@
},
"dependencies": {
"@sentry/browser": "7.93.0",
"@sentry/core": "7.93.0",
"@sentry/types": "7.93.0",
"@sentry/utils": "7.93.0",
"magic-string": "^0.30.0"
Expand Down
3 changes: 2 additions & 1 deletion packages/svelte/src/performance.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import type { Span, Transaction } from '@sentry/types';
import { afterUpdate, beforeUpdate, onMount } from 'svelte';
import { current_component } from 'svelte/internal';

import { getRootSpan } from '@sentry/core';
import { DEFAULT_COMPONENT_NAME, UI_SVELTE_INIT, UI_SVELTE_UPDATE } from './constants';
import type { TrackComponentOptions } from './types';

Expand DownExpand Up@@ -74,7 +75,7 @@ function recordUpdateSpans(componentName: string, initSpan?: Span): void {
// If we are initializing the component when the update span is started, we start it as child
// of the init span. Else, we start it as a child of the transaction.
const parentSpan =
initSpan && !initSpan.endTimestamp && initSpan.transaction === transaction ? initSpan : transaction;
initSpan && !initSpan.endTimestamp && getRootSpan(initSpan) === transaction ? initSpan : transaction;

// eslint-disable-next-line deprecation/deprecation
updateSpan = parentSpan.startChild({
Expand Down
3 changes: 2 additions & 1 deletion packages/tracing-internal/src/browser/request.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@ import {
getCurrentScope,
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
getRootSpan,
hasTracingEnabled,
spanToJSON,
spanToTraceHeader,
Expand DownExpand Up@@ -298,7 +299,7 @@ export function xhrCallback(

if (xhr.setRequestHeader && shouldAttachHeaders(sentryXhrData.url)) {
if (span) {
const transaction = span && span.transaction;
const transaction = span && getRootSpan(span);
const dynamicSamplingContext = transaction && getDynamicSamplingContextFromSpan(transaction);
const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
setHeaderOnXhr(xhr, spanToTraceHeader(span), sentryBaggageHeader);
Expand Down
3 changes: 2 additions & 1 deletion packages/tracing-internal/src/common/fetch.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ import {
getCurrentScope,
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
getRootSpan,
hasTracingEnabled,
spanToTraceHeader,
} from '@sentry/core';
Expand DownExpand Up@@ -134,7 +135,7 @@ export function addTracingHeadersToFetchRequest(
// eslint-disable-next-line deprecation/deprecation
const span = requestSpan || scope.getSpan();

const transaction = span && span.transaction;
const transaction = span && getRootSpan(span);

const { traceId, sampled, dsc } = scope.getPropagationContext();

Expand Down
1 change: 1 addition & 0 deletions packages/types/src/span.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -216,6 +216,7 @@ export interface Span extends SpanContext {

/**
* The transaction containing this span
* @deprecated Use top level `Sentry.getRootSpan()` instead
*/
transaction?: Transaction;

Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MIGRATION.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -152,6 +152,7 @@ In v8, the Span class is heavily reworked. The following properties & methods ar
- `span.setTag()`: Use `span.setAttribute()` instead or set tags on the surrounding scope.
- `span.setData()`: Use `span.setAttribute()` instead.
- `span.instrumenter` This field was removed and will be replaced internally.
- `span.transaction`: Use `getRootSpan` utility function instead.
- `transaction.setContext()`: Set context on the surrounding scope instead.

## Deprecate `pushScope` & `popScope` in favor of `withScope`
Expand Down
7 changes: 4 additions & 3 deletions packages/astro/src/server/meta.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import {
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
getRootSpan,
spanToTraceHeader,
} from '@sentry/core';
import type { Client, Scope, Span } from '@sentry/types';
Expand DownExpand Up@@ -32,12 +33,12 @@ export function getTracingMetaTags(
client: Client | undefined,
): { sentryTrace: string; baggage?: string } {
const { dsc, sampled, traceId } = scope.getPropagationContext();
const transaction = span?.transaction;
const rootSpan = span && getRootSpan(span);

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

const dynamicSamplingContext = transaction
? getDynamicSamplingContextFromSpan(transaction)
const dynamicSamplingContext = rootSpan
? getDynamicSamplingContextFromSpan(rootSpan)
: dsc
? dsc
: client
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -81,6 +81,7 @@ export {
spanToJSON,
spanIsSampled,
} from './utils/spanUtils';
export { getRootSpan } from './utils/getRootSpan';
export { DEFAULT_ENVIRONMENT } from './constants';
export { ModuleMetadata } from './integrations/metadata';
export { RequestData } from './integrations/requestdata';
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/scope.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -334,6 +334,9 @@ export class Scope implements ScopeInterface {
// Often, this span (if it exists at all) will be a transaction, but it's not guaranteed to be. Regardless, it will
// have a pointer to the currently-active transaction.
const span = this._span;
// Cannot replace with getRootSpan because getRootSpan returns a span, not a transaction
// Also, this method will be removed anyway.
// eslint-disable-next-line deprecation/deprecation
return span && span.transaction;
}

Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/server-runtime-client.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@ import {
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
} from './tracing';
import { getRootSpan } from './utils/getRootSpan';
import { spanToTraceContext } from './utils/spanUtils';

export interface ServerRuntimeClientOptions extends ClientOptions<BaseTransportOptions> {
Expand DownExpand Up@@ -262,7 +263,7 @@ export class ServerRuntimeClient<
// eslint-disable-next-line deprecation/deprecation
const span = scope.getSpan();
if (span) {
const samplingContext = span.transaction ? getDynamicSamplingContextFromSpan(span) : undefined;
const samplingContext = getRootSpan(span) ? getDynamicSamplingContextFromSpan(span) : undefined;
return [samplingContext, spanToTraceContext(span)];
}

Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/tracing/dynamicSamplingContext.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import { dropUndefinedKeys } from '@sentry/utils';

import { DEFAULT_ENVIRONMENT } from '../constants';
import { getClient, getCurrentScope } from '../exports';
import { getRootSpan } from '../utils/getRootSpan';
import { spanIsSampled, spanToJSON } from '../utils/spanUtils';

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

// As long as we use `Transaction`s internally, this should be fine.
// TODO: We need to replace this with a `getRootSpan(span)` function though
const txn = span.transaction as TransactionWithV7FrozenDsc | undefined;
// TODO (v8): Remove v7FrozenDsc as a Transaction will no longer have _frozenDynamicSamplingContext
const txn = getRootSpan(span) as TransactionWithV7FrozenDsc | undefined;
if (!txn) {
return dsc;
}
Expand Down
17 changes: 12 additions & 5 deletions packages/core/src/tracing/span.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,6 +16,7 @@ import type {
import { dropUndefinedKeys, logger, timestampInSeconds, uuid4 } from '@sentry/utils';

import { DEBUG_BUILD } from '../debug-build';
import { getRootSpan } from '../utils/getRootSpan';
import {
TRACE_FLAG_NONE,
TRACE_FLAG_SAMPLED,
Expand DownExpand Up@@ -105,6 +106,7 @@ export class Span implements SpanInterface {

/**
* @inheritDoc
* @deprecated Use top level `Sentry.getRootSpan()` instead
*/
public transaction?: Transaction;

Expand DownExpand Up@@ -304,12 +306,16 @@ export class Span implements SpanInterface {
childSpan.spanRecorder.add(childSpan);
}

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

if (DEBUG_BUILD && childSpan.transaction) {
if (DEBUG_BUILD && rootSpan) {
const opStr = (spanContext && spanContext.op) || '< unknown op >';
const nameStr = spanToJSON(childSpan).description || '< unknown name >';
const idStr = childSpan.transaction.spanContext().spanId;
const idStr = rootSpan.spanContext().spanId;

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

/** @inheritdoc */
public end(endTimestamp?: SpanTimeInput): void {
const rootSpan = getRootSpan(this);
if (
DEBUG_BUILD &&
// Don't call this for transactions
this.transaction &&
this.transaction.spanContext().spanId !== this._spanId
rootSpan &&
rootSpan.spanContext().spanId !== this._spanId
) {
const logMessage = this._logMessage;
if (logMessage) {
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/tracing/transaction.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -66,6 +66,8 @@ export class Transaction extends SpanClass implements TransactionInterface {
this._trimEnd = transactionContext.trimEnd;

// this is because transactions are also spans, and spans have a transaction pointer
// TODO (v8): Replace this with another way to set the root span

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there even a todo for v8 here? Won't we get rid of the transaction class?

@Lms24Lms24Jan 10, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I mean we somehow still need to construct a transaction for the event payload so I guess it's not gonna vanish completely. We'll just need some way to set and find the root span/txn of a span in v8.

// eslint-disable-next-line deprecation/deprecation
this.transaction = this;

// If Dynamic Sampling Context is provided during the creation of the transaction, we freeze it as it usually means
Expand Down
7 changes: 4 additions & 3 deletions packages/core/src/utils/applyScopeDataToEvent.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import type { Breadcrumb, Event, PropagationContext, ScopeData, Span } from '@sentry/types';
import { arrayify } from '@sentry/utils';
import { getDynamicSamplingContextFromSpan } from '../tracing/dynamicSamplingContext';
import { getRootSpan } from './getRootSpan';
import { spanToJSON, spanToTraceContext } from './spanUtils';

/**
Expand DownExpand Up@@ -174,13 +175,13 @@ function applySdkMetadataToEvent(

function applySpanToEvent(event: Event, span: Span): void {
event.contexts = { trace: spanToTraceContext(span), ...event.contexts };
const transaction = span.transaction;
if (transaction) {
const rootSpan = getRootSpan(span);
if (rootSpan) {
event.sdkProcessingMetadata = {
dynamicSamplingContext: getDynamicSamplingContextFromSpan(span),
...event.sdkProcessingMetadata,
};
const transactionName = spanToJSON(transaction).description;
const transactionName = spanToJSON(rootSpan).description;
if (transactionName) {
event.tags = { transaction: transactionName, ...event.tags };
}
Expand Down
15 changes: 15 additions & 0 deletions packages/core/src/utils/getRootSpan.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
import type { Span } from '@sentry/types';

/**
* Returns the root span of a given span.
*
* As long as we use `Transaction`s internally, the returned root span
* will be a `Transaction` but be aware that this might change in the future.
*
* If the given span has no root span or transaction, `undefined` is returned.
*/
export function getRootSpan(span: Span): Span | undefined {
// TODO (v8): Remove this check and just return span
// eslint-disable-next-line deprecation/deprecation
return span.transaction;
}
11 changes: 5 additions & 6 deletions packages/core/test/lib/tracing/dynamicSamplingContext.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
});

test('returns the DSC provided during transaction creation', () => {
// eslint-disable-next-line deprecation/deprecation
// eslint-disable-next-line deprecation/deprecation -- using old API on purpose
const transaction = new Transaction({
name: 'tx',
metadata: { dynamicSamplingContext: { environment: 'myEnv' } },
Expand DownExpand Up@@ -68,7 +68,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
});

test('returns a new DSC, if no DSC was provided during transaction creation (via new Txn and deprecated metadata)', () => {
// eslint-disable-next-line deprecation/deprecation
// eslint-disable-next-line deprecation/deprecation -- using old API on purpose
const transaction = new Transaction({
name: 'tx',
metadata: {
Expand All@@ -92,7 +92,7 @@ describe('getDynamicSamplingContextFromSpan', () => {

describe('Including transaction name in DSC', () => {
test('is not included if transaction source is url', () => {
// eslint-disable-next-line deprecation/deprecation
// eslint-disable-next-line deprecation/deprecation -- using old API on purpose
const transaction = new Transaction({
name: 'tx',
metadata: {
Expand All@@ -109,8 +109,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
['is included if transaction source is parameterized route/url', 'route'],
['is included if transaction source is a custom name', 'custom'],
])('%s', (_: string, source) => {
// eslint-disable-next-line deprecation/deprecation
const transaction = new Transaction({
const transaction = startInactiveSpan({
name: 'tx',
metadata: {
...(source && { source: source as TransactionSource }),
Expand All@@ -120,7 +119,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
// Only setting the attribute manually because we're directly calling new Transaction()
transaction?.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);

const dsc = getDynamicSamplingContextFromSpan(transaction);
const dsc = getDynamicSamplingContextFromSpan(transaction!);

expect(dsc.transaction).toEqual('tx');
});
Expand Down
36 changes: 36 additions & 0 deletions packages/core/test/lib/utils/getRootSpan.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
import { Span, Transaction, getRootSpan } from '../../../src';

describe('getRootSpan', () => {
it('returns the root span of a span (Span)', () => {
const root = new Span({ name: 'test' });
// @ts-expect-error this is highly illegal and shouldn't happen IRL
// eslint-disable-next-line deprecation/deprecation
root.transaction = root;

// eslint-disable-next-line deprecation/deprecation
const childSpan = root.startChild({ name: 'child' });
expect(getRootSpan(childSpan)).toBe(root);
});

it('returns the root span of a span (Transaction)', () => {
// eslint-disable-next-line deprecation/deprecation
const root = new Transaction({ name: 'test' });

// eslint-disable-next-line deprecation/deprecation
const childSpan = root.startChild({ name: 'child' });
expect(getRootSpan(childSpan)).toBe(root);
});

it('returns the span itself if it is a root span', () => {
// eslint-disable-next-line deprecation/deprecation
const span = new Transaction({ name: 'test' });

expect(getRootSpan(span)).toBe(span);
});

it('returns undefined if span has no root span', () => {
const span = new Span({ name: 'test' });

expect(getRootSpan(span)).toBe(undefined);
});
});
4 changes: 2 additions & 2 deletions packages/opentelemetry-node/src/propagator.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
import type { Baggage, Context, TextMapGetter, TextMapSetter } from '@opentelemetry/api';
import { TraceFlags, isSpanContextValid, propagation, trace } from '@opentelemetry/api';
import { W3CBaggagePropagator, isTracingSuppressed } from '@opentelemetry/core';
import { getDynamicSamplingContextFromSpan, spanToTraceHeader } from '@sentry/core';
import { getDynamicSamplingContextFromSpan, getRootSpan, spanToTraceHeader } from '@sentry/core';
import {
SENTRY_BAGGAGE_KEY_PREFIX,
baggageHeaderToDynamicSamplingContext,
Expand DownExpand Up@@ -35,7 +35,7 @@ export class SentryPropagator extends W3CBaggagePropagator {
if (span) {
setter.set(carrier, SENTRY_TRACE_HEADER, spanToTraceHeader(span));

if (span.transaction) {
if (getRootSpan(span)) {
const dynamicSamplingContext = getDynamicSamplingContextFromSpan(span);
baggage = Object.entries(dynamicSamplingContext).reduce<Baggage>((b, [dscKey, dscValue]) => {
if (dscValue) {
Expand Down
3 changes: 2 additions & 1 deletion packages/opentelemetry-node/src/utils/spanMap.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import { getRootSpan } from '@sentry/core';
import type { Span as SentrySpan } from '@sentry/types';

interface SpanMapEntry {
Expand DownExpand Up@@ -31,7 +32,7 @@ export function getSentrySpan(spanId: string): SentrySpan | undefined {
export function setSentrySpan(spanId: string, sentrySpan: SentrySpan): void {
let ref: SpanRefType = SPAN_REF_ROOT;

const rootSpanId = sentrySpan.transaction?.spanContext().spanId;
const rootSpanId = getRootSpan(sentrySpan)?.spanContext().spanId;

if (rootSpanId && rootSpanId !== spanId) {
const root = SPAN_MAP.get(rootSpanId);
Expand Down
1 change: 1 addition & 0 deletions packages/svelte/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,7 @@
},
"dependencies": {
"@sentry/browser": "7.93.0",
"@sentry/core": "7.93.0",
"@sentry/types": "7.93.0",
"@sentry/utils": "7.93.0",
"magic-string": "^0.30.0"
Expand Down
3 changes: 2 additions & 1 deletion packages/svelte/src/performance.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import type { Span, Transaction } from '@sentry/types';
import { afterUpdate, beforeUpdate, onMount } from 'svelte';
import { current_component } from 'svelte/internal';

import { getRootSpan } from '@sentry/core';
import { DEFAULT_COMPONENT_NAME, UI_SVELTE_INIT, UI_SVELTE_UPDATE } from './constants';
import type { TrackComponentOptions } from './types';

Expand DownExpand Up@@ -74,7 +75,7 @@ function recordUpdateSpans(componentName: string, initSpan?: Span): void {
// If we are initializing the component when the update span is started, we start it as child
// of the init span. Else, we start it as a child of the transaction.
const parentSpan =
initSpan && !initSpan.endTimestamp && initSpan.transaction === transaction ? initSpan : transaction;
initSpan && !initSpan.endTimestamp && getRootSpan(initSpan) === transaction ? initSpan : transaction;

// eslint-disable-next-line deprecation/deprecation
updateSpan = parentSpan.startChild({
Expand Down
3 changes: 2 additions & 1 deletion packages/tracing-internal/src/browser/request.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@ import {
getCurrentScope,
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
getRootSpan,
hasTracingEnabled,
spanToJSON,
spanToTraceHeader,
Expand DownExpand Up@@ -298,7 +299,7 @@ export function xhrCallback(

if (xhr.setRequestHeader && shouldAttachHeaders(sentryXhrData.url)) {
if (span) {
const transaction = span && span.transaction;
const transaction = span && getRootSpan(span);
const dynamicSamplingContext = transaction && getDynamicSamplingContextFromSpan(transaction);
const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
setHeaderOnXhr(xhr, spanToTraceHeader(span), sentryBaggageHeader);
Expand Down
3 changes: 2 additions & 1 deletion packages/tracing-internal/src/common/fetch.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ import {
getCurrentScope,
getDynamicSamplingContextFromClient,
getDynamicSamplingContextFromSpan,
getRootSpan,
hasTracingEnabled,
spanToTraceHeader,
} from '@sentry/core';
Expand DownExpand Up@@ -134,7 +135,7 @@ export function addTracingHeadersToFetchRequest(
// eslint-disable-next-line deprecation/deprecation
const span = requestSpan || scope.getSpan();

const transaction = span && span.transaction;
const transaction = span && getRootSpan(span);

const { traceId, sampled, dsc } = scope.getPropagationContext();

Expand Down
1 change: 1 addition & 0 deletions packages/types/src/span.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -216,6 +216,7 @@ export interface Span extends SpanContext {

/**
* The transaction containing this span
* @deprecated Use top level `Sentry.getRootSpan()` instead
*/
transaction?: Transaction;

Expand Down