Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Abstract common base of startSpan and startSpanManual
  • Loading branch information
andreiborza committed Nov 26, 2025
commit c2baa79de24c7a597c1f6c235b2f62b4f051c8dc
91 changes: 23 additions & 68 deletions packages/opentelemetry/src/trace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,12 @@ import { getSamplingDecision } from './utils/getSamplingDecision';
import { makeTraceState } from './utils/makeTraceState';

/**
* Wraps a function with a transaction/span and finishes the span after the function is done.
* 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.
*
* If you want to create a span that is not set as active, use {@link startInactiveSpan}.
*
* You'll always get a span passed to the callback,
* it may just be a non-recording span if the span is not sampled or if tracing is disabled.
* Internal helper for starting spans and manual spans. See {@link startSpan} and {@link startSpanManual} for the public APIs.
* @param options - The span context options
* @param callback - The callback to execute with the span
* @param autoEnd - Whether to automatically end the span after the callback completes
*/
export function startSpan<T>(options: OpenTelemetrySpanContext, callback: (span: Span) => T): T {
function _startSpan<T>(options: OpenTelemetrySpanContext, callback: (span: Span) => T, autoEnd: boolean): T {
const tracer = getTracer();

const { name, parentSpan: customParentSpan } = options;
Expand Down Expand Up @@ -78,7 +74,7 @@ export function startSpan<T>(options: OpenTelemetrySpanContext, callback: (span:
span.setStatus({ code: SpanStatusCode.ERROR });
}
},
() => span.end(),
autoEnd ? () => span.end() : undefined,
);
});
});
Expand All @@ -94,15 +90,29 @@ export function startSpan<T>(options: OpenTelemetrySpanContext, callback: (span:
span.setStatus({ code: SpanStatusCode.ERROR });
}
},
() => span.end(),
autoEnd ? () => span.end() : undefined,
);
});
});
}

/**
* Wraps a function with a transaction/span and finishes the span after the function is done.
* 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.
*
* If you want to create a span that is not set as active, use {@link startInactiveSpan}.
*
* You'll always get a span passed to the callback,
* it may just be a non-recording span if the span is not sampled or if tracing is disabled.
*/
export function startSpan<T>(options: OpenTelemetrySpanContext, callback: (span: Span) => T): T {
return _startSpan(options, callback, true);
}

/**
* Similar to `Sentry.startSpan`. Wraps a function with a span, but does not finish the span
* after the function is done automatically. You'll have to call `span.end()` manually.
* after the function is done automatically. You'll have to call `span.end()` or the `finish` function passed to the callback manually.
*
* 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.
Expand All @@ -114,62 +124,7 @@ export function startSpanManual<T>(
options: OpenTelemetrySpanContext,
callback: (span: Span, finish: () => void) => T,
): T {
const tracer = getTracer();

const { name, parentSpan: customParentSpan } = options;

// If `options.parentSpan` is defined, we want to wrap the callback in `withActiveSpan`
const wrapper = getActiveSpanWrapper<T>(customParentSpan);

return wrapper(() => {
const activeCtx = getContext(options.scope, options.forceTransaction);
const shouldSkipSpan = options.onlyIfParent && !trace.getSpan(activeCtx);
const ctx = shouldSkipSpan ? suppressTracing(activeCtx) : activeCtx;

const spanOptions = getSpanOptions(options);

if (!hasSpansEnabled()) {
// If spans are not enabled, ensure we suppress tracing for the span creation
// but preserve the original context for the callback execution
// This ensures that we don't create spans when tracing is disabled which
// would otherwise be a problem for users that don't enable tracing but use
// custom OpenTelemetry setups.
const suppressedCtx = isTracingSuppressed(ctx) ? ctx : suppressTracing(ctx);

return context.with(suppressedCtx, () => {
return tracer.startActiveSpan(name, spanOptions, suppressedCtx, span => {
// Restore the original unsuppressed context for the callback execution
// so that custom OpenTelemetry spans maintain the correct context.
// We use activeCtx (not ctx) because ctx may be suppressed when onlyIfParent is true
// and no parent span exists. Using activeCtx ensures custom OTel spans are never
// inadvertently suppressed.
return context.with(activeCtx, () => {
return handleCallbackErrors(
() => callback(span, () => span.end()),
() => {
// Only set the span status to ERROR when there wasn't any status set before, in order to avoid stomping useful span statuses
if (spanToJSON(span).status === undefined) {
span.setStatus({ code: SpanStatusCode.ERROR });
}
},
);
});
});
});
}

return tracer.startActiveSpan(name, spanOptions, ctx, span => {
return handleCallbackErrors(
() => callback(span, () => span.end()),
() => {
// Only set the span status to ERROR when there wasn't any status set before, in order to avoid stomping useful span statuses
if (spanToJSON(span).status === undefined) {
span.setStatus({ code: SpanStatusCode.ERROR });
}
},
);
});
});
return _startSpan(options, span => callback(span, () => span.end()), false);
}

/**
Expand Down
Loading