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,15 +24,10 @@ 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
3733 function createChildSpanOrTransaction ( ) : Span | undefined {
@@ -42,6 +38,7 @@ export function trace<T>(
4238 }
4339
4440 const activeSpan = createChildSpanOrTransaction ( ) ;
41+
4542 scope . setSpan ( activeSpan ) ;
4643
4744 function finishAndSetSpan ( ) : void {
@@ -89,25 +86,13 @@ export function trace<T>(
8986 * and the `span` returned from the callback will be undefined.
9087 */
9188export 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- }
89+ const ctx = normalizeContext ( context ) ;
9790
9891 const hub = getCurrentHub ( ) ;
9992 const scope = hub . getScope ( ) ;
100-
10193 const parentSpan = scope . getSpan ( ) ;
10294
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 ( ) ;
95+ const activeSpan = createChildSpanOrTransaction ( hub , parentSpan , ctx ) ;
11196 scope . setSpan ( activeSpan ) ;
11297
11398 function finishAndSetSpan ( ) : void {
@@ -146,6 +131,52 @@ export function startSpan<T>(context: TransactionContext, callback: (span: Span
146131 */
147132export const startActiveSpan = startSpan ;
148133
134+ /**
135+ * Similar to `Sentry.startSpan`. Wraps a function with a transaction/span, but does not finish the span
136+ * after the function is done automatically.
137+ *
138+ * The created span is the active span and will be used as parent by other spans created inside the function
139+ * and can be accessed via `Sentry.getActiveSpan()`, as long as the function is executed while the scope is active.
140+ *
141+ * Note that if you have not enabled tracing extensions via `addTracingExtensions`
142+ * or you didn't set `tracesSampleRate`, this function will not generate spans
143+ * and the `span` returned from the callback will be undefined.
144+ */
145+ export function startSpanManual < T > (
146+ context : TransactionContext ,
147+ callback : ( span : Span | undefined , finish : ( ) => void ) => T ,
148+ ) : T {
149+ const ctx = normalizeContext ( context ) ;
150+
151+ const hub = getCurrentHub ( ) ;
152+ const scope = hub . getScope ( ) ;
153+ const parentSpan = scope . getSpan ( ) ;
154+
155+ const activeSpan = createChildSpanOrTransaction ( hub , parentSpan , ctx ) ;
156+ scope . setSpan ( activeSpan ) ;
157+
158+ function finishAndSetSpan ( ) : void {
159+ activeSpan && activeSpan . finish ( ) ;
160+ hub . getScope ( ) . setSpan ( parentSpan ) ;
161+ }
162+
163+ let maybePromiseResult : T ;
164+ try {
165+ maybePromiseResult = callback ( activeSpan , finishAndSetSpan ) ;
166+ } catch ( e ) {
167+ activeSpan && activeSpan . setStatus ( 'internal_error' ) ;
168+ throw e ;
169+ }
170+
171+ if ( isThenable ( maybePromiseResult ) ) {
172+ Promise . resolve ( maybePromiseResult ) . then ( undefined , ( ) => {
173+ activeSpan && activeSpan . setStatus ( 'internal_error' ) ;
174+ } ) ;
175+ }
176+
177+ return maybePromiseResult ;
178+ }
179+
149180/**
150181 * Creates a span. This span is not set as active, so will not get automatic instrumentation spans
151182 * as children or be able to be accessed via `Sentry.getSpan()`.
@@ -178,3 +209,24 @@ export function startInactiveSpan(context: TransactionContext): Span | undefined
178209export function getActiveSpan ( ) : Span | undefined {
179210 return getCurrentHub ( ) . getScope ( ) . getSpan ( ) ;
180211}
212+
213+ function createChildSpanOrTransaction (
214+ hub : Hub ,
215+ parentSpan : Span | undefined ,
216+ ctx : TransactionContext ,
217+ ) : Span | undefined {
218+ if ( ! hasTracingEnabled ( ) ) {
219+ return undefined ;
220+ }
221+ return parentSpan ? parentSpan . startChild ( ctx ) : hub . startTransaction ( ctx ) ;
222+ }
223+
224+ function normalizeContext ( context : TransactionContext ) : TransactionContext {
225+ const ctx = { ...context } ;
226+ // If a name is set and a description is not, set the description to the name.
227+ if ( ctx . name !== undefined && ctx . description === undefined ) {
228+ ctx . description = ctx . name ;
229+ }
230+
231+ return ctx ;
232+ }
0 commit comments