@@ -4,21 +4,31 @@ import { logAndExitProcess } from '../utils/errorhandling';
44
55type UnhandledRejectionMode = 'none' | 'warn' | 'strict' ;
66
7+ type IgnoreMatcher = { symbol : symbol } | { name ?: string | RegExp ; message ?: string | RegExp } ;
8+
79interface OnUnhandledRejectionOptions {
810 /**
911 * Option deciding what to do after capturing unhandledRejection,
1012 * that mimicks behavior of node's --unhandled-rejection flag.
1113 */
1214 mode : UnhandledRejectionMode ;
15+ /** Rejection Errors to ignore (don't capture or warn). */
16+ ignore ?: IgnoreMatcher [ ] ;
1317}
1418
1519const INTEGRATION_NAME = 'OnUnhandledRejection' ;
1620
21+ const DEFAULT_IGNORES : IgnoreMatcher [ ] = [
22+ {
23+ name : 'AI_NoOutputGeneratedError' , // When stream aborts in Vercel AI SDK, flush() fails with an error
24+ } ,
25+ ] ;
26+
1727const _onUnhandledRejectionIntegration = ( ( options : Partial < OnUnhandledRejectionOptions > = { } ) => {
18- const opts = {
19- mode : 'warn' ,
20- ...options ,
21- } satisfies OnUnhandledRejectionOptions ;
28+ const opts : OnUnhandledRejectionOptions = {
29+ mode : options . mode ?? 'warn' ,
30+ ignore : [ ...DEFAULT_IGNORES , ... ( options . ignore ?? [ ] ) ] ,
31+ } ;
2232
2333 return {
2434 name : INTEGRATION_NAME ,
@@ -28,31 +38,67 @@ const _onUnhandledRejectionIntegration = ((options: Partial<OnUnhandledRejection
2838 } ;
2939} ) satisfies IntegrationFn ;
3040
31- /**
32- * Add a global promise rejection handler.
33- */
3441export const onUnhandledRejectionIntegration = defineIntegration ( _onUnhandledRejectionIntegration ) ;
3542
36- /**
37- * Send an exception with reason
38- * @param reason string
39- * @param promise promise
40- *
41- * Exported only for tests.
42- */
43+ /** Extract error info safely */
44+ function extractErrorInfo ( reason : unknown ) : { name : string ; message : string ; isObject : boolean } {
45+ const isObject = reason !== null && typeof reason === 'object' ;
46+ if ( ! isObject ) {
47+ return { name : '' , message : String ( reason ?? '' ) , isObject } ;
48+ }
49+
50+ const errorLike = reason as Record < string , unknown > ;
51+ const name = typeof errorLike . name === 'string' ? errorLike . name : '' ;
52+ const message = typeof errorLike . message === 'string' ? errorLike . message : String ( reason ) ;
53+
54+ return { name, message, isObject } ;
55+ }
56+
57+ /** Check if a matcher matches the reason */
58+ function checkMatcher (
59+ matcher : IgnoreMatcher ,
60+ reason : unknown ,
61+ errorInfo : ReturnType < typeof extractErrorInfo > ,
62+ ) : boolean {
63+ if ( 'symbol' in matcher ) {
64+ return errorInfo . isObject && matcher . symbol in ( reason as object ) ;
65+ }
66+
67+ // name/message matcher
68+ const nameMatches =
69+ matcher . name === undefined ||
70+ ( typeof matcher . name === 'string' ? errorInfo . name === matcher . name : matcher . name . test ( errorInfo . name ) ) ;
71+
72+ const messageMatches =
73+ matcher . message === undefined ||
74+ ( typeof matcher . message === 'string'
75+ ? errorInfo . message . includes ( matcher . message )
76+ : matcher . message . test ( errorInfo . message ) ) ;
77+
78+ return nameMatches && messageMatches ;
79+ }
80+
81+ /** Match helper */
82+ function matchesIgnore ( reason : unknown , list : IgnoreMatcher [ ] ) : boolean {
83+ const errorInfo = extractErrorInfo ( reason ) ;
84+ return list . some ( matcher => checkMatcher ( matcher , reason , errorInfo ) ) ;
85+ }
86+
87+ /** Core handler */
4388export function makeUnhandledPromiseHandler (
4489 client : Client ,
4590 options : OnUnhandledRejectionOptions ,
4691) : ( reason : unknown , promise : unknown ) => void {
4792 return function sendUnhandledPromise ( reason : unknown , promise : unknown ) : void {
48- if ( getClient ( ) !== client ) {
49- return ;
50- }
93+ // Only handle for the active client
94+ if ( getClient ( ) !== client ) return ;
95+
96+ // Skip if configured to ignore
97+ if ( matchesIgnore ( reason , options . ignore ?? [ ] ) ) return ;
5198
5299 const level : SeverityLevel = options . mode === 'strict' ? 'fatal' : 'error' ;
53100
54- // this can be set in places where we cannot reliably get access to the active span/error
55- // when the error bubbles up to this handler, we can use this to set the active span
101+ // If upstream code stored an active span on the error, use it for linking.
56102 const activeSpanForError =
57103 reason && typeof reason === 'object' ? ( reason as { _sentry_active_span ?: Span } ) . _sentry_active_span : undefined ;
58104
0 commit comments