From 3c8260af6b81247220f3e0910e3a4f42729f2f3e Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 16 Jan 2024 09:14:04 +0100 Subject: [PATCH] feat(core): Deprecate span `startTimestamp` & `endTimestamp` Instead, you can use `spanToJSON()` to _read_ the timestamps. Updating timestamps after span creation will not be possible in v8. Also ensure that `span.end()` can only be called once. --- MIGRATION.md | 3 + packages/core/src/tracing/idletransaction.ts | 13 ++-- packages/core/src/tracing/span.ts | 70 +++++++++++++------ packages/core/src/tracing/trace.ts | 2 +- packages/core/src/tracing/transaction.ts | 22 +++--- packages/core/test/lib/tracing/span.test.ts | 20 ++++-- packages/core/test/lib/tracing/trace.test.ts | 20 +++--- .../core/test/lib/utils/spanUtils.test.ts | 4 +- packages/node/test/handlers.test.ts | 2 +- .../test/spanprocessor.test.ts | 32 ++++----- packages/react/src/profiler.tsx | 5 +- packages/svelte/src/performance.ts | 2 +- packages/svelte/test/performance.test.ts | 10 +-- .../src/browser/metrics/index.ts | 13 ++-- .../src/browser/metrics/utils.ts | 5 ++ .../src/node/integrations/mysql.ts | 2 +- .../test/browser/backgroundtab.test.ts | 4 +- .../test/browser/browsertracing.test.ts | 19 ++--- .../test/browser/metrics/utils.test.ts | 8 +-- .../test/browser/request.test.ts | 4 +- packages/tracing/test/idletransaction.test.ts | 49 +++++++------ packages/tracing/test/span.test.ts | 20 +++--- packages/types/src/span.ts | 9 ++- packages/vue/src/tracing.ts | 2 +- 24 files changed, 206 insertions(+), 134 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 61807fee0dfe..f459f5a6bd03 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -183,6 +183,9 @@ In v8, the Span class is heavily reworked. The following properties & methods ar - `transaction.setMeasurement()`: Use `Sentry.setMeasurement()` instead. In v8, setting measurements will be limited to the currently active root span. - `transaction.setName()`: Set the name with `.updateName()` and the source with `.setAttribute()` instead. +- `span.startTimestamp`: use `spanToJSON(span).start_timestamp` instead. You cannot update this anymore in v8. +- `span.endTimestamp`: use `spanToJSON(span).timestamp` instead. You cannot update this anymore in v8. You can pass a + custom end timestamp to `span.end(endTimestamp)`. ## Deprecate `pushScope` & `popScope` in favor of `withScope` diff --git a/packages/core/src/tracing/idletransaction.ts b/packages/core/src/tracing/idletransaction.ts index c29d82a1586e..156bb88dd5ba 100644 --- a/packages/core/src/tracing/idletransaction.ts +++ b/packages/core/src/tracing/idletransaction.ts @@ -4,7 +4,7 @@ import { logger, timestampInSeconds } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; import type { Hub } from '../hub'; -import { spanTimeInputToSeconds } from '../utils/spanUtils'; +import { spanTimeInputToSeconds, spanToJSON } from '../utils/spanUtils'; import type { Span } from './span'; import { SpanRecorder } from './span'; import { Transaction } from './transaction'; @@ -55,7 +55,7 @@ export class IdleTransactionSpanRecorder extends SpanRecorder { }; // We should only push new activities if the span does not have an end timestamp. - if (span.endTimestamp === undefined) { + if (spanToJSON(span).timestamp === undefined) { this._pushActivity(span.spanContext().spanId); } } @@ -167,18 +167,19 @@ export class IdleTransaction extends Transaction { } // We cancel all pending spans with status "cancelled" to indicate the idle transaction was finished early - if (!span.endTimestamp) { - span.endTimestamp = endTimestampInS; + if (!spanToJSON(span).timestamp) { span.setStatus('cancelled'); + span.end(endTimestampInS); DEBUG_BUILD && logger.log('[Tracing] cancelling span since transaction ended early', JSON.stringify(span, undefined, 2)); } - const spanStartedBeforeTransactionFinish = span.startTimestamp < endTimestampInS; + const { start_timestamp: startTime, timestamp: endTime } = spanToJSON(span); + const spanStartedBeforeTransactionFinish = startTime && startTime < endTimestampInS; // Add a delta with idle timeout so that we prevent false positives const timeoutWithMarginOfError = (this._finalTimeout + this._idleTimeout) / 1000; - const spanEndedBeforeFinalTimeout = span.endTimestamp - this.startTimestamp < timeoutWithMarginOfError; + const spanEndedBeforeFinalTimeout = endTime && startTime && endTime - startTime < timeoutWithMarginOfError; if (DEBUG_BUILD) { const stringifiedSpan = JSON.stringify(span, undefined, 2); diff --git a/packages/core/src/tracing/span.ts b/packages/core/src/tracing/span.ts index 353f2e4d694f..050b45593dd3 100644 --- a/packages/core/src/tracing/span.ts +++ b/packages/core/src/tracing/span.ts @@ -71,16 +71,6 @@ export class Span implements SpanInterface { */ public status?: SpanStatusType | string; - /** - * Timestamp in seconds when the span was created. - */ - public startTimestamp: number; - - /** - * Timestamp in seconds when the span ended. - */ - public endTimestamp?: number; - /** * @inheritDoc */ @@ -131,6 +121,10 @@ export class Span implements SpanInterface { protected _sampled: boolean | undefined; protected _name?: string; protected _attributes: SpanAttributes; + /** Epoch timestamp in seconds when the span started. */ + protected _startTime: number; + /** Epoch timestamp in seconds when the span ended. */ + protected _endTime?: number; private _logMessage?: string; @@ -144,7 +138,7 @@ export class Span implements SpanInterface { public constructor(spanContext: SpanContext = {}) { this._traceId = spanContext.traceId || uuid4(); this._spanId = spanContext.spanId || uuid4().substring(16); - this.startTimestamp = spanContext.startTimestamp || timestampInSeconds(); + this._startTime = spanContext.startTimestamp || timestampInSeconds(); // eslint-disable-next-line deprecation/deprecation this.tags = spanContext.tags ? { ...spanContext.tags } : {}; // eslint-disable-next-line deprecation/deprecation @@ -170,7 +164,7 @@ export class Span implements SpanInterface { this.status = spanContext.status; } if (spanContext.endTimestamp) { - this.endTimestamp = spanContext.endTimestamp; + this._endTime = spanContext.endTimestamp; } } @@ -273,6 +267,38 @@ export class Span implements SpanInterface { this._attributes = attributes; } + /** + * Timestamp in seconds (epoch time) indicating when the span started. + * @deprecated Use `spanToJSON()` instead. + */ + public get startTimestamp(): number { + return this._startTime; + } + + /** + * Timestamp in seconds (epoch time) indicating when the span started. + * @deprecated In v8, you will not be able to update the span start time after creation. + */ + public set startTimestamp(startTime: number) { + this._startTime = startTime; + } + + /** + * Timestamp in seconds when the span ended. + * @deprecated Use `spanToJSON()` instead. + */ + public get endTimestamp(): number | undefined { + return this._endTime; + } + + /** + * Timestamp in seconds when the span ended. + * @deprecated Set the end time via `span.end()` instead. + */ + public set endTimestamp(endTime: number | undefined) { + this._endTime = endTime; + } + /* eslint-enable @typescript-eslint/member-ordering */ /** @inheritdoc */ @@ -426,6 +452,10 @@ export class Span implements SpanInterface { /** @inheritdoc */ public end(endTimestamp?: SpanTimeInput): void { + // If already ended, skip + if (this._endTime) { + return; + } const rootSpan = getRootSpan(this); if ( DEBUG_BUILD && @@ -439,7 +469,7 @@ export class Span implements SpanInterface { } } - this.endTimestamp = spanTimeInputToSeconds(endTimestamp); + this._endTime = spanTimeInputToSeconds(endTimestamp); } /** @@ -460,12 +490,12 @@ export class Span implements SpanInterface { return dropUndefinedKeys({ data: this._getData(), description: this._name, - endTimestamp: this.endTimestamp, + endTimestamp: this._endTime, op: this.op, parentSpanId: this.parentSpanId, sampled: this._sampled, spanId: this._spanId, - startTimestamp: this.startTimestamp, + startTimestamp: this._startTime, status: this.status, // eslint-disable-next-line deprecation/deprecation tags: this.tags, @@ -483,12 +513,12 @@ export class Span implements SpanInterface { this.data = spanContext.data || {}; // eslint-disable-next-line deprecation/deprecation this._name = spanContext.name || spanContext.description; - this.endTimestamp = spanContext.endTimestamp; + this._endTime = spanContext.endTimestamp; this.op = spanContext.op; this.parentSpanId = spanContext.parentSpanId; this._sampled = spanContext.sampled; this._spanId = spanContext.spanId || this._spanId; - this.startTimestamp = spanContext.startTimestamp || this.startTimestamp; + this._startTime = spanContext.startTimestamp || this._startTime; this.status = spanContext.status; // eslint-disable-next-line deprecation/deprecation this.tags = spanContext.tags || {}; @@ -516,11 +546,11 @@ export class Span implements SpanInterface { op: this.op, parent_span_id: this.parentSpanId, span_id: this._spanId, - start_timestamp: this.startTimestamp, + start_timestamp: this._startTime, status: this.status, // eslint-disable-next-line deprecation/deprecation tags: Object.keys(this.tags).length > 0 ? this.tags : undefined, - timestamp: this.endTimestamp, + timestamp: this._endTime, trace_id: this._traceId, origin: this.origin, }); @@ -528,7 +558,7 @@ export class Span implements SpanInterface { /** @inheritdoc */ public isRecording(): boolean { - return !this.endTimestamp && !!this._sampled; + return !this._endTime && !!this._sampled; } /** diff --git a/packages/core/src/tracing/trace.ts b/packages/core/src/tracing/trace.ts index 015bf4757b8f..df3cebb1734d 100644 --- a/packages/core/src/tracing/trace.ts +++ b/packages/core/src/tracing/trace.ts @@ -242,7 +242,7 @@ export function startSpanManual( () => callback(activeSpan, finishAndSetSpan), () => { // Only update the span status if it hasn't been changed yet, and the span is not yet finished - if (activeSpan && !activeSpan.endTimestamp && (!activeSpan.status || activeSpan.status === 'ok')) { + if (activeSpan && activeSpan.isRecording() && (!activeSpan.status || activeSpan.status === 'ok')) { activeSpan.setStatus('internal_error'); } }, diff --git a/packages/core/src/tracing/transaction.ts b/packages/core/src/tracing/transaction.ts index d7c57f7828e8..28b5f1cbfb2b 100644 --- a/packages/core/src/tracing/transaction.ts +++ b/packages/core/src/tracing/transaction.ts @@ -16,7 +16,7 @@ import { DEBUG_BUILD } from '../debug-build'; import type { Hub } from '../hub'; import { getCurrentHub } from '../hub'; import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '../semanticAttributes'; -import { spanTimeInputToSeconds, spanToTraceContext } from '../utils/spanUtils'; +import { spanTimeInputToSeconds, spanToJSON, spanToTraceContext } from '../utils/spanUtils'; import { getDynamicSamplingContextFromSpan } from './dynamicSamplingContext'; import { Span as SpanClass, SpanRecorder } from './span'; @@ -257,7 +257,7 @@ export class Transaction extends SpanClass implements TransactionInterface { */ protected _finishTransaction(endTimestamp?: number): TransactionEvent | undefined { // This transaction is already finished, so we should not flush it again. - if (this.endTimestamp !== undefined) { + if (this._endTime !== undefined) { return undefined; } @@ -286,15 +286,15 @@ export class Transaction extends SpanClass implements TransactionInterface { return undefined; } - const finishedSpans = this.spanRecorder ? this.spanRecorder.spans.filter(s => s !== this && s.endTimestamp) : []; + const finishedSpans = this.spanRecorder + ? this.spanRecorder.spans.filter(span => span !== this && spanToJSON(span).timestamp) + : []; if (this._trimEnd && finishedSpans.length > 0) { - this.endTimestamp = finishedSpans.reduce((prev: SpanClass, current: SpanClass) => { - if (prev.endTimestamp && current.endTimestamp) { - return prev.endTimestamp > current.endTimestamp ? prev : current; - } - return prev; - }).endTimestamp; + const endTimes = finishedSpans.map(span => spanToJSON(span).timestamp).filter(Boolean) as number[]; + this._endTime = endTimes.reduce((prev, current) => { + return prev > current ? prev : current; + }); } // eslint-disable-next-line deprecation/deprecation @@ -310,10 +310,10 @@ export class Transaction extends SpanClass implements TransactionInterface { }, // TODO: Pass spans serialized via `spanToJSON()` here instead in v8. spans: finishedSpans, - start_timestamp: this.startTimestamp, + start_timestamp: this._startTime, // eslint-disable-next-line deprecation/deprecation tags: this.tags, - timestamp: this.endTimestamp, + timestamp: this._endTime, transaction: this._name, type: 'transaction', sdkProcessingMetadata: { diff --git a/packages/core/test/lib/tracing/span.test.ts b/packages/core/test/lib/tracing/span.test.ts index 1adff93123ac..2896ae494ad1 100644 --- a/packages/core/test/lib/tracing/span.test.ts +++ b/packages/core/test/lib/tracing/span.test.ts @@ -1,6 +1,6 @@ import { timestampInSeconds } from '@sentry/utils'; import { Span } from '../../../src'; -import { TRACE_FLAG_NONE, TRACE_FLAG_SAMPLED } from '../../../src/utils/spanUtils'; +import { TRACE_FLAG_NONE, TRACE_FLAG_SAMPLED, spanToJSON } from '../../../src/utils/spanUtils'; describe('span', () => { describe('name', () => { @@ -186,7 +186,7 @@ describe('span', () => { const now = timestampInSeconds(); span.end(); - expect(span.endTimestamp).toBeGreaterThanOrEqual(now); + expect(spanToJSON(span).timestamp).toBeGreaterThanOrEqual(now); }); it('works with endTimestamp in seconds', () => { @@ -194,7 +194,7 @@ describe('span', () => { const timestamp = timestampInSeconds() - 1; span.end(timestamp); - expect(span.endTimestamp).toEqual(timestamp); + expect(spanToJSON(span).timestamp).toEqual(timestamp); }); it('works with endTimestamp in milliseconds', () => { @@ -202,7 +202,7 @@ describe('span', () => { const timestamp = Date.now() - 1000; span.end(timestamp); - expect(span.endTimestamp).toEqual(timestamp / 1000); + expect(spanToJSON(span).timestamp).toEqual(timestamp / 1000); }); it('works with endTimestamp in array form', () => { @@ -210,7 +210,17 @@ describe('span', () => { const seconds = Math.floor(timestampInSeconds() - 1); span.end([seconds, 0]); - expect(span.endTimestamp).toEqual(seconds); + expect(spanToJSON(span).timestamp).toEqual(seconds); + }); + + it('skips if span is already ended', () => { + const startTimestamp = timestampInSeconds() - 5; + const endTimestamp = timestampInSeconds() - 1; + const span = new Span({ startTimestamp, endTimestamp }); + + span.end(); + + expect(spanToJSON(span).timestamp).toBe(endTimestamp); }); }); diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts index 4de432cbf0b3..86c5e6d72a2e 100644 --- a/packages/core/test/lib/tracing/trace.test.ts +++ b/packages/core/test/lib/tracing/trace.test.ts @@ -1,4 +1,4 @@ -import { Hub, addTracingExtensions, getCurrentScope, makeMain } from '../../../src'; +import { Hub, addTracingExtensions, getCurrentScope, makeMain, spanToJSON } from '../../../src'; import { Scope } from '../../../src/scope'; import { Span, @@ -183,17 +183,17 @@ describe('startSpan', () => { let _span: Span | undefined; startSpan({ name: 'GET users/[id]' }, span => { expect(span).toBeDefined(); - expect(span?.endTimestamp).toBeUndefined(); + expect(spanToJSON(span!).timestamp).toBeUndefined(); _span = span as Span; }); expect(_span).toBeDefined(); - expect(_span?.endTimestamp).toBeDefined(); + expect(spanToJSON(_span!).timestamp).toBeDefined(); }); it('allows to pass a `startTime`', () => { const start = startSpan({ name: 'outer', startTime: [1234, 0] }, span => { - return span?.startTimestamp; + return spanToJSON(span!).start_timestamp; }); expect(start).toEqual(1234); @@ -236,9 +236,9 @@ describe('startSpanManual', () => { it('creates & finishes span', async () => { startSpanManual({ name: 'GET users/[id]' }, (span, finish) => { expect(span).toBeDefined(); - expect(span?.endTimestamp).toBeUndefined(); + expect(spanToJSON(span!).timestamp).toBeUndefined(); finish(); - expect(span?.endTimestamp).toBeDefined(); + expect(spanToJSON(span!).timestamp).toBeDefined(); }); }); @@ -286,7 +286,7 @@ describe('startSpanManual', () => { it('allows to pass a `startTime`', () => { const start = startSpanManual({ name: 'outer', startTime: [1234, 0] }, span => { span?.end(); - return span?.startTimestamp; + return spanToJSON(span!).start_timestamp; }); expect(start).toEqual(1234); @@ -298,11 +298,11 @@ describe('startInactiveSpan', () => { const span = startInactiveSpan({ name: 'GET users/[id]' }); expect(span).toBeDefined(); - expect(span?.endTimestamp).toBeUndefined(); + expect(spanToJSON(span!).timestamp).toBeUndefined(); span?.end(); - expect(span?.endTimestamp).toBeDefined(); + expect(spanToJSON(span!).timestamp).toBeDefined(); }); it('does not set span on scope', () => { @@ -335,7 +335,7 @@ describe('startInactiveSpan', () => { it('allows to pass a `startTime`', () => { const span = startInactiveSpan({ name: 'outer', startTime: [1234, 0] }); - expect(span?.startTimestamp).toEqual(1234); + expect(spanToJSON(span!).start_timestamp).toEqual(1234); }); }); diff --git a/packages/core/test/lib/utils/spanUtils.test.ts b/packages/core/test/lib/utils/spanUtils.test.ts index e521df7c2dc9..82978854353a 100644 --- a/packages/core/test/lib/utils/spanUtils.test.ts +++ b/packages/core/test/lib/utils/spanUtils.test.ts @@ -54,7 +54,7 @@ describe('spanToJSON', () => { span_id: span.spanContext().spanId, trace_id: span.spanContext().traceId, origin: 'manual', - start_timestamp: span.startTimestamp, + start_timestamp: span['_startTime'], }); }); @@ -71,6 +71,7 @@ describe('spanToJSON', () => { traceId: 'abcd', origin: 'auto', startTimestamp: 123, + endTimestamp: 456, }); expect(spanToJSON(span)).toEqual({ @@ -85,6 +86,7 @@ describe('spanToJSON', () => { trace_id: 'abcd', origin: 'auto', start_timestamp: 123, + timestamp: 456, }); }); diff --git a/packages/node/test/handlers.test.ts b/packages/node/test/handlers.test.ts index 1b12a972d7d3..87e2ff9768da 100644 --- a/packages/node/test/handlers.test.ts +++ b/packages/node/test/handlers.test.ts @@ -462,7 +462,7 @@ describe('tracingHandler', () => { setImmediate(() => { expect(finishSpan).toHaveBeenCalled(); expect(finishTransaction).toHaveBeenCalled(); - expect(span.endTimestamp).toBeLessThanOrEqual(transaction.endTimestamp!); + expect(spanToJSON(span).timestamp).toBeLessThanOrEqual(spanToJSON(transaction).timestamp!); expect(sentEvent.spans?.length).toEqual(1); expect(sentEvent.spans?.[0].spanContext().spanId).toEqual(span.spanContext().spanId); done(); diff --git a/packages/opentelemetry-node/test/spanprocessor.test.ts b/packages/opentelemetry-node/test/spanprocessor.test.ts index 7eecb73ea8bb..305035490ea7 100644 --- a/packages/opentelemetry-node/test/spanprocessor.test.ts +++ b/packages/opentelemetry-node/test/spanprocessor.test.ts @@ -91,14 +91,14 @@ describe('SentrySpanProcessor', () => { expect(sentrySpanTransaction).toBeInstanceOf(Transaction); expect(spanToJSON(sentrySpanTransaction!).description).toBe('GET /users'); - expect(sentrySpanTransaction?.startTimestamp).toEqual(startTimestampMs / 1000); + expect(spanToJSON(sentrySpanTransaction!).start_timestamp).toEqual(startTimestampMs / 1000); expect(sentrySpanTransaction?.spanContext().traceId).toEqual(otelSpan.spanContext().traceId); expect(sentrySpanTransaction?.parentSpanId).toEqual(otelSpan.parentSpanId); expect(sentrySpanTransaction?.spanContext().spanId).toEqual(otelSpan.spanContext().spanId); otelSpan.end(endTime); - expect(sentrySpanTransaction?.endTimestamp).toBe(endTimestampMs / 1000); + expect(spanToJSON(sentrySpanTransaction!).timestamp).toBe(endTimestampMs / 1000); }); it('creates a child span if there is a running transaction', () => { @@ -119,7 +119,7 @@ describe('SentrySpanProcessor', () => { const sentrySpan = getSpanForOtelSpan(childOtelSpan); expect(sentrySpan).toBeInstanceOf(SentrySpan); expect(sentrySpan ? spanToJSON(sentrySpan).description : undefined).toBe('SELECT * FROM users;'); - expect(sentrySpan?.startTimestamp).toEqual(startTimestampMs / 1000); + expect(spanToJSON(sentrySpan!).start_timestamp).toEqual(startTimestampMs / 1000); expect(sentrySpan?.spanContext().spanId).toEqual(childOtelSpan.spanContext().spanId); expect(sentrySpan?.parentSpanId).toEqual(sentrySpanTransaction?.spanContext().spanId); @@ -128,7 +128,7 @@ describe('SentrySpanProcessor', () => { child.end(endTime); - expect(sentrySpan?.endTimestamp).toEqual(endTimestampMs / 1000); + expect(spanToJSON(sentrySpan!).timestamp).toEqual(endTimestampMs / 1000); }); parentOtelSpan.end(); @@ -160,7 +160,7 @@ describe('SentrySpanProcessor', () => { expect(sentrySpan).toBeInstanceOf(SentrySpan); expect(sentrySpan).toBeInstanceOf(Transaction); expect(spanToJSON(sentrySpan!).description).toBe('SELECT * FROM users;'); - expect(sentrySpan?.startTimestamp).toEqual(startTimestampMs / 1000); + expect(spanToJSON(sentrySpan!).start_timestamp).toEqual(startTimestampMs / 1000); expect(sentrySpan?.spanContext().spanId).toEqual(childOtelSpan.spanContext().spanId); expect(sentrySpan?.parentSpanId).toEqual(parentOtelSpan.spanContext().spanId); @@ -169,7 +169,7 @@ describe('SentrySpanProcessor', () => { child.end(endTime); - expect(sentrySpan?.endTimestamp).toEqual(endTimestampMs / 1000); + expect(spanToJSON(sentrySpan!).timestamp).toEqual(endTimestampMs / 1000); }); parentOtelSpan.end(); @@ -229,9 +229,9 @@ describe('SentrySpanProcessor', () => { expect(childSpan).toBeDefined(); expect(grandchildSpan).toBeDefined(); - expect(parentSpan?.endTimestamp).toBeDefined(); - expect(childSpan?.endTimestamp).toBeDefined(); - expect(grandchildSpan?.endTimestamp).toBeDefined(); + expect(spanToJSON(parentSpan!).timestamp).toBeDefined(); + expect(spanToJSON(childSpan!).timestamp).toBeDefined(); + expect(spanToJSON(grandchildSpan!).timestamp).toBeDefined(); }); }); }); @@ -253,8 +253,8 @@ describe('SentrySpanProcessor', () => { expect(childSpan).toBeDefined(); expect(parentSpan).toBeInstanceOf(Transaction); expect(childSpan).toBeInstanceOf(Transaction); - expect(parentSpan?.endTimestamp).toBeDefined(); - expect(childSpan?.endTimestamp).toBeDefined(); + expect(spanToJSON(parentSpan!).timestamp).toBeDefined(); + expect(spanToJSON(childSpan!).timestamp).toBeDefined(); expect(parentSpan?.parentSpanId).toBeUndefined(); expect(childSpan?.parentSpanId).toEqual(parentSpan?.spanContext().spanId); }); @@ -790,7 +790,7 @@ describe('SentrySpanProcessor', () => { otelSpan.end(); - expect(sentrySpanTransaction?.endTimestamp).toBeUndefined(); + expect(spanToJSON(sentrySpanTransaction!).timestamp).toBeUndefined(); // Ensure it is still removed from map! expect(getSpanForOtelSpan(otelSpan)).toBeUndefined(); @@ -809,7 +809,7 @@ describe('SentrySpanProcessor', () => { otelSpan.end(); - expect(sentrySpanTransaction?.endTimestamp).toBeDefined(); + expect(spanToJSON(sentrySpanTransaction!).timestamp).toBeDefined(); }); it('does not finish spans for Sentry request', async () => { @@ -833,7 +833,7 @@ describe('SentrySpanProcessor', () => { childOtelSpan.end(); parent.end(); - expect(sentrySpan?.endTimestamp).toBeUndefined(); + expect(spanToJSON(sentrySpan!).timestamp).toBeUndefined(); // Ensure it is still removed from map! expect(getSpanForOtelSpan(childOtelSpan)).toBeUndefined(); @@ -867,8 +867,8 @@ describe('SentrySpanProcessor', () => { child.end(); parent.end(); - expect(sentryGrandchildSpan?.endTimestamp).toBeDefined(); - expect(sentrySpan?.endTimestamp).toBeUndefined(); + expect(spanToJSON(sentryGrandchildSpan!).timestamp).toBeDefined(); + expect(spanToJSON(sentrySpan!).timestamp).toBeUndefined(); }, ); }); diff --git a/packages/react/src/profiler.tsx b/packages/react/src/profiler.tsx index 4a233407eab1..d8ee675ea742 100644 --- a/packages/react/src/profiler.tsx +++ b/packages/react/src/profiler.tsx @@ -2,6 +2,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import type { Hub } from '@sentry/browser'; import { getCurrentHub } from '@sentry/browser'; +import { spanToJSON } from '@sentry/core'; import type { Span, Transaction } from '@sentry/types'; import { timestampInSeconds } from '@sentry/utils'; import hoistNonReactStatics from 'hoist-non-react-statics'; @@ -124,7 +125,7 @@ class Profiler extends React.Component { endTimestamp: timestampInSeconds(), op: REACT_RENDER_OP, origin: 'auto.ui.react.profiler', - startTimestamp: this._mountSpan.endTimestamp, + startTimestamp: spanToJSON(this._mountSpan).timestamp, data: { 'ui.component_name': name }, }); } @@ -211,7 +212,7 @@ function useProfiler( endTimestamp: timestampInSeconds(), op: REACT_RENDER_OP, origin: 'auto.ui.react.profiler', - startTimestamp: mountSpan.endTimestamp, + startTimestamp: spanToJSON(mountSpan).timestamp, data: { 'ui.component_name': name }, }); } diff --git a/packages/svelte/src/performance.ts b/packages/svelte/src/performance.ts index 38d68468ffd6..d2e23b53daba 100644 --- a/packages/svelte/src/performance.ts +++ b/packages/svelte/src/performance.ts @@ -75,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 && getRootSpan(initSpan) === transaction ? initSpan : transaction; + initSpan && initSpan.isRecording() && getRootSpan(initSpan) === transaction ? initSpan : transaction; // eslint-disable-next-line deprecation/deprecation updateSpan = parentSpan.startChild({ diff --git a/packages/svelte/test/performance.test.ts b/packages/svelte/test/performance.test.ts index bde6b7a734db..9588da6e7d16 100644 --- a/packages/svelte/test/performance.test.ts +++ b/packages/svelte/test/performance.test.ts @@ -6,16 +6,18 @@ import DummyComponent from './components/Dummy.svelte'; let returnUndefinedTransaction = false; -const testTransaction: { spans: any[]; startChild: jest.Mock; end: jest.Mock } = { +const testTransaction: { spans: any[]; startChild: jest.Mock; end: jest.Mock; isRecording: () => boolean } = { spans: [], startChild: jest.fn(), end: jest.fn(), + isRecording: () => true, }; const testUpdateSpan = { end: jest.fn() }; const testInitSpan: any = { transaction: testTransaction, end: jest.fn(), startChild: jest.fn(), + isRecording: () => true, }; jest.mock('@sentry/core', () => { @@ -48,7 +50,7 @@ describe('Sentry.trackComponent()', () => { }); testInitSpan.end = jest.fn(); - testInitSpan.endTimestamp = undefined; + testInitSpan.isRecording = () => true; returnUndefinedTransaction = false; }); @@ -75,7 +77,7 @@ describe('Sentry.trackComponent()', () => { it('creates an update span, when the component is updated', async () => { // Make the end() function actually end the initSpan testInitSpan.end.mockImplementation(() => { - testInitSpan.endTimestamp = Date.now(); + testInitSpan.isRecording = () => false; }); // first we create the component @@ -171,7 +173,7 @@ describe('Sentry.trackComponent()', () => { it("doesn't record update spans, if there's no ongoing transaction at that time", async () => { // Make the end() function actually end the initSpan testInitSpan.end.mockImplementation(() => { - testInitSpan.endTimestamp = Date.now(); + testInitSpan.isRecording = () => false; }); // first we create the component diff --git a/packages/tracing-internal/src/browser/metrics/index.ts b/packages/tracing-internal/src/browser/metrics/index.ts index 36aa0c3659d9..7525653e8b2f 100644 --- a/packages/tracing-internal/src/browser/metrics/index.ts +++ b/packages/tracing-internal/src/browser/metrics/index.ts @@ -1,5 +1,6 @@ /* eslint-disable max-lines */ import type { IdleTransaction, Transaction } from '@sentry/core'; +import { spanToJSON } from '@sentry/core'; import { getActiveTransaction, setMeasurement } from '@sentry/core'; import type { Measurements, SpanContext } from '@sentry/types'; import { browserPerformanceTimeOrigin, getComponentName, htmlTreeAsString, logger } from '@sentry/utils'; @@ -185,12 +186,14 @@ export function addPerformanceEntries(transaction: Transaction): void { let responseStartTimestamp: number | undefined; let requestStartTimestamp: number | undefined; + const transactionStartTime = spanToJSON(transaction).start_timestamp; + // eslint-disable-next-line @typescript-eslint/no-explicit-any performanceEntries.slice(_performanceCursor).forEach((entry: Record) => { const startTime = msToSec(entry.startTime); const duration = msToSec(entry.duration); - if (transaction.op === 'navigation' && timeOrigin + startTime < transaction.startTimestamp) { + if (transaction.op === 'navigation' && transactionStartTime && timeOrigin + startTime < transactionStartTime) { return; } @@ -239,10 +242,10 @@ export function addPerformanceEntries(transaction: Transaction): void { if (transaction.op === 'pageload') { // Generate TTFB (Time to First Byte), which measured as the time between the beginning of the transaction and the // start of the response in milliseconds - if (typeof responseStartTimestamp === 'number') { + if (typeof responseStartTimestamp === 'number' && transactionStartTime) { DEBUG_BUILD && logger.log('[Measurements] Adding TTFB'); _measurements['ttfb'] = { - value: (responseStartTimestamp - transaction.startTimestamp) * 1000, + value: (responseStartTimestamp - transactionStartTime) * 1000, unit: 'millisecond', }; @@ -257,7 +260,7 @@ export function addPerformanceEntries(transaction: Transaction): void { } ['fcp', 'fp', 'lcp'].forEach(name => { - if (!_measurements[name] || timeOrigin >= transaction.startTimestamp) { + if (!_measurements[name] || !transactionStartTime || timeOrigin >= transactionStartTime) { return; } // The web vitals, fcp, fp, lcp, and ttfb, all measure relative to timeOrigin. @@ -267,7 +270,7 @@ export function addPerformanceEntries(transaction: Transaction): void { const measurementTimestamp = timeOrigin + msToSec(oldValue); // normalizedValue should be in milliseconds - const normalizedValue = Math.abs((measurementTimestamp - transaction.startTimestamp) * 1000); + const normalizedValue = Math.abs((measurementTimestamp - transactionStartTime) * 1000); const delta = normalizedValue - oldValue; DEBUG_BUILD && logger.log(`[Measurements] Normalized ${name} from ${oldValue} to ${normalizedValue} (${delta})`); diff --git a/packages/tracing-internal/src/browser/metrics/utils.ts b/packages/tracing-internal/src/browser/metrics/utils.ts index cebabd9abf1c..4669ff13b762 100644 --- a/packages/tracing-internal/src/browser/metrics/utils.ts +++ b/packages/tracing-internal/src/browser/metrics/utils.ts @@ -12,9 +12,14 @@ export function isMeasurementValue(value: unknown): value is number { * Helper function to start child on transactions. This function will make sure that the transaction will * use the start timestamp of the created child span if it is earlier than the transactions actual * start timestamp. + * + * Note: this will not be possible anymore in v8, + * unless we do some special handling for browser here... */ export function _startChild(transaction: Transaction, { startTimestamp, ...ctx }: SpanContext): Span { + // eslint-disable-next-line deprecation/deprecation if (startTimestamp && transaction.startTimestamp > startTimestamp) { + // eslint-disable-next-line deprecation/deprecation transaction.startTimestamp = startTimestamp; } diff --git a/packages/tracing-internal/src/node/integrations/mysql.ts b/packages/tracing-internal/src/node/integrations/mysql.ts index 285f4988e2f0..c7bf5451e5f5 100644 --- a/packages/tracing-internal/src/node/integrations/mysql.ts +++ b/packages/tracing-internal/src/node/integrations/mysql.ts @@ -85,7 +85,7 @@ export class Mysql implements LazyLoadedIntegration { } function finishSpan(span: Span | undefined): void { - if (!span || span.endTimestamp) { + if (!span) { return; } diff --git a/packages/tracing-internal/test/browser/backgroundtab.test.ts b/packages/tracing-internal/test/browser/backgroundtab.test.ts index 66310b122cbc..6c7ea89d982f 100644 --- a/packages/tracing-internal/test/browser/backgroundtab.test.ts +++ b/packages/tracing-internal/test/browser/backgroundtab.test.ts @@ -1,4 +1,4 @@ -import { Hub, makeMain, startSpan } from '@sentry/core'; +import { Hub, makeMain, spanToJSON, startSpan } from '@sentry/core'; import { JSDOM } from 'jsdom'; import { addExtensionMethods } from '../../../tracing/src'; @@ -58,7 +58,7 @@ conditionalTest({ min: 10 })('registerBackgroundTabDetection', () => { expect(span?.status).toBe('cancelled'); // eslint-disable-next-line deprecation/deprecation expect(span?.tags.visibilitychange).toBe('document.hidden'); - expect(span?.endTimestamp).toBeDefined(); + expect(spanToJSON(span!).timestamp).toBeDefined(); }); }); }); diff --git a/packages/tracing-internal/test/browser/browsertracing.test.ts b/packages/tracing-internal/test/browser/browsertracing.test.ts index 81efa21d869f..54242d83eeac 100644 --- a/packages/tracing-internal/test/browser/browsertracing.test.ts +++ b/packages/tracing-internal/test/browser/browsertracing.test.ts @@ -1,9 +1,10 @@ /* eslint-disable deprecation/deprecation */ -import { Hub, TRACING_DEFAULTS, makeMain, setCurrentClient } from '@sentry/core'; +import { Hub, TRACING_DEFAULTS, makeMain, setCurrentClient, spanToJSON } from '@sentry/core'; import * as hubExtensions from '@sentry/core'; import type { BaseTransportOptions, ClientOptions, DsnComponents, HandlerDataHistory } from '@sentry/types'; import { JSDOM } from 'jsdom'; +import { timestampInSeconds } from '@sentry/utils'; import type { IdleTransaction } from '../../../tracing/src'; import { getActiveTransaction } from '../../../tracing/src'; import { conditionalTest, getDefaultBrowserClientOptions } from '../../../tracing/test/testutils'; @@ -178,12 +179,12 @@ conditionalTest({ min: 10 })('BrowserTracing', () => { const transaction = getActiveTransaction(hub) as IdleTransaction; const span = transaction.startChild(); - span.end(); - if (span.endTimestamp) { - transaction.end(span.endTimestamp + 12345); - } - expect(transaction.endTimestamp).toBe(span.endTimestamp); + const timestamp = timestampInSeconds(); + span.end(timestamp); + transaction.end(timestamp + 12345); + + expect(spanToJSON(transaction).timestamp).toBe(timestamp); }); // TODO (v8): remove these tests @@ -507,20 +508,20 @@ conditionalTest({ min: 10 })('BrowserTracing', () => { createBrowserTracing(true); const transaction1 = getActiveTransaction(hub) as IdleTransaction; expect(transaction1.op).toBe('pageload'); - expect(transaction1.endTimestamp).not.toBeDefined(); + expect(spanToJSON(transaction1).timestamp).not.toBeDefined(); mockChangeHistory({ to: 'here', from: 'there' }); const transaction2 = getActiveTransaction(hub) as IdleTransaction; expect(transaction2.op).toBe('navigation'); - expect(transaction1.endTimestamp).toBeDefined(); + expect(spanToJSON(transaction1).timestamp).toBeDefined(); }); it('is not created if startTransactionOnLocationChange is false', () => { createBrowserTracing(true, { startTransactionOnLocationChange: false }); const transaction1 = getActiveTransaction(hub) as IdleTransaction; expect(transaction1.op).toBe('pageload'); - expect(transaction1.endTimestamp).not.toBeDefined(); + expect(spanToJSON(transaction1).timestamp).not.toBeDefined(); mockChangeHistory({ to: 'here', from: 'there' }); const transaction2 = getActiveTransaction(hub) as IdleTransaction; diff --git a/packages/tracing-internal/test/browser/metrics/utils.test.ts b/packages/tracing-internal/test/browser/metrics/utils.test.ts index ae614abc41a6..fbafd4f8d880 100644 --- a/packages/tracing-internal/test/browser/metrics/utils.test.ts +++ b/packages/tracing-internal/test/browser/metrics/utils.test.ts @@ -25,8 +25,8 @@ describe('_startChild()', () => { startTimestamp: 100, }); - expect(transaction.startTimestamp).toEqual(span.startTimestamp); - expect(transaction.startTimestamp).toEqual(100); + expect(spanToJSON(transaction).start_timestamp).toEqual(spanToJSON(span).start_timestamp); + expect(spanToJSON(transaction).start_timestamp).toEqual(100); }); it('does not adjust start timestamp if child span starts after transaction', () => { @@ -38,7 +38,7 @@ describe('_startChild()', () => { startTimestamp: 150, }); - expect(transaction.startTimestamp).not.toEqual(span.startTimestamp); - expect(transaction.startTimestamp).toEqual(123); + expect(spanToJSON(transaction).start_timestamp).not.toEqual(spanToJSON(span).start_timestamp); + expect(spanToJSON(transaction).start_timestamp).toEqual(123); }); }); diff --git a/packages/tracing-internal/test/browser/request.test.ts b/packages/tracing-internal/test/browser/request.test.ts index faa2d5b96600..8bf28befb886 100644 --- a/packages/tracing-internal/test/browser/request.test.ts +++ b/packages/tracing-internal/test/browser/request.test.ts @@ -172,7 +172,7 @@ describe('callbacks', () => { // triggered by response coming back instrumentFetchRequest(postRequestFetchHandlerData, alwaysCreateSpan, alwaysAttachHeaders, spans); - expect(newSpan.endTimestamp).toBeDefined(); + expect(spanToJSON(newSpan).timestamp).toBeDefined(); }); it('sets response status on finish', () => { @@ -364,7 +364,7 @@ describe('callbacks', () => { // triggered by response coming back xhrCallback(postRequestXHRHandlerData, alwaysCreateSpan, alwaysAttachHeaders, spans); - expect(newSpan.endTimestamp).toBeDefined(); + expect(spanToJSON(newSpan).timestamp).toBeDefined(); }); it('sets response status on finish', () => { diff --git a/packages/tracing/test/idletransaction.test.ts b/packages/tracing/test/idletransaction.test.ts index e4a84ccec52c..c6380691b1b3 100644 --- a/packages/tracing/test/idletransaction.test.ts +++ b/packages/tracing/test/idletransaction.test.ts @@ -4,6 +4,7 @@ import { TRACING_DEFAULTS, Transaction, getCurrentScope, + spanToJSON, startInactiveSpan, startSpan, startSpanManual, @@ -197,16 +198,22 @@ describe('IdleTransaction', () => { getCurrentScope().setSpan(transaction); // regular child - should be kept - const regularSpan = startInactiveSpan({ name: 'span1', startTimestamp: transaction.startTimestamp + 2 })!; + const regularSpan = startInactiveSpan({ + name: 'span1', + startTimestamp: spanToJSON(transaction).start_timestamp! + 2, + })!; // discardedSpan - startTimestamp is too large startInactiveSpan({ name: 'span2', startTimestamp: 645345234 }); // Should be cancelled - will not finish - const cancelledSpan = startInactiveSpan({ name: 'span3', startTimestamp: transaction.startTimestamp + 4 })!; + const cancelledSpan = startInactiveSpan({ + name: 'span3', + startTimestamp: spanToJSON(transaction).start_timestamp! + 4, + })!; - regularSpan.end(regularSpan.startTimestamp + 4); - transaction.end(transaction.startTimestamp + 10); + regularSpan.end(spanToJSON(regularSpan).start_timestamp! + 4); + transaction.end(spanToJSON(transaction).start_timestamp! + 10); expect(transaction.spanRecorder).toBeDefined(); if (transaction.spanRecorder) { @@ -216,12 +223,12 @@ describe('IdleTransaction', () => { // Regular Span - should not modified expect(spans[1].spanContext().spanId).toBe(regularSpan.spanContext().spanId); - expect(spans[1].endTimestamp).not.toBe(transaction.endTimestamp); + expect(spans[1]['_endTime']).not.toBe(spanToJSON(transaction).timestamp); // Cancelled Span - has endtimestamp of transaction expect(spans[2].spanContext().spanId).toBe(cancelledSpan.spanContext().spanId); expect(spans[2].status).toBe('cancelled'); - expect(spans[2].endTimestamp).toBe(transaction.endTimestamp); + expect(spans[2]['_endTime']).toBe(spanToJSON(transaction).timestamp); } }); @@ -231,10 +238,10 @@ describe('IdleTransaction', () => { // eslint-disable-next-line deprecation/deprecation getCurrentScope().setSpan(transaction); - const span = startInactiveSpan({ name: 'span', startTimestamp: transaction.startTimestamp + 2 })!; - span.end(span.startTimestamp + 10 + 30 + 1); + const span = startInactiveSpan({ name: 'span', startTimestamp: spanToJSON(transaction).start_timestamp! + 2 })!; + span.end(spanToJSON(span).start_timestamp! + 10 + 30 + 1); - transaction.end(transaction.startTimestamp + 50); + transaction.end(spanToJSON(transaction).start_timestamp! + 50); expect(transaction.spanRecorder).toBeDefined(); expect(transaction.spanRecorder!.spans).toHaveLength(1); @@ -248,7 +255,7 @@ describe('IdleTransaction', () => { const recordDroppedEventSpy = jest.spyOn(client, 'recordDroppedEvent'); transaction.initSpanRecorder(10); - transaction.end(transaction.startTimestamp + 10); + transaction.end(spanToJSON(transaction).start_timestamp! + 10); expect(recordDroppedEventSpy).toHaveBeenCalledWith('sample_rate', 'transaction'); }); @@ -259,7 +266,7 @@ describe('IdleTransaction', () => { transaction.initSpanRecorder(10); jest.advanceTimersByTime(TRACING_DEFAULTS.idleTimeout); - expect(transaction.endTimestamp).toBeDefined(); + expect(spanToJSON(transaction).timestamp).toBeDefined(); }); it('does not finish if a activity is started', () => { @@ -271,7 +278,7 @@ describe('IdleTransaction', () => { startInactiveSpan({ name: 'span' }); jest.advanceTimersByTime(TRACING_DEFAULTS.idleTimeout); - expect(transaction.endTimestamp).toBeUndefined(); + expect(spanToJSON(transaction).timestamp).toBeUndefined(); }); it('does not finish when idleTimeout is not exceed after last activity finished', () => { @@ -289,7 +296,7 @@ describe('IdleTransaction', () => { jest.advanceTimersByTime(8); - expect(transaction.endTimestamp).toBeUndefined(); + expect(spanToJSON(transaction).timestamp).toBeUndefined(); }); it('finish when idleTimeout is exceeded after last activity finished', () => { @@ -307,7 +314,7 @@ describe('IdleTransaction', () => { jest.advanceTimersByTime(10); - expect(transaction.endTimestamp).toBeDefined(); + expect(spanToJSON(transaction).timestamp).toBeDefined(); }); }); @@ -325,7 +332,7 @@ describe('IdleTransaction', () => { firstSpan.end(); secondSpan.end(); - expect(transaction.endTimestamp).toBeDefined(); + expect(spanToJSON(transaction).timestamp).toBeDefined(); }); it('permanent idle timeout cancel finished the transaction with the last child', () => { @@ -341,13 +348,13 @@ describe('IdleTransaction', () => { const thirdSpan = startInactiveSpan({ name: 'span3' })!; firstSpan.end(); - expect(transaction.endTimestamp).toBeUndefined(); + expect(spanToJSON(transaction).timestamp).toBeUndefined(); secondSpan.end(); - expect(transaction.endTimestamp).toBeUndefined(); + expect(spanToJSON(transaction).timestamp).toBeUndefined(); thirdSpan.end(); - expect(transaction.endTimestamp).toBeDefined(); + expect(spanToJSON(transaction).timestamp).toBeDefined(); }); it('permanent idle timeout cancel finishes transaction if there are no activities', () => { @@ -363,7 +370,7 @@ describe('IdleTransaction', () => { transaction.cancelIdleTimeout(undefined, { restartOnChildSpanChange: false }); - expect(transaction.endTimestamp).toBeDefined(); + expect(spanToJSON(transaction).timestamp).toBeDefined(); }); it('default idle cancel timeout is restarted by child span change', () => { @@ -382,10 +389,10 @@ describe('IdleTransaction', () => { startSpan({ name: 'span' }, () => {}); jest.advanceTimersByTime(8); - expect(transaction.endTimestamp).toBeUndefined(); + expect(spanToJSON(transaction).timestamp).toBeUndefined(); jest.advanceTimersByTime(2); - expect(transaction.endTimestamp).toBeDefined(); + expect(spanToJSON(transaction).timestamp).toBeDefined(); }); }); diff --git a/packages/tracing/test/span.test.ts b/packages/tracing/test/span.test.ts index e2ebf335ecb5..1e6ade7d4dec 100644 --- a/packages/tracing/test/span.test.ts +++ b/packages/tracing/test/span.test.ts @@ -1,6 +1,6 @@ /* eslint-disable deprecation/deprecation */ import { BrowserClient } from '@sentry/browser'; -import { Hub, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, Scope, makeMain } from '@sentry/core'; +import { Hub, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, Scope, makeMain, spanToJSON } from '@sentry/core'; import type { BaseTransportOptions, ClientOptions, TransactionSource } from '@sentry/types'; import { Span, TRACEPARENT_REGEXP, Transaction } from '../src'; @@ -172,9 +172,9 @@ describe('Span', () => { describe('finish', () => { test('simple', () => { const span = new Span({}); - expect(span.endTimestamp).toBeUndefined(); + expect(spanToJSON(span).timestamp).toBeUndefined(); span.end(); - expect(span.endTimestamp).toBeGreaterThan(1); + expect(spanToJSON(span).timestamp).toBeGreaterThan(1); }); describe('hub.startTransaction', () => { @@ -299,25 +299,25 @@ describe('Span', () => { describe('end', () => { test('simple', () => { const span = new Span({}); - expect(span.endTimestamp).toBeUndefined(); + expect(spanToJSON(span).timestamp).toBeUndefined(); span.end(); - expect(span.endTimestamp).toBeGreaterThan(1); + expect(spanToJSON(span).timestamp).toBeGreaterThan(1); }); test('with endTime in seconds', () => { const span = new Span({}); - expect(span.endTimestamp).toBeUndefined(); + expect(spanToJSON(span).timestamp).toBeUndefined(); const endTime = Date.now() / 1000; span.end(endTime); - expect(span.endTimestamp).toBe(endTime); + expect(spanToJSON(span).timestamp).toBe(endTime); }); test('with endTime in milliseconds', () => { const span = new Span({}); - expect(span.endTimestamp).toBeUndefined(); + expect(spanToJSON(span).timestamp).toBeUndefined(); const endTime = Date.now(); span.end(endTime); - expect(span.endTimestamp).toBe(endTime / 1000); + expect(spanToJSON(span).timestamp).toBe(endTime / 1000); }); describe('hub.startTransaction', () => { @@ -544,7 +544,7 @@ describe('Span', () => { expect(span.spanContext().traceId).toBe('a'); expect(span.spanContext().spanId).toBe('b'); expect(span.description).toBe('new'); - expect(span.endTimestamp).toBe(1); + expect(spanToJSON(span).timestamp).toBe(1); expect(span.op).toBe('new-op'); expect(span.sampled).toBe(true); expect(span.tags).toStrictEqual({ tag1: 'bye' }); diff --git a/packages/types/src/span.ts b/packages/types/src/span.ts index e85fda90642a..5bbe6408ea30 100644 --- a/packages/types/src/span.ts +++ b/packages/types/src/span.ts @@ -192,10 +192,17 @@ export interface Span extends SpanContext { sampled?: boolean; /** - * @inheritDoc + * Timestamp in seconds (epoch time) indicating when the span started. + * @deprecated Use `spanToJSON()` instead. */ startTimestamp: number; + /** + * Timestamp in seconds (epoch time) indicating when the span ended. + * @deprecated Use `spanToJSON()` instead. + */ + endTimestamp?: number; + /** * Tags for the span. * @deprecated Use `getSpanAttributes(span)` instead. diff --git a/packages/vue/src/tracing.ts b/packages/vue/src/tracing.ts index cff758c4b571..a5ade00e8ec0 100644 --- a/packages/vue/src/tracing.ts +++ b/packages/vue/src/tracing.ts @@ -115,7 +115,7 @@ export const createTracingMixins = (options: TracingOptions): Mixins => { // will ever be the case that cleanup hooks re not called, but we had users report that spans didn't get // finished so we finish the span before starting a new one, just to be sure. const oldSpan = this.$_sentrySpans[operation]; - if (oldSpan && !oldSpan.endTimestamp) { + if (oldSpan) { oldSpan.end(); }