diff --git a/MIGRATION.md b/MIGRATION.md index df21637dc6a4..0d27fa476ffd 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -193,6 +193,7 @@ In v8, the Span class is heavily reworked. The following properties & methods ar - `span.getTraceContext()`: Use `spanToTraceContext(span)` utility function instead. - `span.sampled`: Use `span.isRecording()` instead. - `span.spanId`: Use `span.spanContext().spanId` instead. +- `span.parentSpanId`: Use `spanToJSON(span).parent_span_id` instead. - `span.traceId`: Use `span.spanContext().traceId` instead. - `span.name`: Use `spanToJSON(span).description` instead. - `span.description`: Use `spanToJSON(span).description` instead. diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/test.ts index 95d09927e463..9df0e24f3a38 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import type { Event } from '@sentry/types'; +import type { SerializedEvent } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -10,7 +10,7 @@ sentryTest('should send a transaction in an envelope', async ({ getLocalTestPath } const url = await getLocalTestPath({ testDir: __dirname }); - const transaction = await getFirstSentryEnvelopeRequest(page, url); + const transaction = await getFirstSentryEnvelopeRequest(page, url); expect(transaction.transaction).toBe('parent_span'); expect(transaction.spans).toBeDefined(); @@ -22,14 +22,13 @@ sentryTest('should report finished spans as children of the root transaction', a } const url = await getLocalTestPath({ testDir: __dirname }); - const transaction = await getFirstSentryEnvelopeRequest(page, url); + const transaction = await getFirstSentryEnvelopeRequest(page, url); - const rootSpanId = transaction?.contexts?.trace?.spanId; + const rootSpanId = transaction?.contexts?.trace?.span_id; expect(transaction.spans).toHaveLength(1); const span_1 = transaction.spans?.[0]; - // eslint-disable-next-line deprecation/deprecation expect(span_1?.description).toBe('child_span'); - expect(span_1?.parentSpanId).toEqual(rootSpanId); + expect(span_1?.parent_span_id).toEqual(rootSpanId); }); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startTransaction/basic_usage/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startTransaction/basic_usage/test.ts index 7176b951225f..aa5a332ac748 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startTransaction/basic_usage/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startTransaction/basic_usage/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import type { Event } from '@sentry/types'; +import type { SerializedEvent } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -10,7 +10,7 @@ sentryTest('should report a transaction in an envelope', async ({ getLocalTestPa } const url = await getLocalTestPath({ testDir: __dirname }); - const transaction = await getFirstSentryEnvelopeRequest(page, url); + const transaction = await getFirstSentryEnvelopeRequest(page, url); expect(transaction.transaction).toBe('test_transaction_1'); expect(transaction.spans).toBeDefined(); @@ -22,28 +22,26 @@ sentryTest('should report finished spans as children of the root transaction', a } const url = await getLocalTestPath({ testDir: __dirname }); - const transaction = await getFirstSentryEnvelopeRequest(page, url); + const transaction = await getFirstSentryEnvelopeRequest(page, url); - const rootSpanId = transaction?.contexts?.trace?.spanId; + const rootSpanId = transaction?.contexts?.trace?.span_id; expect(transaction.spans).toHaveLength(3); const span_1 = transaction.spans?.[0]; - // eslint-disable-next-line deprecation/deprecation - expect(span_1?.op).toBe('span_1'); - expect(span_1?.parentSpanId).toEqual(rootSpanId); - // eslint-disable-next-line deprecation/deprecation - expect(span_1?.data).toMatchObject({ foo: 'bar', baz: [1, 2, 3] }); + expect(span_1).toBeDefined(); + expect(span_1!.op).toBe('span_1'); + expect(span_1!.parent_span_id).toEqual(rootSpanId); + expect(span_1!.data).toMatchObject({ foo: 'bar', baz: [1, 2, 3] }); const span_3 = transaction.spans?.[1]; - // eslint-disable-next-line deprecation/deprecation - expect(span_3?.op).toBe('span_3'); - expect(span_3?.parentSpanId).toEqual(rootSpanId); + expect(span_3).toBeDefined(); + expect(span_3!.op).toBe('span_3'); + expect(span_3!.parent_span_id).toEqual(rootSpanId); const span_5 = transaction.spans?.[2]; - // eslint-disable-next-line deprecation/deprecation - expect(span_5?.op).toBe('span_5'); - // eslint-disable-next-line deprecation/deprecation - expect(span_5?.parentSpanId).toEqual(span_3?.spanId); + expect(span_5).toBeDefined(); + expect(span_5!.op).toBe('span_5'); + expect(span_5!.parent_span_id).toEqual(span_3!.span_id); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fid/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fid/test.ts index e61588f91e73..90dde1ec2ecd 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fid/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fid/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import type { Event } from '@sentry/types'; +import type { SerializedEvent } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -16,16 +16,14 @@ sentryTest('should capture a FID vital.', async ({ browserName, getLocalTestPath // To trigger FID await page.click('#fid-btn'); - const eventData = await getFirstSentryEnvelopeRequest(page); + const eventData = await getFirstSentryEnvelopeRequest(page); expect(eventData.measurements).toBeDefined(); expect(eventData.measurements?.fid?.value).toBeDefined(); - // eslint-disable-next-line deprecation/deprecation const fidSpan = eventData.spans?.filter(({ description }) => description === 'first input delay')[0]; expect(fidSpan).toBeDefined(); - // eslint-disable-next-line deprecation/deprecation expect(fidSpan?.op).toBe('ui.action'); - expect(fidSpan?.parentSpanId).toBe(eventData.contexts?.trace_span_id); + expect(fidSpan?.parent_span_id).toBe(eventData.contexts?.trace?.span_id); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fp-fcp/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fp-fcp/test.ts index 2d8b77b61c7a..2ba417a84c18 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fp-fcp/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fp-fcp/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import type { Event } from '@sentry/types'; +import type { SerializedEvent } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -11,18 +11,16 @@ sentryTest('should capture FP vital.', async ({ browserName, getLocalTestPath, p } const url = await getLocalTestPath({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); + const eventData = await getFirstSentryEnvelopeRequest(page, url); expect(eventData.measurements).toBeDefined(); expect(eventData.measurements?.fp?.value).toBeDefined(); - // eslint-disable-next-line deprecation/deprecation const fpSpan = eventData.spans?.filter(({ description }) => description === 'first-paint')[0]; expect(fpSpan).toBeDefined(); - // eslint-disable-next-line deprecation/deprecation expect(fpSpan?.op).toBe('paint'); - expect(fpSpan?.parentSpanId).toBe(eventData.contexts?.trace_span_id); + expect(fpSpan?.parent_span_id).toBe(eventData.contexts?.trace?.span_id); }); sentryTest('should capture FCP vital.', async ({ getLocalTestPath, page }) => { @@ -31,16 +29,14 @@ sentryTest('should capture FCP vital.', async ({ getLocalTestPath, page }) => { } const url = await getLocalTestPath({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); + const eventData = await getFirstSentryEnvelopeRequest(page, url); expect(eventData.measurements).toBeDefined(); expect(eventData.measurements?.fcp?.value).toBeDefined(); - // eslint-disable-next-line deprecation/deprecation const fcpSpan = eventData.spans?.filter(({ description }) => description === 'first-contentful-paint')[0]; expect(fcpSpan).toBeDefined(); - // eslint-disable-next-line deprecation/deprecation expect(fcpSpan?.op).toBe('paint'); - expect(fcpSpan?.parentSpanId).toBe(eventData.contexts?.trace_span_id); + expect(fcpSpan?.parent_span_id).toBe(eventData.contexts?.trace?.span_id); }); diff --git a/docs/v8-new-performance-apis.md b/docs/v8-new-performance-apis.md index 2f258ec386cb..4b3c10f55a2a 100644 --- a/docs/v8-new-performance-apis.md +++ b/docs/v8-new-performance-apis.md @@ -46,7 +46,7 @@ below to see which things used to exist, and how they can/should be mapped going | --------------------- | ---------------------------------------------------- | | `traceId` | `spanContext().traceId` | | `spanId` | `spanContext().spanId` | -| `parentSpanId` | Unchanged | +| `parentSpanId` | `spanToJSON(span).parent_span_id` | | `status` | use utility method TODO | | `sampled` | `spanIsSampled(span)` | | `startTimestamp` | `startTime` - note that this has a different format! | diff --git a/packages/core/src/tracing/span.ts b/packages/core/src/tracing/span.ts index 6938ef00e47a..6371435e2d58 100644 --- a/packages/core/src/tracing/span.ts +++ b/packages/core/src/tracing/span.ts @@ -63,11 +63,6 @@ export class SpanRecorder { * Span contains all data about a span */ export class Span implements SpanInterface { - /** - * @inheritDoc - */ - public parentSpanId?: string; - /** * Tags for the span. * @deprecated Use `getSpanAttributes(span)` instead. @@ -112,6 +107,7 @@ export class Span implements SpanInterface { protected _traceId: string; protected _spanId: string; + protected _parentSpanId?: string; protected _sampled: boolean | undefined; protected _name?: string; protected _attributes: SpanAttributes; @@ -147,7 +143,7 @@ export class Span implements SpanInterface { this._name = spanContext.name || spanContext.description; if (spanContext.parentSpanId) { - this.parentSpanId = spanContext.parentSpanId; + this._parentSpanId = spanContext.parentSpanId; } // We want to include booleans as well here if ('sampled' in spanContext) { @@ -231,6 +227,24 @@ export class Span implements SpanInterface { this._spanId = spanId; } + /** + * @inheritDoc + * + * @deprecated Use `startSpan` functions instead. + */ + public set parentSpanId(string) { + this._parentSpanId = string; + } + + /** + * @inheritDoc + * + * @deprecated Use `spanToJSON(span).parent_span_id` instead. + */ + public get parentSpanId(): string | undefined { + return this._parentSpanId; + } + /** * Was this span chosen to be sent as part of the sample? * @deprecated Use `isRecording()` instead. @@ -531,7 +545,7 @@ export class Span implements SpanInterface { endTimestamp: this._endTime, // eslint-disable-next-line deprecation/deprecation op: this.op, - parentSpanId: this.parentSpanId, + parentSpanId: this._parentSpanId, sampled: this._sampled, spanId: this._spanId, startTimestamp: this._startTime, @@ -555,7 +569,7 @@ export class Span implements SpanInterface { this._endTime = spanContext.endTimestamp; // eslint-disable-next-line deprecation/deprecation this.op = spanContext.op; - this.parentSpanId = spanContext.parentSpanId; + this._parentSpanId = spanContext.parentSpanId; this._sampled = spanContext.sampled; this._spanId = spanContext.spanId || this._spanId; this._startTime = spanContext.startTimestamp || this._startTime; @@ -589,7 +603,7 @@ export class Span implements SpanInterface { data: this._getData(), description: this._name, op: this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] as string | undefined, - parent_span_id: this.parentSpanId, + parent_span_id: this._parentSpanId, span_id: this._spanId, start_timestamp: this._startTime, status: this._status, diff --git a/packages/core/test/lib/scope.test.ts b/packages/core/test/lib/scope.test.ts index 90678eb5062b..e4510ef58e3b 100644 --- a/packages/core/test/lib/scope.test.ts +++ b/packages/core/test/lib/scope.test.ts @@ -7,6 +7,7 @@ import { getCurrentScope, getIsolationScope, makeMain, + spanToJSON, startInactiveSpan, startSpan, withIsolationScope, @@ -543,12 +544,14 @@ describe('withActiveSpan()', () => { }); it('should create child spans when calling startSpan within the callback', done => { - expect.assertions(1); + expect.assertions(2); const inactiveSpan = startInactiveSpan({ name: 'inactive-span' }); withActiveSpan(inactiveSpan!, () => { startSpan({ name: 'child-span' }, childSpan => { + // eslint-disable-next-line deprecation/deprecation expect(childSpan?.parentSpanId).toBe(inactiveSpan?.spanContext().spanId); + expect(spanToJSON(childSpan!).parent_span_id).toBe(inactiveSpan?.spanContext().spanId); done(); }); }); diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts index 344bea5b7818..a2bdac43b6dd 100644 --- a/packages/core/test/lib/tracing/trace.test.ts +++ b/packages/core/test/lib/tracing/trace.test.ts @@ -276,6 +276,8 @@ describe('startSpan', () => { expect(getCurrentScope()).toBe(manualScope); expect(getActiveSpan()).toBe(span); + expect(spanToJSON(span!).parent_span_id).toBe('parent-span-id'); + // eslint-disable-next-line deprecation/deprecation expect(span?.parentSpanId).toBe('parent-span-id'); }); @@ -323,6 +325,8 @@ describe('startSpanManual', () => { expect(getCurrentScope()).not.toBe(initialScope); expect(getCurrentScope()).toBe(manualScope); expect(getActiveSpan()).toBe(span); + expect(spanToJSON(span!).parent_span_id).toBe('parent-span-id'); + // eslint-disable-next-line deprecation/deprecation expect(span?.parentSpanId).toBe('parent-span-id'); finish(); @@ -377,6 +381,8 @@ describe('startInactiveSpan', () => { const span = startInactiveSpan({ name: 'GET users/[id]', scope: manualScope }); expect(span).toBeDefined(); + expect(spanToJSON(span!).parent_span_id).toBe('parent-span-id'); + // eslint-disable-next-line deprecation/deprecation expect(span?.parentSpanId).toBe('parent-span-id'); expect(getActiveSpan()).toBeUndefined(); diff --git a/packages/opentelemetry-node/test/spanprocessor.test.ts b/packages/opentelemetry-node/test/spanprocessor.test.ts index 85e56a92f814..23818b17442e 100644 --- a/packages/opentelemetry-node/test/spanprocessor.test.ts +++ b/packages/opentelemetry-node/test/spanprocessor.test.ts @@ -93,6 +93,8 @@ describe('SentrySpanProcessor', () => { expect(spanToJSON(sentrySpanTransaction!).description).toBe('GET /users'); expect(spanToJSON(sentrySpanTransaction!).start_timestamp).toEqual(startTimestampMs / 1000); expect(sentrySpanTransaction?.spanContext().traceId).toEqual(otelSpan.spanContext().traceId); + expect(spanToJSON(sentrySpanTransaction!).parent_span_id).toEqual(otelSpan.parentSpanId); + // eslint-disable-next-line deprecation/deprecation expect(sentrySpanTransaction?.parentSpanId).toEqual(otelSpan.parentSpanId); expect(sentrySpanTransaction?.spanContext().spanId).toEqual(otelSpan.spanContext().spanId); @@ -121,6 +123,9 @@ describe('SentrySpanProcessor', () => { expect(sentrySpan ? spanToJSON(sentrySpan).description : undefined).toBe('SELECT * FROM users;'); expect(spanToJSON(sentrySpan!).start_timestamp).toEqual(startTimestampMs / 1000); expect(sentrySpan?.spanContext().spanId).toEqual(childOtelSpan.spanContext().spanId); + + expect(spanToJSON(sentrySpan!).parent_span_id).toEqual(sentrySpanTransaction?.spanContext().spanId); + // eslint-disable-next-line deprecation/deprecation expect(sentrySpan?.parentSpanId).toEqual(sentrySpanTransaction?.spanContext().spanId); // eslint-disable-next-line deprecation/deprecation @@ -162,6 +167,9 @@ describe('SentrySpanProcessor', () => { expect(spanToJSON(sentrySpan!).description).toBe('SELECT * FROM users;'); expect(spanToJSON(sentrySpan!).start_timestamp).toEqual(startTimestampMs / 1000); expect(sentrySpan?.spanContext().spanId).toEqual(childOtelSpan.spanContext().spanId); + + expect(spanToJSON(sentrySpan!).parent_span_id).toEqual(parentOtelSpan.spanContext().spanId); + // eslint-disable-next-line deprecation/deprecation expect(sentrySpan?.parentSpanId).toEqual(parentOtelSpan.spanContext().spanId); // eslint-disable-next-line deprecation/deprecation @@ -194,8 +202,16 @@ describe('SentrySpanProcessor', () => { const sentrySpan2 = getSpanForOtelSpan(span2); const sentrySpan3 = getSpanForOtelSpan(span3); + expect(spanToJSON(sentrySpan1!).parent_span_id).toEqual(sentrySpanTransaction?.spanContext().spanId); + // eslint-disable-next-line deprecation/deprecation expect(sentrySpan1?.parentSpanId).toEqual(sentrySpanTransaction?.spanContext().spanId); + + expect(spanToJSON(sentrySpan2!).parent_span_id).toEqual(sentrySpanTransaction?.spanContext().spanId); + // eslint-disable-next-line deprecation/deprecation expect(sentrySpan2?.parentSpanId).toEqual(sentrySpanTransaction?.spanContext().spanId); + + expect(spanToJSON(sentrySpan3!).parent_span_id).toEqual(sentrySpanTransaction?.spanContext().spanId); + // eslint-disable-next-line deprecation/deprecation expect(sentrySpan3?.parentSpanId).toEqual(sentrySpanTransaction?.spanContext().spanId); expect(spanToJSON(sentrySpan1!).description).toEqual('SELECT * FROM users;'); @@ -255,7 +271,14 @@ describe('SentrySpanProcessor', () => { expect(childSpan).toBeInstanceOf(Transaction); expect(spanToJSON(parentSpan!).timestamp).toBeDefined(); expect(spanToJSON(childSpan!).timestamp).toBeDefined(); + expect(spanToJSON(parentSpan!).parent_span_id).toBeUndefined(); + + expect(spanToJSON(parentSpan!).parent_span_id).toBeUndefined(); + // eslint-disable-next-line deprecation/deprecation expect(parentSpan?.parentSpanId).toBeUndefined(); + + expect(spanToJSON(childSpan!).parent_span_id).toEqual(parentSpan?.spanContext().spanId); + // eslint-disable-next-line deprecation/deprecation expect(childSpan?.parentSpanId).toEqual(parentSpan?.spanContext().spanId); }); }); diff --git a/packages/tracing-internal/src/browser/browsertracing.ts b/packages/tracing-internal/src/browser/browsertracing.ts index b09fb87a2dab..e9f61c73c0f3 100644 --- a/packages/tracing-internal/src/browser/browsertracing.ts +++ b/packages/tracing-internal/src/browser/browsertracing.ts @@ -6,6 +6,7 @@ import { addTracingExtensions, getActiveTransaction, spanIsSampled, + spanToJSON, startIdleTransaction, } from '@sentry/core'; import type { EventProcessor, Integration, Transaction, TransactionContext, TransactionSource } from '@sentry/types'; @@ -396,7 +397,7 @@ export class BrowserTracing implements Integration { scope.setPropagationContext({ traceId: idleTransaction.spanContext().traceId, spanId: idleTransaction.spanContext().spanId, - parentSpanId: idleTransaction.parentSpanId, + parentSpanId: spanToJSON(idleTransaction).parent_span_id, sampled: spanIsSampled(idleTransaction), }); } diff --git a/packages/types/src/span.ts b/packages/types/src/span.ts index bed08b56b6ee..f4eb56c88194 100644 --- a/packages/types/src/span.ts +++ b/packages/types/src/span.ts @@ -187,6 +187,13 @@ export interface Span extends Omit { */ spanId: string; + /** + * Parent Span ID + * + * @deprecated Use `spanToJSON(span).parent_span_id` instead. + */ + parentSpanId?: string; + /** * The ID of the trace. * @deprecated Use `spanContext().traceId` instead.