11import type { TransactionContext } from '@sentry/types' ;
22import { isThenable } from '@sentry/utils' ;
33
4+ import type { Hub } from '../hub' ;
45import { getCurrentHub } from '../hub' ;
56import { hasTracingEnabled } from '../utils/hasTracingEnabled' ;
67import type { Span } from './span' ;
@@ -23,25 +24,14 @@ export function trace<T>(
2324 // eslint-disable-next-line @typescript-eslint/no-empty-function
2425 onError : ( error : unknown ) => void = ( ) => { } ,
2526) : T {
26- const ctx = { ...context } ;
27- // If a name is set and a description is not, set the description to the name.
28- if ( ctx . name !== undefined && ctx . description === undefined ) {
29- ctx . description = ctx . name ;
30- }
27+ const ctx = normalizeContext ( context ) ;
3128
3229 const hub = getCurrentHub ( ) ;
3330 const scope = hub . getScope ( ) ;
34-
3531 const parentSpan = scope . getSpan ( ) ;
3632
37- function createChildSpanOrTransaction ( ) : Span | undefined {
38- if ( ! hasTracingEnabled ( ) ) {
39- return undefined ;
40- }
41- return parentSpan ? parentSpan . startChild ( ctx ) : hub . startTransaction ( ctx ) ;
42- }
33+ const activeSpan = createChildSpanOrTransaction ( hub , parentSpan , ctx ) ;
4334
44- const activeSpan = createChildSpanOrTransaction ( ) ;
4535 scope . setSpan ( activeSpan ) ;
4636
4737 function finishAndSetSpan ( ) : void {
@@ -89,25 +79,13 @@ export function trace<T>(
8979 * and the `span` returned from the callback will be undefined.
9080 */
9181export function startSpan < T > ( context : TransactionContext , callback : ( span : Span | undefined ) => T ) : T {
92- const ctx = { ...context } ;
93- // If a name is set and a description is not, set the description to the name.
94- if ( ctx . name !== undefined && ctx . description === undefined ) {
95- ctx . description = ctx . name ;
96- }
82+ const ctx = normalizeContext ( context ) ;
9783
9884 const hub = getCurrentHub ( ) ;
9985 const scope = hub . getScope ( ) ;
100-
10186 const parentSpan = scope . getSpan ( ) ;
10287
103- function createChildSpanOrTransaction ( ) : Span | undefined {
104- if ( ! hasTracingEnabled ( ) ) {
105- return undefined ;
106- }
107- return parentSpan ? parentSpan . startChild ( ctx ) : hub . startTransaction ( ctx ) ;
108- }
109-
110- const activeSpan = createChildSpanOrTransaction ( ) ;
88+ const activeSpan = createChildSpanOrTransaction ( hub , parentSpan , ctx ) ;
11189 scope . setSpan ( activeSpan ) ;
11290
11391 function finishAndSetSpan ( ) : void {
@@ -146,6 +124,52 @@ export function startSpan<T>(context: TransactionContext, callback: (span: Span
146124 */
147125export const startActiveSpan = startSpan ;
148126
127+ /**
128+ * Similar to `Sentry.startSpan`. Wraps a function with a transaction/span, but does not finish the span
129+ * after the function is done automatically.
130+ *
131+ * The created span is the active span and will be used as parent by other spans created inside the function
132+ * and can be accessed via `Sentry.getActiveSpan()`, as long as the function is executed while the scope is active.
133+ *
134+ * Note that if you have not enabled tracing extensions via `addTracingExtensions`
135+ * or you didn't set `tracesSampleRate`, this function will not generate spans
136+ * and the `span` returned from the callback will be undefined.
137+ */
138+ export function startSpanManual < T > (
139+ context : TransactionContext ,
140+ callback : ( span : Span | undefined , finish : ( ) => void ) => T ,
141+ ) : T {
142+ const ctx = normalizeContext ( context ) ;
143+
144+ const hub = getCurrentHub ( ) ;
145+ const scope = hub . getScope ( ) ;
146+ const parentSpan = scope . getSpan ( ) ;
147+
148+ const activeSpan = createChildSpanOrTransaction ( hub , parentSpan , ctx ) ;
149+ scope . setSpan ( activeSpan ) ;
150+
151+ function finishAndSetSpan ( ) : void {
152+ activeSpan && activeSpan . finish ( ) ;
153+ hub . getScope ( ) . setSpan ( parentSpan ) ;
154+ }
155+
156+ let maybePromiseResult : T ;
157+ try {
158+ maybePromiseResult = callback ( activeSpan , finishAndSetSpan ) ;
159+ } catch ( e ) {
160+ activeSpan && activeSpan . setStatus ( 'internal_error' ) ;
161+ throw e ;
162+ }
163+
164+ if ( isThenable ( maybePromiseResult ) ) {
165+ Promise . resolve ( maybePromiseResult ) . then ( undefined , ( ) => {
166+ activeSpan && activeSpan . setStatus ( 'internal_error' ) ;
167+ } ) ;
168+ }
169+
170+ return maybePromiseResult ;
171+ }
172+
149173/**
150174 * Creates a span. This span is not set as active, so will not get automatic instrumentation spans
151175 * as children or be able to be accessed via `Sentry.getSpan()`.
@@ -178,3 +202,24 @@ export function startInactiveSpan(context: TransactionContext): Span | undefined
178202export function getActiveSpan ( ) : Span | undefined {
179203 return getCurrentHub ( ) . getScope ( ) . getSpan ( ) ;
180204}
205+
206+ function createChildSpanOrTransaction (
207+ hub : Hub ,
208+ parentSpan : Span | undefined ,
209+ ctx : TransactionContext ,
210+ ) : Span | undefined {
211+ if ( ! hasTracingEnabled ( ) ) {
212+ return undefined ;
213+ }
214+ return parentSpan ? parentSpan . startChild ( ctx ) : hub . startTransaction ( ctx ) ;
215+ }
216+
217+ function normalizeContext ( context : TransactionContext ) : TransactionContext {
218+ const ctx = { ...context } ;
219+ // If a name is set and a description is not, set the description to the name.
220+ if ( ctx . name !== undefined && ctx . description === undefined ) {
221+ ctx . description = ctx . name ;
222+ }
223+
224+ return ctx ;
225+ }
0 commit comments