diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts index 659b80bc8962..f46b55f45214 100644 --- a/packages/browser/src/exports.ts +++ b/packages/browser/src/exports.ts @@ -40,6 +40,7 @@ export { getActiveSpan, startSpan, startInactiveSpan, + startSpanManual, SDK_VERSION, setContext, setExtra, diff --git a/packages/core/src/tracing/index.ts b/packages/core/src/tracing/index.ts index c4ed853c5316..7c42aad2a15d 100644 --- a/packages/core/src/tracing/index.ts +++ b/packages/core/src/tracing/index.ts @@ -7,6 +7,6 @@ export { extractTraceparentData, getActiveTransaction } from './utils'; export { SpanStatus } from './spanstatus'; export type { SpanStatusType } from './span'; // eslint-disable-next-line deprecation/deprecation -export { trace, getActiveSpan, startSpan, startInactiveSpan, startActiveSpan } from './trace'; +export { trace, getActiveSpan, startSpan, startInactiveSpan, startActiveSpan, startSpanManual } from './trace'; export { getDynamicSamplingContextFromClient } from './dynamicSamplingContext'; export { setMeasurement } from './measurement'; diff --git a/packages/core/src/tracing/trace.ts b/packages/core/src/tracing/trace.ts index 31671104bd02..8f9b226b4afb 100644 --- a/packages/core/src/tracing/trace.ts +++ b/packages/core/src/tracing/trace.ts @@ -1,6 +1,7 @@ import type { TransactionContext } from '@sentry/types'; import { isThenable } from '@sentry/utils'; +import type { Hub } from '../hub'; import { getCurrentHub } from '../hub'; import { hasTracingEnabled } from '../utils/hasTracingEnabled'; import type { Span } from './span'; @@ -23,25 +24,14 @@ export function trace( // eslint-disable-next-line @typescript-eslint/no-empty-function onError: (error: unknown) => void = () => {}, ): T { - const ctx = { ...context }; - // If a name is set and a description is not, set the description to the name. - if (ctx.name !== undefined && ctx.description === undefined) { - ctx.description = ctx.name; - } + const ctx = normalizeContext(context); const hub = getCurrentHub(); const scope = hub.getScope(); - const parentSpan = scope.getSpan(); - function createChildSpanOrTransaction(): Span | undefined { - if (!hasTracingEnabled()) { - return undefined; - } - return parentSpan ? parentSpan.startChild(ctx) : hub.startTransaction(ctx); - } + const activeSpan = createChildSpanOrTransaction(hub, parentSpan, ctx); - const activeSpan = createChildSpanOrTransaction(); scope.setSpan(activeSpan); function finishAndSetSpan(): void { @@ -89,25 +79,13 @@ export function trace( * and the `span` returned from the callback will be undefined. */ export function startSpan(context: TransactionContext, callback: (span: Span | undefined) => T): T { - const ctx = { ...context }; - // If a name is set and a description is not, set the description to the name. - if (ctx.name !== undefined && ctx.description === undefined) { - ctx.description = ctx.name; - } + const ctx = normalizeContext(context); const hub = getCurrentHub(); const scope = hub.getScope(); - const parentSpan = scope.getSpan(); - function createChildSpanOrTransaction(): Span | undefined { - if (!hasTracingEnabled()) { - return undefined; - } - return parentSpan ? parentSpan.startChild(ctx) : hub.startTransaction(ctx); - } - - const activeSpan = createChildSpanOrTransaction(); + const activeSpan = createChildSpanOrTransaction(hub, parentSpan, ctx); scope.setSpan(activeSpan); function finishAndSetSpan(): void { @@ -146,6 +124,52 @@ export function startSpan(context: TransactionContext, callback: (span: Span */ export const startActiveSpan = startSpan; +/** + * Similar to `Sentry.startSpan`. Wraps a function with a transaction/span, but does not finish the span + * after the function is done automatically. + * + * The created span is the active span and will be used as parent by other spans created inside the function + * and can be accessed via `Sentry.getActiveSpan()`, as long as the function is executed while the scope is active. + * + * Note that if you have not enabled tracing extensions via `addTracingExtensions` + * or you didn't set `tracesSampleRate`, this function will not generate spans + * and the `span` returned from the callback will be undefined. + */ +export function startSpanManual( + context: TransactionContext, + callback: (span: Span | undefined, finish: () => void) => T, +): T { + const ctx = normalizeContext(context); + + const hub = getCurrentHub(); + const scope = hub.getScope(); + const parentSpan = scope.getSpan(); + + const activeSpan = createChildSpanOrTransaction(hub, parentSpan, ctx); + scope.setSpan(activeSpan); + + function finishAndSetSpan(): void { + activeSpan && activeSpan.finish(); + hub.getScope().setSpan(parentSpan); + } + + let maybePromiseResult: T; + try { + maybePromiseResult = callback(activeSpan, finishAndSetSpan); + } catch (e) { + activeSpan && activeSpan.setStatus('internal_error'); + throw e; + } + + if (isThenable(maybePromiseResult)) { + Promise.resolve(maybePromiseResult).then(undefined, () => { + activeSpan && activeSpan.setStatus('internal_error'); + }); + } + + return maybePromiseResult; +} + /** * Creates a span. This span is not set as active, so will not get automatic instrumentation spans * as children or be able to be accessed via `Sentry.getSpan()`. @@ -178,3 +202,24 @@ export function startInactiveSpan(context: TransactionContext): Span | undefined export function getActiveSpan(): Span | undefined { return getCurrentHub().getScope().getSpan(); } + +function createChildSpanOrTransaction( + hub: Hub, + parentSpan: Span | undefined, + ctx: TransactionContext, +): Span | undefined { + if (!hasTracingEnabled()) { + return undefined; + } + return parentSpan ? parentSpan.startChild(ctx) : hub.startTransaction(ctx); +} + +function normalizeContext(context: TransactionContext): TransactionContext { + const ctx = { ...context }; + // If a name is set and a description is not, set the description to the name. + if (ctx.name !== undefined && ctx.description === undefined) { + ctx.description = ctx.name; + } + + return ctx; +} diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index ab8c82e5a3fd..c7d93ef16463 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -60,6 +60,7 @@ export { // eslint-disable-next-line deprecation/deprecation startActiveSpan, startInactiveSpan, + startSpanManual, } from '@sentry/core'; export type { SpanStatusType } from '@sentry/core'; export { autoDiscoverNodePerformanceMonitoringIntegrations } from './tracing'; diff --git a/packages/serverless/src/index.ts b/packages/serverless/src/index.ts index 3d1a8c7c3aad..a17d0463202d 100644 --- a/packages/serverless/src/index.ts +++ b/packages/serverless/src/index.ts @@ -55,4 +55,5 @@ export { // eslint-disable-next-line deprecation/deprecation startActiveSpan, startInactiveSpan, + startSpanManual, } from '@sentry/node'; diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index b4a128843217..90c651a41175 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -50,6 +50,7 @@ export { // eslint-disable-next-line deprecation/deprecation startActiveSpan, startInactiveSpan, + startSpanManual, } from '@sentry/node'; // We can still leave this for the carrier init and type exports