diff --git a/packages/bun/test/integrations/bunserver.test.ts b/packages/bun/test/integrations/bunserver.test.ts index 95dcc9363e7d..b1dc17381ccb 100644 --- a/packages/bun/test/integrations/bunserver.test.ts +++ b/packages/bun/test/integrations/bunserver.test.ts @@ -23,11 +23,11 @@ describe('Bun Serve Integration', () => { }); test('generates a transaction around a request', async () => { - client.on('finishTransaction', transaction => { - expect(spanToJSON(transaction).status).toBe('ok'); - expect(spanToJSON(transaction).data?.['http.response.status_code']).toEqual(200); - expect(spanToJSON(transaction).op).toEqual('http.server'); - expect(spanToJSON(transaction).description).toEqual('GET /'); + client.on('spanEnd', span => { + expect(spanToJSON(span).status).toBe('ok'); + expect(spanToJSON(span).data?.['http.response.status_code']).toEqual(200); + expect(spanToJSON(span).op).toEqual('http.server'); + expect(spanToJSON(span).description).toEqual('GET /'); }); const server = Bun.serve({ @@ -43,11 +43,11 @@ describe('Bun Serve Integration', () => { }); test('generates a post transaction', async () => { - client.on('finishTransaction', transaction => { - expect(spanToJSON(transaction).status).toBe('ok'); - expect(spanToJSON(transaction).data?.['http.response.status_code']).toEqual(200); - expect(spanToJSON(transaction).op).toEqual('http.server'); - expect(spanToJSON(transaction).description).toEqual('POST /'); + client.on('spanEnd', span => { + expect(spanToJSON(span).status).toBe('ok'); + expect(spanToJSON(span).data?.['http.response.status_code']).toEqual(200); + expect(spanToJSON(span).op).toEqual('http.server'); + expect(spanToJSON(span).description).toEqual('POST /'); }); const server = Bun.serve({ @@ -72,14 +72,13 @@ describe('Bun Serve Integration', () => { const SENTRY_TRACE_HEADER = `${TRACE_ID}-${PARENT_SPAN_ID}-${PARENT_SAMPLED}`; const SENTRY_BAGGAGE_HEADER = 'sentry-version=1.0,sentry-environment=production'; - client.on('finishTransaction', transaction => { - expect(transaction.spanContext().traceId).toBe(TRACE_ID); - expect(transaction.parentSpanId).toBe(PARENT_SPAN_ID); - expect(spanIsSampled(transaction)).toBe(true); - // span.endTimestamp is already set in `finishTransaction` hook - expect(transaction.isRecording()).toBe(false); + client.on('spanEnd', span => { + expect(span.spanContext().traceId).toBe(TRACE_ID); + expect(spanToJSON(span).parent_span_id).toBe(PARENT_SPAN_ID); + expect(spanIsSampled(span)).toBe(true); + expect(span.isRecording()).toBe(false); - expect(getDynamicSamplingContextFromSpan(transaction)).toStrictEqual({ + expect(getDynamicSamplingContextFromSpan(span)).toStrictEqual({ version: '1.0', environment: 'production', }); @@ -100,7 +99,7 @@ describe('Bun Serve Integration', () => { }); test('does not create transactions for OPTIONS or HEAD requests', async () => { - client.on('finishTransaction', () => { + client.on('spanEnd', () => { // This will never run, but we want to make sure it doesn't run. expect(false).toEqual(true); }); diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index 811c6466e612..c27ecfa5c341 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -24,7 +24,6 @@ import type { SeverityLevel, Span, StartSpanOptions, - Transaction, TransactionEvent, Transport, TransportMakeRequestResponse, @@ -417,12 +416,6 @@ export abstract class BaseClient implements Client { // Keep on() & emit() signatures in sync with types' client.ts interface /* eslint-disable @typescript-eslint/unified-signatures */ - /** @inheritdoc */ - public on(hook: 'startTransaction', callback: (transaction: Transaction) => void): void; - - /** @inheritdoc */ - public on(hook: 'finishTransaction', callback: (transaction: Transaction) => void): void; - /** @inheritdoc */ public on(hook: 'spanStart', callback: (span: Span) => void): void; @@ -476,12 +469,6 @@ export abstract class BaseClient implements Client { this._hooks[hook].push(callback); } - /** @inheritdoc */ - public emit(hook: 'startTransaction', transaction: Transaction): void; - - /** @inheritdoc */ - public emit(hook: 'finishTransaction', transaction: Transaction): void; - /** @inheritdoc */ public emit(hook: 'spanStart', span: Span): void; diff --git a/packages/core/src/tracing/trace.ts b/packages/core/src/tracing/trace.ts index 6faa9b4f9505..8b3534be93c7 100644 --- a/packages/core/src/tracing/trace.ts +++ b/packages/core/src/tracing/trace.ts @@ -416,7 +416,6 @@ function _startTransaction(transactionContext: TransactionContext): Transaction }, }); if (client) { - client.emit('startTransaction', transaction); client.emit('spanStart', transaction); } return transaction; diff --git a/packages/core/src/tracing/transaction.ts b/packages/core/src/tracing/transaction.ts index 42a442d7ec0d..a95fe7c394f9 100644 --- a/packages/core/src/tracing/transaction.ts +++ b/packages/core/src/tracing/transaction.ts @@ -224,9 +224,6 @@ export class Transaction extends SentrySpan implements TransactionInterface { // eslint-disable-next-line deprecation/deprecation const client = this._hub.getClient(); - if (client) { - client.emit('finishTransaction', this); - } if (this._sampled !== true) { // At this point if `sampled !== true` we want to discard the transaction. diff --git a/packages/core/test/lib/base.test.ts b/packages/core/test/lib/base.test.ts index b85df791af9b..8f3af775f095 100644 --- a/packages/core/test/lib/base.test.ts +++ b/packages/core/test/lib/base.test.ts @@ -1,4 +1,4 @@ -import type { Client, Envelope, Event, Transaction } from '@sentry/types'; +import type { Client, Envelope, Event } from '@sentry/types'; import { SentryError, SyncPromise, dsnToString, logger } from '@sentry/utils'; import { @@ -1935,20 +1935,6 @@ describe('BaseClient', () => { ] as const; describe.each(scenarios)('with client %s', (_, client) => { - it('should call a startTransaction hook', () => { - expect.assertions(1); - - const mockTransaction = { - traceId: '86f39e84263a4de99c326acab3bfe3bd', - } as Transaction; - - client.on('startTransaction', transaction => { - expect(transaction).toEqual(mockTransaction); - }); - - client.emit('startTransaction', mockTransaction); - }); - it('should call a beforeEnvelope hook', () => { expect.assertions(1); diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts index 5d57f770719b..516f379f9cdb 100644 --- a/packages/core/test/lib/tracing/trace.test.ts +++ b/packages/core/test/lib/tracing/trace.test.ts @@ -23,7 +23,7 @@ import { withActiveSpan, } from '../../../src/tracing'; import { SentryNonRecordingSpan } from '../../../src/tracing/sentryNonRecordingSpan'; -import { getActiveSpan, getSpanDescendants } from '../../../src/utils/spanUtils'; +import { getActiveSpan, getRootSpan, getSpanDescendants } from '../../../src/utils/spanUtils'; import { TestClient, getDefaultTestClientOptions } from '../../mocks/client'; beforeAll(() => { @@ -84,8 +84,8 @@ describe('startSpan', () => { it('creates a transaction', async () => { let _span: Span | undefined = undefined; - client.on('finishTransaction', transaction => { - _span = transaction; + client.on('spanEnd', span => { + _span = span; }); try { await startSpan({ name: 'GET users/[id]' }, () => { @@ -102,8 +102,8 @@ describe('startSpan', () => { it('allows traceparent information to be overriden', async () => { let _span: Span | undefined = undefined; - client.on('finishTransaction', transaction => { - _span = transaction; + client.on('spanEnd', span => { + _span = span; }); try { await startSpan( @@ -129,8 +129,8 @@ describe('startSpan', () => { it('allows for transaction to be mutated', async () => { let _span: Span | undefined = undefined; - client.on('finishTransaction', transaction => { - _span = transaction; + client.on('spanEnd', span => { + _span = span; }); try { await startSpan({ name: 'GET users/[id]' }, span => { @@ -146,8 +146,10 @@ describe('startSpan', () => { it('creates a span with correct description', async () => { let _span: Span | undefined = undefined; - client.on('finishTransaction', transaction => { - _span = transaction; + client.on('spanEnd', span => { + if (span === getRootSpan(span)) { + _span = span; + } }); try { await startSpan({ name: 'GET users/[id]', parentSampled: true }, () => { @@ -170,8 +172,10 @@ describe('startSpan', () => { it('allows for span to be mutated', async () => { let _span: Span | undefined = undefined; - client.on('finishTransaction', transaction => { - _span = transaction; + client.on('spanEnd', span => { + if (span === getRootSpan(span)) { + _span = span; + } }); try { await startSpan({ name: 'GET users/[id]', parentSampled: true }, () => { @@ -200,8 +204,8 @@ describe('startSpan', () => { { origin: 'manual', attributes: { 'sentry.origin': 'auto.http.browser' } }, ])('correctly sets the span origin', async () => { let _span: Span | undefined = undefined; - client.on('finishTransaction', transaction => { - _span = transaction; + client.on('spanEnd', span => { + _span = span; }); try { await startSpan({ name: 'GET users/[id]', origin: 'auto.http.browser' }, () => { diff --git a/packages/replay/src/replay.ts b/packages/replay/src/replay.ts index 30ccda422544..6588a9e4d79d 100644 --- a/packages/replay/src/replay.ts +++ b/packages/replay/src/replay.ts @@ -3,11 +3,12 @@ import { EventType, record } from '@sentry-internal/rrweb'; import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, captureException, + getActiveSpan, getClient, - getCurrentScope, + getRootSpan, spanToJSON, } from '@sentry/core'; -import type { ReplayRecordingMode, Transaction } from '@sentry/types'; +import type { ReplayRecordingMode, Span } from '@sentry/types'; import { logger } from '@sentry/utils'; import { @@ -87,10 +88,10 @@ export class ReplayContainer implements ReplayContainerInterface { public recordingMode: ReplayRecordingMode; /** - * The current or last active transcation. + * The current or last active span. * This is only available when performance is enabled. */ - public lastTransaction?: Transaction; + public lastActiveSpan?: Span; /** * These are here so we can overwrite them in tests etc. @@ -720,16 +721,16 @@ export class ReplayContainer implements ReplayContainerInterface { * This is only available if performance is enabled, and if an instrumented router is used. */ public getCurrentRoute(): string | undefined { - // eslint-disable-next-line deprecation/deprecation - const lastTransaction = this.lastTransaction || getCurrentScope().getTransaction(); + const lastActiveSpan = this.lastActiveSpan || getActiveSpan(); + const lastRootSpan = lastActiveSpan && getRootSpan(lastActiveSpan); - const attributes = (lastTransaction && spanToJSON(lastTransaction).data) || {}; + const attributes = (lastRootSpan && spanToJSON(lastRootSpan).data) || {}; const source = attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]; - if (!lastTransaction || !source || !['route', 'custom'].includes(source)) { + if (!lastRootSpan || !source || !['route', 'custom'].includes(source)) { return undefined; } - return spanToJSON(lastTransaction).description; + return spanToJSON(lastRootSpan).description; } /** diff --git a/packages/replay/src/types/replay.ts b/packages/replay/src/types/replay.ts index 61325e4a9959..8278ec781805 100644 --- a/packages/replay/src/types/replay.ts +++ b/packages/replay/src/types/replay.ts @@ -6,7 +6,7 @@ import type { ReplayRecordingData, ReplayRecordingMode, SentryWrappedXMLHttpRequest, - Transaction, + Span, XhrBreadcrumbHint, } from '@sentry/types'; @@ -478,7 +478,7 @@ export interface ReplayContainer { session: Session | undefined; recordingMode: ReplayRecordingMode; timeouts: Timeouts; - lastTransaction?: Transaction; + lastActiveSpan?: Span; throttledAddEvent: ( event: RecordingEvent, isCheckout?: boolean, diff --git a/packages/replay/src/util/addGlobalListeners.ts b/packages/replay/src/util/addGlobalListeners.ts index a5900fdea696..164b054ba2ac 100644 --- a/packages/replay/src/util/addGlobalListeners.ts +++ b/packages/replay/src/util/addGlobalListeners.ts @@ -44,14 +44,14 @@ export function addGlobalListeners(replay: ReplayContainer): void { } }); - client.on('startTransaction', transaction => { - replay.lastTransaction = transaction; + client.on('spanStart', span => { + replay.lastActiveSpan = span; }); - // We may be missing the initial startTransaction due to timing issues, + // We may be missing the initial spanStart due to timing issues, // so we capture it on finish again. - client.on('finishTransaction', transaction => { - replay.lastTransaction = transaction; + client.on('spanEnd', span => { + replay.lastActiveSpan = span; }); // We want to flush replay diff --git a/packages/sveltekit/test/server/handle.test.ts b/packages/sveltekit/test/server/handle.test.ts index 9dfbaa765dfc..42326baeade3 100644 --- a/packages/sveltekit/test/server/handle.test.ts +++ b/packages/sveltekit/test/server/handle.test.ts @@ -1,13 +1,14 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, addTracingExtensions, + getRootSpan, getSpanDescendants, spanIsSampled, spanToJSON, } from '@sentry/core'; import { NodeClient, setCurrentClient } from '@sentry/node-experimental'; import * as SentryNode from '@sentry/node-experimental'; -import type { Span, Transaction } from '@sentry/types'; +import type { Span } from '@sentry/types'; import type { Handle } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit'; import { vi } from 'vitest'; @@ -124,8 +125,10 @@ describe('handleSentry', () => { it("creates a transaction if there's no active span", async () => { let _span: Span | undefined = undefined; - client.on('finishTransaction', (transaction: Transaction) => { - _span = transaction; + client.on('spanEnd', span => { + if (span === getRootSpan(span)) { + _span = span; + } }); try { @@ -150,9 +153,11 @@ describe('handleSentry', () => { it('creates a child span for nested server calls (i.e. if there is an active span)', async () => { let _span: Span | undefined = undefined; let txnCount = 0; - client.on('finishTransaction', (transaction: Transaction) => { - _span = transaction; - ++txnCount; + client.on('spanEnd', span => { + if (span === getRootSpan(span)) { + _span = span; + ++txnCount; + } }); try { @@ -208,8 +213,10 @@ describe('handleSentry', () => { }); let _span: Span | undefined = undefined; - client.on('finishTransaction', (transaction: Transaction) => { - _span = transaction; + client.on('spanEnd', span => { + if (span === getRootSpan(span)) { + _span = span; + } }); try { @@ -248,8 +255,10 @@ describe('handleSentry', () => { }); let _span: Span | undefined = undefined; - client.on('finishTransaction', (transaction: Transaction) => { - _span = transaction; + client.on('spanEnd', span => { + if (span === getRootSpan(span)) { + _span = span; + } }); try { @@ -312,8 +321,10 @@ describe('handleSentry', () => { it("doesn't create a transaction if there's no route", async () => { let _span: Span | undefined = undefined; - client.on('finishTransaction', (transaction: Transaction) => { - _span = transaction; + client.on('spanEnd', span => { + if (span === getRootSpan(span)) { + _span = span; + } }); try { @@ -327,8 +338,10 @@ describe('handleSentry', () => { it("Creates a transaction if there's no route but `handleUnknownRequests` is true", async () => { let _span: Span | undefined = undefined; - client.on('finishTransaction', (transaction: Transaction) => { - _span = transaction; + client.on('spanEnd', span => { + if (span === getRootSpan(span)) { + _span = span; + } }); try { diff --git a/packages/types/src/client.ts b/packages/types/src/client.ts index 2bee7d3047ad..dad1911c72a7 100644 --- a/packages/types/src/client.ts +++ b/packages/types/src/client.ts @@ -16,7 +16,6 @@ import type { Session, SessionAggregates } from './session'; import type { SeverityLevel } from './severity'; import type { Span } from './span'; import type { StartSpanOptions } from './startSpanOptions'; -import type { Transaction } from './transaction'; import type { Transport, TransportMakeRequestResponse } from './transport'; /** @@ -185,18 +184,6 @@ export interface Client { // HOOKS /* eslint-disable @typescript-eslint/unified-signatures */ - /** - * Register a callback for transaction start. - * Receives the transaction as argument. - */ - on(hook: 'startTransaction', callback: (transaction: Transaction) => void): void; - - /** - * Register a callback for transaction finish. - * Receives the transaction as argument. - */ - on(hook: 'finishTransaction', callback: (transaction: Transaction) => void): void; - /** * Register a callback for whenever a span is started. * Receives the span as argument. @@ -278,18 +265,6 @@ export interface Client { */ on(hook: 'close', callback: () => void): void; - /** - * Fire a hook event for transaction start. - * Expects to be given a transaction as the second argument. - */ - emit(hook: 'startTransaction', transaction: Transaction): void; - - /** - * Fire a hook event for transaction finish. - * Expects to be given a transaction as the second argument. - */ - emit(hook: 'finishTransaction', transaction: Transaction): void; - /** Fire a hook whener a span starts. */ emit(hook: 'spanStart', span: Span): void;