From 7a7c799da4e95055b28edfbee0356663c796bda9 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 3 Apr 2024 16:51:11 +0200 Subject: [PATCH 1/2] ref(v8): Remove Transaction concept --- packages/browser/src/exports.ts | 1 - packages/bun/src/index.ts | 1 - packages/bun/src/integrations/bunserver.ts | 13 +- packages/core/src/integrations/requestdata.ts | 4 +- packages/core/src/tracing/index.ts | 1 - packages/core/src/tracing/sentrySpan.ts | 116 +++++++++++- packages/core/src/tracing/trace.ts | 87 +++++---- packages/core/src/tracing/transaction.ts | 170 ------------------ .../tracing/dynamicSamplingContext.test.ts | 52 +++--- packages/core/test/lib/tracing/trace.test.ts | 2 +- .../core/test/lib/tracing/transaction.test.ts | 24 --- packages/deno/src/index.ts | 1 - packages/deno/test/normalize.ts | 2 +- packages/nextjs/src/common/types.ts | 5 +- packages/nextjs/src/edge/index.ts | 1 - packages/nextjs/src/index.types.ts | 2 - packages/node/src/index.ts | 1 - packages/opentelemetry/src/sampler.ts | 4 +- packages/remix/src/client/performance.tsx | 36 ++-- packages/types/src/index.ts | 2 - packages/types/src/transaction.ts | 44 ++--- packages/vercel-edge/src/index.ts | 1 - packages/vue/src/router.ts | 4 +- 23 files changed, 224 insertions(+), 350 deletions(-) delete mode 100644 packages/core/src/tracing/transaction.ts delete mode 100644 packages/core/test/lib/tracing/transaction.test.ts diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts index 8d581eaeda21..a83b1e549eba 100644 --- a/packages/browser/src/exports.ts +++ b/packages/browser/src/exports.ts @@ -10,7 +10,6 @@ export type { StackFrame, Stacktrace, Thread, - Transaction, User, Session, } from '@sentry/types'; diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index e94994cd9170..bfadeb476c94 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -13,7 +13,6 @@ export type { StackFrame, Stacktrace, Thread, - Transaction, User, } from '@sentry/types'; export type { AddRequestDataToEventOptions } from '@sentry/utils'; diff --git a/packages/bun/src/integrations/bunserver.ts b/packages/bun/src/integrations/bunserver.ts index a530fc0517c2..93a3f94dd4a0 100644 --- a/packages/bun/src/integrations/bunserver.ts +++ b/packages/bun/src/integrations/bunserver.ts @@ -1,11 +1,9 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, - Transaction, captureException, continueTrace, defineIntegration, - getCurrentScope, setHttpStatus, startSpan, withIsolationScope, @@ -100,13 +98,10 @@ function instrumentBunServeOptions(serveOptions: Parameters[0] >); if (response && response.status) { setHttpStatus(span, response.status); - if (span instanceof Transaction) { - const scope = getCurrentScope(); - scope.setContext('response', { - headers: response.headers.toJSON(), - status_code: response.status, - }); - } + isolationScope.setContext('response', { + headers: response.headers.toJSON(), + status_code: response.status, + }); } return response; } catch (e) { diff --git a/packages/core/src/integrations/requestdata.ts b/packages/core/src/integrations/requestdata.ts index e7c1e56dec82..23c3ae311533 100644 --- a/packages/core/src/integrations/requestdata.ts +++ b/packages/core/src/integrations/requestdata.ts @@ -1,4 +1,4 @@ -import type { Client, IntegrationFn, Transaction } from '@sentry/types'; +import type { Client, IntegrationFn, Span } from '@sentry/types'; import type { AddRequestDataToEventOptions, TransactionNamingScheme } from '@sentry/utils'; import { addRequestDataToEvent, extractPathForTransaction } from '@sentry/utils'; import { defineIntegration } from '../integration'; @@ -92,7 +92,7 @@ const _requestDataIntegration = ((options: RequestDataIntegrationOptions = {}) = // In all other cases, use the request's associated transaction (if any) to overwrite the event's `transaction` // value with a high-quality one - const reqWithTransaction = req as { _sentryTransaction?: Transaction }; + const reqWithTransaction = req as { _sentryTransaction?: Span }; const transaction = reqWithTransaction._sentryTransaction; if (transaction) { const name = spanToJSON(transaction).description || ''; diff --git a/packages/core/src/tracing/index.ts b/packages/core/src/tracing/index.ts index a6b510538608..cd9ca5ea6351 100644 --- a/packages/core/src/tracing/index.ts +++ b/packages/core/src/tracing/index.ts @@ -2,7 +2,6 @@ export { addTracingExtensions } from './hubextensions'; export { startIdleSpan, TRACING_DEFAULTS } from './idleSpan'; export { SentrySpan } from './sentrySpan'; export { SentryNonRecordingSpan } from './sentryNonRecordingSpan'; -export { Transaction } from './transaction'; export { setHttpStatus, getSpanStatusFromHttpCode, diff --git a/packages/core/src/tracing/sentrySpan.ts b/packages/core/src/tracing/sentrySpan.ts index 27ba7b7dbdaa..f69f5a8f610b 100644 --- a/packages/core/src/tracing/sentrySpan.ts +++ b/packages/core/src/tracing/sentrySpan.ts @@ -9,15 +9,33 @@ import type { SpanStatus, SpanTimeInput, TimedEvent, + TransactionEvent, + TransactionSource, } from '@sentry/types'; import { dropUndefinedKeys, logger, timestampInSeconds, uuid4 } from '@sentry/utils'; -import { getClient } from '../currentScopes'; +import { getClient, getCurrentScope } from '../currentScopes'; import { DEBUG_BUILD } from '../debug-build'; import { getMetricSummaryJsonForSpan } from '../metrics/metric-summary'; -import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../semanticAttributes'; -import { TRACE_FLAG_NONE, TRACE_FLAG_SAMPLED, getStatusMessage, spanTimeInputToSeconds } from '../utils/spanUtils'; +import { + SEMANTIC_ATTRIBUTE_SENTRY_OP, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, +} from '../semanticAttributes'; +import { + TRACE_FLAG_NONE, + TRACE_FLAG_SAMPLED, + getRootSpan, + getSpanDescendants, + getStatusMessage, + spanTimeInputToSeconds, + spanToJSON, + spanToTraceContext, +} from '../utils/spanUtils'; +import { getDynamicSamplingContextFromSpan } from './dynamicSamplingContext'; import { logSpanEnd } from './logSpans'; +import { timedEventsToMeasurements } from './measurement'; +import { getCapturedScopesOnSpan } from './utils'; /** * Span contains all data about a span @@ -71,6 +89,11 @@ export class SentrySpan implements Span { } this._events = []; + + // If the span is already ended, ensure we finalize the span immediately + if (this._endTime) { + this._onSpanEnded(); + } } /** @inheritdoc */ @@ -194,13 +217,100 @@ export class SentrySpan implements Span { /** Emit `spanEnd` when the span is ended. */ private _onSpanEnded(): void { + console.log('SPAN END!', this === getRootSpan(this)); const client = getClient(); if (client) { client.emit('spanEnd', this); } + + // If this is a root span, send it when it is endedf + if (this === getRootSpan(this)) { + const transactionEvent = this._convertSpanToTransaction(); + if (transactionEvent) { + const scope = getCapturedScopesOnSpan(this).scope || getCurrentScope(); + scope.captureEvent(transactionEvent); + } + } + } + + /** + * Finish the transaction & prepare the event to send to Sentry. + */ + private _convertSpanToTransaction(): TransactionEvent | undefined { + // We can only convert finished spans + if (!isFullFinishedSpan(spanToJSON(this))) { + return undefined; + } + + if (!this._name) { + DEBUG_BUILD && logger.warn('Transaction has no name, falling back to ``.'); + this._name = ''; + } + + const { scope: capturedSpanScope, isolationScope: capturedSpanIsolationScope } = getCapturedScopesOnSpan(this); + const scope = capturedSpanScope || getCurrentScope(); + const client = scope.getClient() || getClient(); + + if (this._sampled !== true) { + // At this point if `sampled !== true` we want to discard the transaction. + DEBUG_BUILD && logger.log('[Tracing] Discarding transaction because its trace was not chosen to be sampled.'); + + if (client) { + client.recordDroppedEvent('sample_rate', 'transaction'); + } + + return undefined; + } + + // The transaction span itself should be filtered out + const finishedSpans = getSpanDescendants(this).filter(span => span !== this); + + const spans = finishedSpans.map(span => spanToJSON(span)).filter(isFullFinishedSpan); + + const source = this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] as TransactionSource | undefined; + + const transaction: TransactionEvent = { + contexts: { + trace: spanToTraceContext(this), + }, + spans, + start_timestamp: this._startTime, + timestamp: this._endTime, + transaction: this._name, + type: 'transaction', + sdkProcessingMetadata: { + capturedSpanScope, + capturedSpanIsolationScope, + ...dropUndefinedKeys({ + dynamicSamplingContext: getDynamicSamplingContextFromSpan(this), + }), + }, + _metrics_summary: getMetricSummaryJsonForSpan(this), + ...(source && { + transaction_info: { + source, + }, + }), + }; + + const measurements = timedEventsToMeasurements(this._events); + const hasMeasurements = Object.keys(measurements).length; + + if (hasMeasurements) { + DEBUG_BUILD && + logger.log('[Measurements] Adding measurements to transaction', JSON.stringify(measurements, undefined, 2)); + transaction.measurements = measurements; + } + + return transaction; } } function isSpanTimeInput(value: undefined | SpanAttributes | SpanTimeInput): value is SpanTimeInput { return (value && typeof value === 'number') || value instanceof Date || Array.isArray(value); } + +// We want to filter out any incomplete SpanJSON objects +function isFullFinishedSpan(input: Partial): input is SpanJSON { + return !!input.start_timestamp && !!input.timestamp && !!input.span_id && !!input.trace_id; +} diff --git a/packages/core/src/tracing/trace.ts b/packages/core/src/tracing/trace.ts index d6676fedec17..d21a567cecc5 100644 --- a/packages/core/src/tracing/trace.ts +++ b/packages/core/src/tracing/trace.ts @@ -1,20 +1,12 @@ -import type { - ClientOptions, - Scope, - SentrySpanArguments, - Span, - SpanTimeInput, - StartSpanOptions, - TransactionArguments, -} from '@sentry/types'; +import type { ClientOptions, Scope, SentrySpanArguments, Span, SpanTimeInput, StartSpanOptions } from '@sentry/types'; import { propagationContextFromHeaders } from '@sentry/utils'; import type { AsyncContextStrategy } from '../asyncContext'; import { getMainCarrier } from '../asyncContext'; import { getClient, getCurrentScope, getIsolationScope, withScope } from '../currentScopes'; -import { getAsyncContextStrategy, getCurrentHub } from '../hub'; -import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '../semanticAttributes'; +import { getAsyncContextStrategy } from '../hub'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '../semanticAttributes'; import { handleCallbackErrors } from '../utils/handleCallbackErrors'; import { hasTracingEnabled } from '../utils/hasTracingEnabled'; import { _getSpanForScope, _setSpanForScope } from '../utils/spanOnScope'; @@ -31,7 +23,6 @@ import { sampleSpan } from './sampling'; import { SentryNonRecordingSpan } from './sentryNonRecordingSpan'; import { SentrySpan } from './sentrySpan'; import { SPAN_STATUS_ERROR } from './spanstatus'; -import { Transaction } from './transaction'; import { setCapturedScopesOnSpan } from './utils'; /** @@ -220,7 +211,7 @@ function createChildSpanOrTransaction({ scope, }: { parentSpan: SentrySpan | undefined; - spanContext: TransactionArguments; + spanContext: SentrySpanArguments; forceTransaction?: boolean; scope: Scope; }): Span { @@ -232,34 +223,43 @@ function createChildSpanOrTransaction({ let span: Span; if (parentSpan && !forceTransaction) { - span = _startChild(parentSpan, spanContext); + span = _startChildSpan(parentSpan, spanContext); addChildSpanToSpan(parentSpan, span); } else if (parentSpan) { // If we forced a transaction but have a parent span, make sure to continue from the parent span, not the scope const dsc = getDynamicSamplingContextFromSpan(parentSpan); const { traceId, spanId: parentSpanId } = parentSpan.spanContext(); - const sampled = spanIsSampled(parentSpan); + const parentSampled = spanIsSampled(parentSpan); - span = _startTransaction({ - traceId, - parentSpanId, - parentSampled: sampled, - ...spanContext, - }); + span = _startRootSpan( + { + traceId, + parentSpanId, + ...spanContext, + }, + parentSampled, + ); freezeDscOnSpan(span, dsc); } else { - const { traceId, dsc, parentSpanId, sampled } = { + const { + traceId, + dsc, + parentSpanId, + sampled: parentSampled, + } = { ...isolationScope.getPropagationContext(), ...scope.getPropagationContext(), }; - span = _startTransaction({ - traceId, - parentSpanId, - parentSampled: sampled, - ...spanContext, - }); + span = _startRootSpan( + { + traceId, + parentSpanId, + ...spanContext, + }, + parentSampled, + ); if (dsc) { freezeDscOnSpan(span, dsc); @@ -274,15 +274,15 @@ function createChildSpanOrTransaction({ } /** - * This converts StartSpanOptions to TransactionArguments. + * This converts StartSpanOptions to SentrySpanArguments. * For the most part (for now) we accept the same options, * but some of them need to be transformed. * * Eventually the StartSpanOptions will be more aligned with OpenTelemetry. */ -function normalizeContext(context: StartSpanOptions): TransactionArguments { +function normalizeContext(context: StartSpanOptions): SentrySpanArguments { if (context.startTime) { - const ctx: TransactionArguments & { startTime?: SpanTimeInput } = { ...context }; + const ctx: SentrySpanArguments & { startTime?: SpanTimeInput } = { ...context }; ctx.startTimestamp = spanTimeInputToSeconds(context.startTime); delete ctx.startTime; return ctx; @@ -296,20 +296,29 @@ function getAcs(): AsyncContextStrategy { return getAsyncContextStrategy(carrier); } -function _startTransaction(transactionContext: TransactionArguments): Transaction { +function _startRootSpan(spanArguments: SentrySpanArguments, parentSampled?: boolean): SentrySpan { const client = getClient(); const options: Partial = (client && client.getOptions()) || {}; - const { name, parentSampled, attributes } = transactionContext; + const { name = '', attributes } = spanArguments; const [sampled, sampleRate] = sampleSpan(options, { name, parentSampled, attributes, - transactionContext, + transactionContext: { + name, + parentSampled, + }, }); - // eslint-disable-next-line deprecation/deprecation - const transaction = new Transaction({ ...transactionContext, sampled }, getCurrentHub()); + const transaction = new SentrySpan({ + ...spanArguments, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', + ...spanArguments.attributes, + }, + sampled, + }); if (sampleRate !== undefined) { transaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, sampleRate); } @@ -325,12 +334,12 @@ function _startTransaction(transactionContext: TransactionArguments): Transactio * Creates a new `Span` while setting the current `Span.id` as `parentSpanId`. * This inherits the sampling decision from the parent span. */ -function _startChild(parentSpan: Span, spanContext: SentrySpanArguments): SentrySpan { +function _startChildSpan(parentSpan: Span, spanArguments: SentrySpanArguments): SentrySpan { const { spanId, traceId } = parentSpan.spanContext(); const sampled = spanIsSampled(parentSpan); const childSpan = new SentrySpan({ - ...spanContext, + ...spanArguments, parentSpanId: spanId, traceId, sampled, @@ -342,7 +351,7 @@ function _startChild(parentSpan: Span, spanContext: SentrySpanArguments): Sentry if (client) { client.emit('spanStart', childSpan); // If it has an endTimestamp, it's already ended - if (spanContext.endTimestamp) { + if (spanArguments.endTimestamp) { client.emit('spanEnd', childSpan); } } diff --git a/packages/core/src/tracing/transaction.ts b/packages/core/src/tracing/transaction.ts deleted file mode 100644 index e9791c9b1ba3..000000000000 --- a/packages/core/src/tracing/transaction.ts +++ /dev/null @@ -1,170 +0,0 @@ -import type { - Contexts, - Hub, - SpanJSON, - SpanTimeInput, - Transaction as TransactionInterface, - TransactionArguments, - TransactionEvent, - TransactionSource, -} from '@sentry/types'; -import { dropUndefinedKeys, logger } from '@sentry/utils'; - -import { DEBUG_BUILD } from '../debug-build'; -import { getCurrentHub } from '../hub'; -import { getMetricSummaryJsonForSpan } from '../metrics/metric-summary'; -import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '../semanticAttributes'; -import { getSpanDescendants, spanTimeInputToSeconds, spanToJSON, spanToTraceContext } from '../utils/spanUtils'; -import { getDynamicSamplingContextFromSpan } from './dynamicSamplingContext'; -import { timedEventsToMeasurements } from './measurement'; -import { SentrySpan } from './sentrySpan'; -import { getCapturedScopesOnSpan } from './utils'; - -/** JSDoc */ -export class Transaction extends SentrySpan implements TransactionInterface { - /** - * The reference to the current hub. - */ - public _hub: Hub; - - protected _name: string; - - private _contexts: Contexts; - - private _trimEnd?: boolean | undefined; - - /** - * This constructor should never be called manually. - * @internal - * @hideconstructor - * @hidden - * - * @deprecated Transactions will be removed in v8. Use spans instead. - */ - public constructor(transactionContext: TransactionArguments, hub?: Hub) { - super(transactionContext); - this._contexts = {}; - - // eslint-disable-next-line deprecation/deprecation - this._hub = hub || getCurrentHub(); - - this._name = transactionContext.name || ''; - - this._trimEnd = transactionContext.trimEnd; - - this._attributes = { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', - ...this._attributes, - }; - } - - /** @inheritdoc */ - public updateName(name: string): this { - this._name = name; - this.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'custom'); - return this; - } - - /** - * @inheritDoc - */ - public end(endTimestamp?: SpanTimeInput): string | undefined { - const timestampInS = spanTimeInputToSeconds(endTimestamp); - const transaction = this._finishTransaction(timestampInS); - if (!transaction) { - return undefined; - } - // eslint-disable-next-line deprecation/deprecation - return this._hub.captureEvent(transaction); - } - - /** - * Finish the transaction & prepare the event to send to Sentry. - */ - protected _finishTransaction(endTimestamp?: number): TransactionEvent | undefined { - // This transaction is already finished, so we should not flush it again. - if (this._endTime !== undefined) { - return undefined; - } - - if (!this._name) { - DEBUG_BUILD && logger.warn('Transaction has no name, falling back to ``.'); - this._name = ''; - } - - // just sets the end timestamp - super.end(endTimestamp); - - // eslint-disable-next-line deprecation/deprecation - const client = this._hub.getClient(); - - if (this._sampled !== true) { - // At this point if `sampled !== true` we want to discard the transaction. - DEBUG_BUILD && logger.log('[Tracing] Discarding transaction because its trace was not chosen to be sampled.'); - - if (client) { - client.recordDroppedEvent('sample_rate', 'transaction'); - } - - return undefined; - } - - // The transaction span itself should be filtered out - const finishedSpans = getSpanDescendants(this).filter(span => span !== this); - - if (this._trimEnd && finishedSpans.length > 0) { - const endTimes = finishedSpans.map(span => spanToJSON(span).timestamp).filter(Boolean) as number[]; - this._endTime = endTimes.reduce((prev, current) => { - return prev > current ? prev : current; - }); - } - - // We want to filter out any incomplete SpanJSON objects - function isFullFinishedSpan(input: Partial): input is SpanJSON { - return !!input.start_timestamp && !!input.timestamp && !!input.span_id && !!input.trace_id; - } - - const spans = finishedSpans.map(span => spanToJSON(span)).filter(isFullFinishedSpan); - - const { scope: capturedSpanScope, isolationScope: capturedSpanIsolationScope } = getCapturedScopesOnSpan(this); - - const source = this._attributes['sentry.source'] as TransactionSource | undefined; - - const transaction: TransactionEvent = { - contexts: { - ...this._contexts, - // We don't want to override trace context - trace: spanToTraceContext(this), - }, - spans, - start_timestamp: this._startTime, - timestamp: this._endTime, - transaction: this._name, - type: 'transaction', - sdkProcessingMetadata: { - capturedSpanScope, - capturedSpanIsolationScope, - ...dropUndefinedKeys({ - dynamicSamplingContext: getDynamicSamplingContextFromSpan(this), - }), - }, - _metrics_summary: getMetricSummaryJsonForSpan(this), - ...(source && { - transaction_info: { - source, - }, - }), - }; - - const measurements = timedEventsToMeasurements(this._events); - const hasMeasurements = Object.keys(measurements).length; - - if (hasMeasurements) { - DEBUG_BUILD && - logger.log('[Measurements] Adding measurements to transaction', JSON.stringify(measurements, undefined, 2)); - transaction.measurements = measurements; - } - - return transaction; - } -} diff --git a/packages/core/test/lib/tracing/dynamicSamplingContext.test.ts b/packages/core/test/lib/tracing/dynamicSamplingContext.test.ts index f68e5e0e75a1..329d13c769af 100644 --- a/packages/core/test/lib/tracing/dynamicSamplingContext.test.ts +++ b/packages/core/test/lib/tracing/dynamicSamplingContext.test.ts @@ -5,7 +5,7 @@ import { setCurrentClient, } from '../../../src'; import { - Transaction, + SentrySpan, addTracingExtensions, getDynamicSamplingContextFromSpan, startInactiveSpan, @@ -27,27 +27,26 @@ describe('getDynamicSamplingContextFromSpan', () => { }); test('uses frozen DSC from span', () => { - // eslint-disable-next-line deprecation/deprecation -- using old API on purpose - const transaction = new Transaction({ + const rootSpan = new SentrySpan({ name: 'tx', sampled: true, }); - freezeDscOnSpan(transaction, { environment: 'myEnv' }); + freezeDscOnSpan(rootSpan, { environment: 'myEnv' }); - const dynamicSamplingContext = getDynamicSamplingContextFromSpan(transaction); + const dynamicSamplingContext = getDynamicSamplingContextFromSpan(rootSpan); expect(dynamicSamplingContext).toStrictEqual({ environment: 'myEnv' }); }); - test('returns a new DSC, if no DSC was provided during transaction creation (via attributes)', () => { - const transaction = startInactiveSpan({ name: 'tx' }); + test('returns a new DSC, if no DSC was provided during rootSpan creation (via attributes)', () => { + const rootSpan = startInactiveSpan({ name: 'tx' }); // Setting the attribute should overwrite the computed values - transaction?.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, 0.56); - transaction?.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); + rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, 0.56); + rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); - const dynamicSamplingContext = getDynamicSamplingContextFromSpan(transaction!); + const dynamicSamplingContext = getDynamicSamplingContextFromSpan(rootSpan); expect(dynamicSamplingContext).toStrictEqual({ release: '1.0.1', @@ -59,12 +58,12 @@ describe('getDynamicSamplingContextFromSpan', () => { }); }); - test('returns a new DSC, if no DSC was provided during transaction creation (via deprecated metadata)', () => { - const transaction = startInactiveSpan({ + test('returns a new DSC, if no DSC was provided during rootSpan creation (via deprecated metadata)', () => { + const rootSpan = startInactiveSpan({ name: 'tx', }); - const dynamicSamplingContext = getDynamicSamplingContextFromSpan(transaction!); + const dynamicSamplingContext = getDynamicSamplingContextFromSpan(rootSpan); expect(dynamicSamplingContext).toStrictEqual({ release: '1.0.1', @@ -76,9 +75,8 @@ 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 -- using old API on purpose - const transaction = new Transaction({ + test('returns a new DSC, if no DSC was provided during rootSpan creation (via new Txn and deprecated metadata)', () => { + const rootSpan = new SentrySpan({ name: 'tx', attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 0.56, @@ -87,7 +85,7 @@ describe('getDynamicSamplingContextFromSpan', () => { sampled: true, }); - const dynamicSamplingContext = getDynamicSamplingContextFromSpan(transaction!); + const dynamicSamplingContext = getDynamicSamplingContextFromSpan(rootSpan); expect(dynamicSamplingContext).toStrictEqual({ release: '1.0.1', @@ -99,10 +97,9 @@ describe('getDynamicSamplingContextFromSpan', () => { }); }); - describe('Including transaction name in DSC', () => { - test('is not included if transaction source is url', () => { - // eslint-disable-next-line deprecation/deprecation -- using old API on purpose - const transaction = new Transaction({ + describe('Including rootSpan name in DSC', () => { + test('is not included if rootSpan source is url', () => { + const rootSpan = new SentrySpan({ name: 'tx', attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', @@ -110,25 +107,24 @@ describe('getDynamicSamplingContextFromSpan', () => { }, }); - const dsc = getDynamicSamplingContextFromSpan(transaction); + const dsc = getDynamicSamplingContextFromSpan(rootSpan); expect(dsc.transaction).toBeUndefined(); }); test.each([ - ['is included if transaction source is parameterized route/url', 'route'], - ['is included if transaction source is a custom name', 'custom'], + ['is included if rootSpan source is parameterized route/url', 'route'], + ['is included if rootSpan source is a custom name', 'custom'], ] as const)('%s', (_: string, source: TransactionSource) => { - const transaction = startInactiveSpan({ + const rootSpan = startInactiveSpan({ name: 'tx', attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, }, }); - // Only setting the attribute manually because we're directly calling new Transaction() - transaction?.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source); + rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source); - const dsc = getDynamicSamplingContextFromSpan(transaction!); + const dsc = getDynamicSamplingContextFromSpan(rootSpan); expect(dsc.transaction).toEqual('tx'); }); diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts index 58fc5623f587..02144174ef1f 100644 --- a/packages/core/test/lib/tracing/trace.test.ts +++ b/packages/core/test/lib/tracing/trace.test.ts @@ -994,7 +994,7 @@ describe('startInactiveSpan', () => { }); }); - it('includes the scope at the time the span was started when finished', async () => { + it('includes the scope at the time the span was started when finished xxx', async () => { const beforeSendTransaction = jest.fn(event => event); const client = new TestClient( diff --git a/packages/core/test/lib/tracing/transaction.test.ts b/packages/core/test/lib/tracing/transaction.test.ts deleted file mode 100644 index 12e631760b3c..000000000000 --- a/packages/core/test/lib/tracing/transaction.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, Transaction, spanToJSON } from '../../../src'; - -describe('transaction', () => { - describe('name', () => { - /* eslint-disable deprecation/deprecation */ - it('works with name', () => { - const transaction = new Transaction({ name: 'span name' }); - expect(spanToJSON(transaction).description).toEqual('span name'); - }); - - it('allows to update the name via updateName', () => { - const transaction = new Transaction({ name: 'span name' }); - transaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); - expect(spanToJSON(transaction).description).toEqual('span name'); - expect(spanToJSON(transaction).data?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('route'); - - transaction.updateName('new name'); - - expect(spanToJSON(transaction).description).toEqual('new name'); - expect(spanToJSON(transaction).data?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('custom'); - }); - /* eslint-enable deprecation/deprecation */ - }); -}); diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index 7f64e0e72132..a78a2392a429 100644 --- a/packages/deno/src/index.ts +++ b/packages/deno/src/index.ts @@ -13,7 +13,6 @@ export type { StackFrame, Stacktrace, Thread, - Transaction, User, } from '@sentry/types'; export type { AddRequestDataToEventOptions } from '@sentry/utils'; diff --git a/packages/deno/test/normalize.ts b/packages/deno/test/normalize.ts index 9f4481e5fe4a..93f4250c00a8 100644 --- a/packages/deno/test/normalize.ts +++ b/packages/deno/test/normalize.ts @@ -2,7 +2,7 @@ import type { sentryTypes } from '../build-test/index.js'; import { sentryUtils } from '../build-test/index.js'; -type EventOrSession = sentryTypes.Event | sentryTypes.Transaction | sentryTypes.Session; +type EventOrSession = sentryTypes.Event | sentryTypes.Session; export function getNormalizedEvent(envelope: sentryTypes.Envelope): sentryTypes.Event | undefined { let event: sentryTypes.Event | undefined; diff --git a/packages/nextjs/src/common/types.ts b/packages/nextjs/src/common/types.ts index 1286e8f9ae15..9e74983b078e 100644 --- a/packages/nextjs/src/common/types.ts +++ b/packages/nextjs/src/common/types.ts @@ -1,4 +1,5 @@ -import type { Transaction, WebFetchHeaders, WrappedFunction } from '@sentry/types'; +import type { SentrySpan } from '@sentry/core'; +import type { WebFetchHeaders, WrappedFunction } from '@sentry/types'; import type { NextApiRequest, NextApiResponse } from 'next'; import type { RequestAsyncStorage } from '../config/templates/requestAsyncStorageShim'; @@ -65,7 +66,7 @@ export type AugmentedNextApiRequest = NextApiRequest & { }; export type AugmentedNextApiResponse = NextApiResponse & { - __sentryTransaction?: Transaction; + __sentryTransaction?: SentrySpan; }; export type ResponseEndMethod = AugmentedNextApiResponse['end']; diff --git a/packages/nextjs/src/edge/index.ts b/packages/nextjs/src/edge/index.ts index d63aa9ec0256..7c4874d051ad 100644 --- a/packages/nextjs/src/edge/index.ts +++ b/packages/nextjs/src/edge/index.ts @@ -48,7 +48,6 @@ export function withSentryConfig(exportedUserNextConfig: T): T { } export * from '@sentry/vercel-edge'; -export { Transaction } from '@sentry/core'; export * from '../common'; diff --git a/packages/nextjs/src/index.types.ts b/packages/nextjs/src/index.types.ts index 70c4de63764d..629560b96312 100644 --- a/packages/nextjs/src/index.types.ts +++ b/packages/nextjs/src/index.types.ts @@ -36,8 +36,6 @@ export declare const createReduxEnhancer: typeof clientSdk.createReduxEnhancer; export declare const showReportDialog: typeof clientSdk.showReportDialog; export declare const withErrorBoundary: typeof clientSdk.withErrorBoundary; -export declare const Transaction: typeof edgeSdk.Transaction; - export declare const metrics: typeof clientSdk.metrics & typeof serverSdk.metrics; export { withSentryConfig } from './config'; diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 46c8b89c7412..d505a9e251d5 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -122,7 +122,6 @@ export type { StackFrame, Stacktrace, Thread, - Transaction, User, Span, } from '@sentry/types'; diff --git a/packages/opentelemetry/src/sampler.ts b/packages/opentelemetry/src/sampler.ts index 22ad9b181773..228c22bbb29d 100644 --- a/packages/opentelemetry/src/sampler.ts +++ b/packages/opentelemetry/src/sampler.ts @@ -4,7 +4,7 @@ import { TraceState } from '@opentelemetry/core'; import type { Sampler, SamplingResult } from '@opentelemetry/sdk-trace-base'; import { SamplingDecision } from '@opentelemetry/sdk-trace-base'; import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, hasTracingEnabled, sampleSpan } from '@sentry/core'; -import type { Client } from '@sentry/types'; +import type { Client, SpanAttributes } from '@sentry/types'; import { logger } from '@sentry/utils'; import { SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING } from './constants'; @@ -30,7 +30,7 @@ export class SentrySampler implements Sampler { traceId: string, spanName: string, _spanKind: unknown, - spanAttributes: unknown, + spanAttributes: SpanAttributes, _links: unknown, ): SamplingResult { const options = this._client.getOptions(); diff --git a/packages/remix/src/client/performance.tsx b/packages/remix/src/client/performance.tsx index bc3bc9944ae9..d92ad323beb1 100644 --- a/packages/remix/src/client/performance.tsx +++ b/packages/remix/src/client/performance.tsx @@ -14,7 +14,7 @@ import { startBrowserTracingPageLoadSpan, withErrorBoundary, } from '@sentry/react'; -import type { Span, StartSpanOptions, TransactionArguments } from '@sentry/types'; +import type { StartSpanOptions } from '@sentry/types'; import { isNodeEnv, logger } from '@sentry/utils'; import * as React from 'react'; @@ -53,7 +53,6 @@ let _useEffect: UseEffect | undefined; let _useLocation: UseLocation | undefined; let _useMatches: UseMatches | undefined; -let _customStartTransaction: ((context: TransactionArguments) => Span | undefined) | undefined; let _instrumentNavigation: boolean | undefined; function getInitPathName(): string | undefined { @@ -84,18 +83,13 @@ export function startPageloadSpan(): void { }, }; - // If _customStartTransaction is not defined, we know that we are using the browserTracingIntegration - if (!_customStartTransaction) { - const client = getClient(); + const client = getClient(); - if (!client) { - return; - } - - startBrowserTracingPageLoadSpan(client, spanContext); - } else { - _customStartTransaction(spanContext); + if (!client) { + return; } + + startBrowserTracingPageLoadSpan(client, spanContext); } function startNavigationSpan(matches: RouteMatch[]): void { @@ -108,18 +102,13 @@ function startNavigationSpan(matches: RouteMatch[]): void { }, }; - // If _customStartTransaction is not defined, we know that we are using the browserTracingIntegration - if (!_customStartTransaction) { - const client = getClient(); + const client = getClient(); - if (!client) { - return; - } - - startBrowserTracingNavigationSpan(client, spanContext); - } else { - _customStartTransaction(spanContext); + if (!client) { + return; } + + startBrowserTracingNavigationSpan(client, spanContext); } /** @@ -219,17 +208,14 @@ export function setGlobals({ useLocation, useMatches, instrumentNavigation, - customStartTransaction, }: { useEffect?: UseEffect; useLocation?: UseLocation; useMatches?: UseMatches; instrumentNavigation?: boolean; - customStartTransaction?: (context: TransactionArguments) => Span | undefined; }): void { _useEffect = useEffect; _useLocation = useLocation; _useMatches = useMatches; _instrumentNavigation = instrumentNavigation; - _customStartTransaction = customStartTransaction; } diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 50eb14684297..e1ceb7e7e4e2 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -110,8 +110,6 @@ export type { CustomSamplingContext, SamplingContext, TraceparentData, - Transaction, - TransactionArguments, TransactionSource, } from './transaction'; export type { diff --git a/packages/types/src/transaction.ts b/packages/types/src/transaction.ts index 3c81866f19c5..8f5109b44ba0 100644 --- a/packages/types/src/transaction.ts +++ b/packages/types/src/transaction.ts @@ -1,28 +1,5 @@ import type { ExtractedNodeRequestData, WorkerLocation } from './misc'; -import type { SentrySpanArguments, Span } from './span'; - -/** - * Interface holding Transaction-specific properties - */ -export interface TransactionArguments extends SentrySpanArguments { - /** - * Human-readable identifier for the transaction - */ - name: string; - - /** - * If true, sets the end timestamp of the transaction to the highest timestamp of child spans, trimming - * the duration of the transaction. This is useful to discard extra time in the transaction that is not - * accounted for in child spans, like what happens in the idle transaction Tracing integration, where we finish the - * transaction after a given "idle time" and we don't want this "idle time" to be part of the transaction. - */ - trimEnd?: boolean | undefined; - - /** - * If this transaction has a parent, the parent's sampling decision - */ - parentSampled?: boolean | undefined; -} +import type { SpanAttributes } from './span'; /** * Data pulled from a `sentry-trace` header @@ -44,11 +21,6 @@ export interface TraceparentData { parentSampled?: boolean | undefined; } -/** - * Transaction "Class", inherits Span only has `setName` - */ -export interface Transaction extends Omit, Span {} - /** * Context data passed by the user when starting a transaction, to be used by the tracesSampler method. */ @@ -63,9 +35,13 @@ export interface CustomSamplingContext { */ export interface SamplingContext extends CustomSamplingContext { /** - * Context data with which transaction being sampled was created + * Context data with which transaction being sampled was created. + * @deprecated This is duplicate data and will be removed eventually. */ - transactionContext: TransactionArguments; + transactionContext: { + name: string; + parentSampled?: boolean | undefined; + }; /** * Sampling decision from the parent transaction, if any. @@ -82,6 +58,12 @@ export interface SamplingContext extends CustomSamplingContext { * Object representing the incoming request to a node server. Passed by default when using the TracingHandler. */ request?: ExtractedNodeRequestData; + + /** The name of the span being sampled. */ + name: string; + + /** Initial attributes that have been passed to the span being sampled. */ + attributes?: SpanAttributes; } /** diff --git a/packages/vercel-edge/src/index.ts b/packages/vercel-edge/src/index.ts index 0e54798f231e..58fdc6bdd9c4 100644 --- a/packages/vercel-edge/src/index.ts +++ b/packages/vercel-edge/src/index.ts @@ -13,7 +13,6 @@ export type { StackFrame, Stacktrace, Thread, - Transaction, User, } from '@sentry/types'; export type { AddRequestDataToEventOptions } from '@sentry/utils'; diff --git a/packages/vue/src/router.ts b/packages/vue/src/router.ts index 8edb6eefd640..e2e124fb63db 100644 --- a/packages/vue/src/router.ts +++ b/packages/vue/src/router.ts @@ -6,7 +6,7 @@ import { getRootSpan, spanToJSON, } from '@sentry/core'; -import type { Span, SpanAttributes, TransactionArguments, TransactionSource } from '@sentry/types'; +import type { Span, SpanAttributes, StartSpanOptions, TransactionSource } from '@sentry/types'; // The following type is an intersection of the Route type from VueRouter v2, v3, and v4. // This is not great, but kinda necessary to make it work with all versions at the same time. @@ -47,7 +47,7 @@ export function instrumentVueRouter( instrumentPageLoad: boolean; instrumentNavigation: boolean; }, - startNavigationSpanFn: (context: TransactionArguments) => void, + startNavigationSpanFn: (context: StartSpanOptions) => void, ): void { router.onError(error => captureException(error, { mechanism: { handled: false } })); From 7b355d032d8076b69bf01b4c5eb0a434948846d2 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Thu, 4 Apr 2024 12:10:51 +0200 Subject: [PATCH 2/2] fix linting --- packages/core/src/tracing/sentrySpan.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/src/tracing/sentrySpan.ts b/packages/core/src/tracing/sentrySpan.ts index f69f5a8f610b..c7d436ae3c0a 100644 --- a/packages/core/src/tracing/sentrySpan.ts +++ b/packages/core/src/tracing/sentrySpan.ts @@ -217,7 +217,6 @@ export class SentrySpan implements Span { /** Emit `spanEnd` when the span is ended. */ private _onSpanEnded(): void { - console.log('SPAN END!', this === getRootSpan(this)); const client = getClient(); if (client) { client.emit('spanEnd', this);