-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(core): Deprecate trace
in favor of startSpan
#10012
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,11 @@ | ||
import type { Span, TransactionContext } from '@sentry/types'; | ||
import { dropUndefinedKeys, isThenable, logger, tracingContextFromHeaders } from '@sentry/utils'; | ||
import { dropUndefinedKeys, logger, tracingContextFromHeaders } from '@sentry/utils'; | ||
|
||
import { DEBUG_BUILD } from '../debug-build'; | ||
import { getCurrentScope, withScope } from '../exports'; | ||
import type { Hub } from '../hub'; | ||
import { getCurrentHub } from '../hub'; | ||
import { handleCallbackErrors } from '../utils/handleCallbackErrors'; | ||
import { hasTracingEnabled } from '../utils/hasTracingEnabled'; | ||
|
||
/** | ||
|
@@ -18,6 +19,8 @@ import { hasTracingEnabled } from '../utils/hasTracingEnabled'; | |
* | ||
* @internal | ||
* @private | ||
* | ||
* @deprecated Use `startSpan` instead. | ||
*/ | ||
export function trace<T>( | ||
context: TransactionContext, | ||
|
@@ -37,43 +40,18 @@ export function trace<T>( | |
|
||
scope.setSpan(activeSpan); | ||
|
||
function finishAndSetSpan(): void { | ||
activeSpan && activeSpan.end(); | ||
scope.setSpan(parentSpan); | ||
} | ||
|
||
let maybePromiseResult: T; | ||
try { | ||
maybePromiseResult = callback(activeSpan); | ||
} catch (e) { | ||
activeSpan && activeSpan.setStatus('internal_error'); | ||
onError(e, activeSpan); | ||
finishAndSetSpan(); | ||
afterFinish(); | ||
throw e; | ||
} | ||
|
||
if (isThenable(maybePromiseResult)) { | ||
// @ts-expect-error - the isThenable check returns the "wrong" type here | ||
return maybePromiseResult.then( | ||
res => { | ||
finishAndSetSpan(); | ||
afterFinish(); | ||
return res; | ||
}, | ||
e => { | ||
activeSpan && activeSpan.setStatus('internal_error'); | ||
onError(e, activeSpan); | ||
finishAndSetSpan(); | ||
afterFinish(); | ||
throw e; | ||
}, | ||
); | ||
} | ||
|
||
finishAndSetSpan(); | ||
afterFinish(); | ||
return maybePromiseResult; | ||
return handleCallbackErrors( | ||
() => callback(activeSpan), | ||
error => { | ||
activeSpan && activeSpan.setStatus('internal_error'); | ||
onError(error, activeSpan); | ||
}, | ||
() => { | ||
activeSpan && activeSpan.end(); | ||
scope.setSpan(parentSpan); | ||
afterFinish(); | ||
}, | ||
); | ||
} | ||
|
||
/** | ||
|
@@ -90,43 +68,23 @@ export function trace<T>( | |
export function startSpan<T>(context: TransactionContext, callback: (span: Span | undefined) => T): T { | ||
const ctx = normalizeContext(context); | ||
|
||
// @ts-expect-error - isThenable returns the wrong type | ||
return withScope(scope => { | ||
const hub = getCurrentHub(); | ||
const parentSpan = scope.getSpan(); | ||
|
||
const activeSpan = createChildSpanOrTransaction(hub, parentSpan, ctx); | ||
scope.setSpan(activeSpan); | ||
|
||
function finishAndSetSpan(): void { | ||
activeSpan && activeSpan.end(); | ||
} | ||
|
||
let maybePromiseResult: T; | ||
try { | ||
maybePromiseResult = callback(activeSpan); | ||
} catch (e) { | ||
activeSpan && activeSpan.setStatus('internal_error'); | ||
finishAndSetSpan(); | ||
throw e; | ||
} | ||
|
||
if (isThenable(maybePromiseResult)) { | ||
return maybePromiseResult.then( | ||
res => { | ||
finishAndSetSpan(); | ||
return res; | ||
}, | ||
e => { | ||
activeSpan && activeSpan.setStatus('internal_error'); | ||
finishAndSetSpan(); | ||
throw e; | ||
}, | ||
); | ||
} | ||
|
||
finishAndSetSpan(); | ||
return maybePromiseResult; | ||
return handleCallbackErrors( | ||
() => callback(activeSpan), | ||
() => { | ||
// Only update the span status if it hasn't been changed yet | ||
if (activeSpan && (!activeSpan.status || activeSpan.status === 'ok')) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Small adjustment here as well to make sure we don't overwrite the span status if it was set to something else before. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure if startSpan should concern itself with the span status. I believe the OTEL equivalent primitive also doesn't do this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, this is a "feature" that we do that automatically. Otherwise users would need to do that themselves all the time 🤔 no strong feelings, but this is what the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. given we are also an error monitoring SDK, I think it makes sense to set error status where appropriate, I think this bit of DX is useful for people. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
activeSpan.setStatus('internal_error'); | ||
} | ||
}, | ||
() => activeSpan && activeSpan.end(), | ||
); | ||
}); | ||
} | ||
|
||
|
@@ -152,7 +110,6 @@ export function startSpanManual<T>( | |
): T { | ||
const ctx = normalizeContext(context); | ||
|
||
// @ts-expect-error - isThenable returns the wrong type | ||
return withScope(scope => { | ||
const hub = getCurrentHub(); | ||
const parentSpan = scope.getSpan(); | ||
|
@@ -164,25 +121,15 @@ export function startSpanManual<T>( | |
activeSpan && activeSpan.end(); | ||
} | ||
|
||
let maybePromiseResult: T; | ||
try { | ||
maybePromiseResult = callback(activeSpan, finishAndSetSpan); | ||
} catch (e) { | ||
activeSpan && activeSpan.setStatus('internal_error'); | ||
throw e; | ||
} | ||
|
||
if (isThenable(maybePromiseResult)) { | ||
return maybePromiseResult.then( | ||
res => res, | ||
e => { | ||
activeSpan && activeSpan.setStatus('internal_error'); | ||
throw e; | ||
}, | ||
); | ||
} | ||
|
||
return maybePromiseResult; | ||
return handleCallbackErrors( | ||
() => callback(activeSpan, finishAndSetSpan), | ||
() => { | ||
// Only update the span status if it hasn't been changed yet, and the span is not yet finished | ||
if (activeSpan && !activeSpan.endTimestamp && (!activeSpan.status || activeSpan.status === 'ok')) { | ||
activeSpan.setStatus('internal_error'); | ||
} | ||
}, | ||
); | ||
}); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { isThenable } from '@sentry/utils'; | ||
|
||
/** | ||
* Wrap a callback function with error handling. | ||
* If an error is thrown, it will be passed to the `onError` callback and re-thrown. | ||
* | ||
* If the return value of the function is a promise, it will be handled with `maybeHandlePromiseRejection`. | ||
* | ||
* If an `onFinally` callback is provided, this will be called when the callback has finished | ||
* - so if it returns a promise, once the promise resolved/rejected, | ||
* else once the callback has finished executing. | ||
* The `onFinally` callback will _always_ be called, no matter if an error was thrown or not. | ||
*/ | ||
export function handleCallbackErrors< | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
Fn extends () => any, | ||
>( | ||
fn: Fn, | ||
onError: (error: unknown) => void, | ||
// eslint-disable-next-line @typescript-eslint/no-empty-function | ||
onFinally: () => void = () => {}, | ||
): ReturnType<Fn> { | ||
let maybePromiseResult: ReturnType<Fn>; | ||
try { | ||
maybePromiseResult = fn(); | ||
} catch (e) { | ||
onError(e); | ||
onFinally(); | ||
throw e; | ||
} | ||
|
||
return maybeHandlePromiseRejection(maybePromiseResult, onError, onFinally); | ||
} | ||
|
||
/** | ||
* Maybe handle a promise rejection. | ||
* This expects to be given a value that _may_ be a promise, or any other value. | ||
* If it is a promise, and it rejects, it will call the `onError` callback. | ||
* Other than this, it will generally return the given value as-is. | ||
*/ | ||
function maybeHandlePromiseRejection<MaybePromise>( | ||
value: MaybePromise, | ||
onError: (error: unknown) => void, | ||
onFinally: () => void, | ||
): MaybePromise { | ||
if (isThenable(value)) { | ||
// @ts-expect-error - the isThenable check returns the "wrong" type here | ||
return value.then( | ||
res => { | ||
onFinally(); | ||
return res; | ||
}, | ||
e => { | ||
onError(e); | ||
onFinally(); | ||
throw e; | ||
}, | ||
); | ||
} | ||
|
||
onFinally(); | ||
return value; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I see some nextjs E2E tests are failing - can it be that this was "swallowed" before due to this (which we changed for
startSpan
but I think not fortrace
? 🤔 cc @Lms24 / @lforstThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the failures might be legit. I don't think there was anything being swallowed. It seems like we're timing out on waiting for elements to appear on screen. Can you verify that you are returning all the values and not stalling anything? Are we returning the response here? https://github.com/getsentry/sentry-javascript/pull/10012/files#diff-6a177c0f974e405d7e55f4d0155181376d92b37749269c0904cdc6e94b17ef84L97
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added it back. Let's see.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OMG. good tests!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like an oversight a linter should have caught...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re added the lint rule here #10049