1
1
import { AppNotification } from '$lib/notifications/notifications' ;
2
+ import type { IJsInvokableLogger } from '$lib/dotnet-types/generated-types/FwLiteShared/Services/IJsInvokableLogger' ;
3
+ import { LogLevel } from '$lib/dotnet-types/generated-types/Microsoft/Extensions/Logging/LogLevel' ;
4
+ import { delay } from '$lib/utils/time' ;
5
+ import { useJsInvokableLogger } from '$lib/services/js-invokable-logger' ;
2
6
3
- function getPromiseRejectionMessage ( event : PromiseRejectionEvent ) : string {
4
- if ( event . reason instanceof Error ) {
5
- return event . reason . message ;
7
+ type UnifiedErrorEvent = {
8
+ message : string ;
9
+ error : unknown ;
10
+ at ?: string ;
11
+ }
12
+
13
+ function unifyErrorEvent ( event : ErrorEvent | PromiseRejectionEvent ) : UnifiedErrorEvent {
14
+ if ( 'message' in event ) {
15
+ return { message : event . message , error : event . error , at : `${ event . filename } :${ event . lineno } :${ event . colno } ` } ;
6
16
} else if ( typeof event . reason === 'string' ) {
7
- return event . reason ;
17
+ return { message : event . reason , error : null } ;
18
+ } else if ( event . reason instanceof Error ) {
19
+ return { message : event . reason . message , error : event . reason } ;
8
20
} else {
9
- return 'Unknown error' ;
21
+ return { message : 'Unknown error' , error : event . reason } ;
10
22
}
11
23
}
12
24
@@ -24,30 +36,73 @@ System.InvalidOperationException: Everything is broken. Here's some ice cream.
24
36
*/
25
37
const dotnetErrorRegex = / ^ ( [ \s \S ] + ?) { 3 } a t / m;
26
38
27
- function processErrorIntoDetails ( message : string ) : { message : string , detail ?: string } {
39
+ function processErrorIntoDetails ( event : UnifiedErrorEvent ) : { message : string , detail ?: string } {
40
+ const message = event . message ;
28
41
const match = dotnetErrorRegex . exec ( message ) ;
29
- if ( ! match ) return { message} ;
30
- return { message : match [ 1 ] . trim ( ) , detail : message . substring ( match [ 1 ] . length ) . trim ( ) } ;
42
+ if ( match ) return { message : match [ 1 ] . trim ( ) , detail : message . substring ( match [ 1 ] . length ) . trim ( ) } ;
43
+ else if ( event . error instanceof Error ) return { message : message , detail : event . error . stack } ;
44
+ else return { message} ;
31
45
}
32
46
33
47
let setup = false ;
34
48
export function setupGlobalErrorHandlers ( ) {
35
49
if ( setup ) return ;
36
50
setup = true ;
37
- window . addEventListener ( 'error' , ( event : ErrorEvent ) => {
38
- console . error ( 'Global error' , event ) ;
39
-
40
- if ( suppressErrorNotification ( event . message ) ) return ;
41
- const { message : simpleMessage , detail} = processErrorIntoDetails ( event . message ) ;
42
- AppNotification . error ( simpleMessage , detail , event . message ) ;
43
- } ) ;
44
-
45
- window . addEventListener ( 'unhandledrejection' , ( event : PromiseRejectionEvent ) => {
46
- const message = getPromiseRejectionMessage ( event ) ;
47
- //no need to log these because they already get logged by blazor.web.js
48
-
49
- if ( suppressErrorNotification ( message ) ) return ;
50
- const { message : simpleMessage , detail} = processErrorIntoDetails ( message ) ;
51
- AppNotification . error ( simpleMessage , detail , message ) ;
52
- } ) ;
51
+ window . addEventListener ( 'error' , onErrorEvent ) ;
52
+ window . addEventListener ( 'unhandledrejection' , onErrorEvent ) ;
53
+ }
54
+
55
+ function onErrorEvent ( event : ErrorEvent | PromiseRejectionEvent ) {
56
+ const errorEvent = unifyErrorEvent ( event ) ;
57
+ void tryLogErrorToDotNet ( errorEvent ) ;
58
+ if ( suppressErrorNotification ( errorEvent . message ) ) return ;
59
+ const { message : simpleMessage , detail} = processErrorIntoDetails ( errorEvent ) ;
60
+ AppNotification . error ( simpleMessage , detail ) ;
61
+ }
62
+
63
+ async function tryLogErrorToDotNet ( error : UnifiedErrorEvent ) {
64
+ try {
65
+ const details = getErrorString ( error ) ;
66
+ if ( details . includes ( 'JsInvokableLogger' ) ) return ; // avoid potential infinite loop
67
+ const logger = await tryGetLogger ( ) ;
68
+ if ( logger ) await logger . log ( LogLevel . Error , details ) ;
69
+ else console . warn ( 'No DotNet logger available to log error' , error ) ;
70
+ } catch ( err ) {
71
+ console . error ( 'Failed to log error to DotNet' , err ) ;
72
+ }
73
+ }
74
+
75
+ // some very cheap durability.
76
+ // As it is today, the logger service is available before our error handlers are registered
77
+ async function tryGetLogger ( ) : Promise < IJsInvokableLogger | undefined > {
78
+ let logger = useJsInvokableLogger ( ) ;
79
+ if ( logger ) return logger ;
80
+ await delay ( 1 ) ;
81
+ logger = useJsInvokableLogger ( ) ;
82
+ if ( logger ) return logger ;
83
+ await delay ( 1000 ) ;
84
+ logger = useJsInvokableLogger ( ) ;
85
+ return logger ;
86
+ }
87
+
88
+ function getErrorString ( event : UnifiedErrorEvent ) {
89
+ const details = [ `Message: ${ event . message } ` ] ;
90
+ if ( event . at ) details . push ( `at ${ event . at } ` ) ;
91
+ if ( event . error instanceof Error ) {
92
+ const error : Error = event . error ;
93
+ if ( error . stack ) details . push ( `Stack: ${ error . stack } ` ) ;
94
+ if ( error . cause ) details . push ( `Cause: ${ tryStringify ( error . cause ) } ` ) ;
95
+ } else if ( event . error ) {
96
+ details . push ( `Error: ${ tryStringify ( event . error ) } ` ) ;
97
+ }
98
+
99
+ return details . join ( '\n' ) ;
100
+ }
101
+
102
+ function tryStringify ( value : unknown ) : string | undefined {
103
+ try {
104
+ return JSON . stringify ( value ) ;
105
+ } catch {
106
+ return '(failed-to-stringify)' ;
107
+ }
53
108
}
0 commit comments