11import { Alert , AlertContent , CacheName , Error , ErrorGroup , ErrorPriority , Ticket , TicketContent } from './models' ;
2- import { AlertProviderInterface , CacheProviderInterface , ErrorProviderInterface , TicketProviderInterface } from './interfaces' ;
2+ import {
3+ AlertProviderInterface ,
4+ CacheProviderInterface ,
5+ ErrorProviderInterface ,
6+ PrioritizationProviderInterface ,
7+ TicketProviderInterface
8+ } from './interfaces' ;
9+ import { ErrorCountPrioritizationProvider } from "./providers" ;
310
411const crypto = require ( 'crypto' ) ;
512
@@ -14,9 +21,16 @@ export type SynchronizerResult = {
1421 exitCode : number ,
1522}
1623
24+ export type SynchronizerErrorProviderConfig = {
25+ name : string ,
26+ provider : ErrorProviderInterface ,
27+ prioritizationProvider ?: PrioritizationProviderInterface ,
28+ lookbackHours ?: number ,
29+ maxErrors ?: number ,
30+ }
31+
1732export type SynchronizerConfig = {
18- serverErrorProvider ?: ErrorProviderInterface ,
19- clientErrorProvider ?: ErrorProviderInterface ,
33+ errors : SynchronizerErrorProviderConfig [ ] ,
2034 ticketProvider : TicketProviderInterface ,
2135 alertProvider : AlertProviderInterface ,
2236 cacheProvider : CacheProviderInterface ,
@@ -27,61 +41,112 @@ export class Synchronizer {
2741
2842 public constructor ( config : SynchronizerConfig ) {
2943 this . config = config ;
44+
45+ // validate config
46+ if ( this . config . errors . length === 0 ) {
47+ throw new Error ( 'There must be at least one error provider set in the configuration' ) ;
48+ }
49+
50+ // apply defaults for anything which is not set
51+ for ( let provider of this . config . errors ) {
52+ provider . lookbackHours ??= 24 ;
53+ provider . maxErrors ??= 1000 ;
54+ provider . prioritizationProvider ??= new ErrorCountPrioritizationProvider ( ) ;
55+ }
3056 }
3157
3258 public async run ( ) : Promise < SynchronizerResult > {
33- let result : SynchronizerResult = {
59+ let finalResult : SynchronizerResult = {
3460 completedErrorGroups : [ ] ,
3561 errors : [ ] ,
3662 exitCode : 0
3763 } ;
3864
65+ // run all error provider synchronizations in parallel
3966 try {
40- const errors = await this . config . serverErrorProvider . getErrors ( 24 , 1000 ) ;
41- const errorGroups : ErrorGroup [ ] = [ ] ;
42-
43- // build up the error groups from raw errors, which drive all downstream work
44- errors . forEach ( ( error ) => this . addToErrorGroups ( error , errorGroups ) ) ;
45-
46- // for each error group, create / update a ticket and alert as needed. in most cases, no work
47- // is done because the ticket + alert has already been created and does not need to be updated.
48- for ( const errorGroup of errorGroups ) {
67+ const errorPromises = this . config . errors . map ( async ( errorConfig ) => {
4968 try {
50- this . syncErrorGroup ( errorGroup ) ;
51- result . completedErrorGroups . push ( errorGroup ) ;
69+ this . runForErrorProvider ( errorConfig , finalResult ) ;
5270 } catch ( e ) {
53- result . errors . push ( {
71+ finalResult . exitCode = 1 ;
72+ finalResult . errors . push ( {
5473 message : e . message || e ,
55- errorGroup,
5674 } ) ;
5775
58- console . error ( 'Failed to synchronize an error into the ticketing and/or alerting system.' ) ;
59- console . error ( `The relevant error is named "${ errorGroup . name } "` ) ;
60- console . error ( 'The exception which occurred is:' , e ) ;
76+ console . error ( e ) ;
77+ }
78+ } ) ;
79+
80+ // check for any promise rejections from our error provider synchronizations
81+ const providerResults = await Promise . allSettled ( errorPromises ) ;
82+ for ( const [ index , providerResult ] of providerResults . entries ( ) ) {
83+ if ( providerResult . status === 'rejected' ) {
84+ const providerName = this . config . errors [ index ] . name ;
85+ console . error ( 'An unexpected exception occurred while trying to synchronize errors for the ' +
86+ `provider named "${ providerName } ":` , providerResult . reason ) ;
87+ finalResult . exitCode = 2 ;
88+ finalResult . errors . push ( {
89+ message : providerResult . reason . message || providerResult . reason ,
90+ } ) ;
6191 }
6292 }
93+ } catch ( e ) {
94+ finalResult . exitCode = 3 ;
95+ finalResult . errors . push ( {
96+ message : e . message || e ,
97+ } ) ;
98+
99+ console . error ( 'An unexpected exception occurred while running the error synchronizations' , e ) ;
100+ }
63101
64- // persist all cached data changes
102+ // persist all cached data changes
103+ try {
65104 this . config . cacheProvider . saveAllCaches ( ) ;
66105 } catch ( e ) {
67- result . exitCode = 1 ;
68- result . errors . push ( {
106+ finalResult . exitCode = 4 ;
107+ finalResult . errors . push ( {
69108 message : e . message || e ,
70109 } ) ;
71110
72- console . error ( e ) ;
111+ console . error ( 'An unexpected exception occurred while running the error synchronizations' , e ) ;
73112 }
74113
75- if ( result . errors . length > 0 ) {
114+ if ( finalResult . errors . length > 0 ) {
76115 console . error ( 'Some errors were not synchronized to the ticketing and/or alerting system. Please see errors above.' ) ;
77- result . exitCode = 2 ;
116+ finalResult . exitCode = finalResult . exitCode || 5 ;
78117 }
79118
80- return result ;
119+ return finalResult ;
120+ }
121+
122+ private async runForErrorProvider ( errorConfig : SynchronizerErrorProviderConfig , result : SynchronizerResult ) {
123+ const errors = await errorConfig . provider . getErrors ( errorConfig . lookbackHours , errorConfig . maxErrors ) ;
124+ const errorGroups : ErrorGroup [ ] = [ ] ;
125+
126+ // build up the error groups from raw errors, which drive all downstream work
127+ errors . forEach ( ( error ) => this . addToErrorGroups ( error , errorGroups , errorConfig . name ) ) ;
128+
129+ // for each error group, create / update a ticket and alert as needed. in most cases, no work
130+ // is done because the ticket + alert has already been created and does not need to be updated.
131+ for ( const errorGroup of errorGroups ) {
132+ try {
133+ this . syncErrorGroup ( errorGroup , errorConfig ) ;
134+ result . completedErrorGroups . push ( errorGroup ) ;
135+ } catch ( e ) {
136+ result . errors . push ( {
137+ message : e . message || e ,
138+ errorGroup,
139+ } ) ;
140+
141+ console . error ( 'Failed to synchronize an error into the ticketing and/or alerting system.' ) ;
142+ console . error ( `The relevant error is named "${ errorGroup . name } " from provider "${ errorConfig . name } "` ) ;
143+ console . error ( 'The exception which occurred is:' , e ) ;
144+ }
145+ }
81146 }
82147
83- private async syncErrorGroup ( errorGroup : ErrorGroup ) {
84- errorGroup . priority = this . determineErrorPriority ( errorGroup ) ;
148+ private async syncErrorGroup ( errorGroup : ErrorGroup , errorConfig : SynchronizerErrorProviderConfig ) {
149+ errorGroup . priority = await errorConfig . prioritizationProvider . determinePriority ( errorGroup ) ;
85150 errorGroup . ticket = await this . config . cacheProvider . getObject ( errorGroup . clientId , CacheName . Tickets ) ;
86151 errorGroup . alert = await this . config . cacheProvider . getObject ( errorGroup . clientId , CacheName . Alerts ) ;
87152
@@ -129,16 +194,14 @@ export class Synchronizer {
129194 this . config . cacheProvider . setObject ( errorGroup . alert . id , errorGroup . alert , CacheName . Alerts , false ) ;
130195 }
131196
132- private createErrorGroup ( error : Error ) : ErrorGroup {
197+ private createErrorGroup ( error : Error , sourceName : string ) : ErrorGroup {
133198 // truncate the error to the first 500 characters
134199 const maxNameLength = 500 ;
135- if ( error . name . length > maxNameLength ) {
136- error . name = error . name . substr ( 0 , maxNameLength ) ;
137- }
200+ error . name = `[${ sourceName } ] ${ error . name } ` . substr ( 0 , maxNameLength ) ;
138201
139202 // wipe out line numbers
140203 let normalizedName = error . name ;
141- normalizedName = normalizedName . replace ( / \. ( p h p | j s | j s x | t s | t s x | p y | g o | j a v a ) [: @ ] \d + / i, '.$1:XXX' ) ;
204+ normalizedName = normalizedName . replace ( / \. ( j s | j s x | t s | t s x | p h p | p y | g o | j a v a | c p p | h | c | c s | e x | e x s | r b ) [: @ ] \d + / i, '.$1:XXX' ) ;
142205
143206 // remove TypeError prefix from client errors that some browsers may emit
144207 normalizedName = normalizedName . replace ( / ( T y p e E r r o r : \s * ) / i, '' ) ;
@@ -148,6 +211,7 @@ export class Synchronizer {
148211
149212 return {
150213 name : normalizedName ,
214+ sourceName,
151215 type : error . type ,
152216 priority : ErrorPriority . P5 ,
153217 clientId : hash ,
@@ -159,8 +223,8 @@ export class Synchronizer {
159223 } ;
160224 }
161225
162- private addToErrorGroups ( error : Error , errorGroups : ErrorGroup [ ] ) {
163- const newErrorGroup = this . createErrorGroup ( error ) ;
226+ private addToErrorGroups ( error : Error , errorGroups : ErrorGroup [ ] , sourceName : string ) {
227+ const newErrorGroup = this . createErrorGroup ( error , sourceName ) ;
164228
165229 for ( let i = 0 ; i < errorGroups . length ; ++ i ) {
166230 const existingErrorGroup = errorGroups [ i ] ;
@@ -200,8 +264,4 @@ export class Synchronizer {
200264 existingAlert . priority !== freshAlertContent . priority ||
201265 existingAlert . ticketUrl !== freshAlertContent . ticketUrl ;
202266 }
203-
204- private determineErrorPriority ( errorGroup : ErrorGroup ) : ErrorPriority {
205- return ErrorPriority . P5 ; // TODO
206- }
207267}
0 commit comments