@@ -11,6 +11,7 @@ const WARN_AFTER_CONSECUTIVE_FAILURES = 10;
1111const WARN_AFTER_FAILURE_MS = 5 * 60 * 1000 ;
1212const WARN_THROTTLE_MS = 15 * 60 * 1000 ;
1313const DROP_ERROR_THROTTLE_MS = 15 * 60 * 1000 ;
14+ const MAX_RESPONSE_BODY_PREVIEW_CHARS = 500 ;
1415
1516type PayloadContext = {
1617 active ?: boolean ;
@@ -66,6 +67,12 @@ export type BulkQueueOptions = {
6667 logger ?: Logger ;
6768} ;
6869
70+ type BulkErrorDetails = {
71+ responseBody ?: string ;
72+ apiErrorCode ?: string ;
73+ apiErrorMessage ?: string ;
74+ } ;
75+
6976function getSessionStorage ( ) : Storage | null {
7077 try {
7178 if ( typeof sessionStorage === "undefined" ) {
@@ -248,17 +255,20 @@ export class BulkQueue {
248255 const res = await this . sendBulk ( batch ) ;
249256 if ( ! res . ok ) {
250257 if ( res . status >= 400 && res . status < 500 ) {
251- const responseBody = await this . getResponseBodyPreview ( res ) ;
258+ const errorDetails = await this . getResponseErrorDetails ( res ) ;
259+ const errorSummary = this . getApiErrorSummary ( errorDetails ) ;
252260 this . retryCount = 0 ;
253261 this . firstFailureAt = null ;
254262 this . consecutiveFailures = 0 ;
255263 this . lastWarnAt = null ;
256264 this . logger ?. error (
257- "bulk request failed with non-retriable status; dropping batch" ,
265+ errorSummary
266+ ? `bulk request failed with non-retriable status; dropping batch: ${ errorSummary } `
267+ : "bulk request failed with non-retriable status; dropping batch" ,
258268 {
259269 status : res . status ,
260270 statusText : res . statusText ,
261- responseBody ,
271+ ... errorDetails ,
262272 } ,
263273 ) ;
264274 nextDelayMs = this . flushDelayMs ;
@@ -397,15 +407,64 @@ export class BulkQueue {
397407 }
398408 }
399409
400- private async getResponseBodyPreview ( res : Response ) {
410+ private getApiErrorSummary ( errorDetails : BulkErrorDetails ) {
411+ const code = errorDetails . apiErrorCode ;
412+ const message = errorDetails . apiErrorMessage ;
413+ if ( code && message ) {
414+ return `${ code } : ${ message } ` ;
415+ }
416+ return message ?? code ;
417+ }
418+
419+ private async getResponseErrorDetails ( res : Response ) : Promise < BulkErrorDetails > {
401420 try {
402421 const body = await res . text ( ) ;
403422 if ( ! body ) {
404- return undefined ;
423+ return { } ;
405424 }
406- return body . slice ( 0 , 500 ) ;
425+
426+ let apiErrorCode : string | undefined ;
427+ let apiErrorMessage : string | undefined ;
428+ try {
429+ const parsed : unknown = JSON . parse ( body ) ;
430+ const parsedError = this . extractApiError ( parsed ) ;
431+ apiErrorCode = parsedError . code ;
432+ apiErrorMessage = parsedError . message ;
433+ } catch {
434+ // ignore JSON parse failures
435+ }
436+
437+ return {
438+ responseBody : body . slice ( 0 , MAX_RESPONSE_BODY_PREVIEW_CHARS ) ,
439+ apiErrorCode,
440+ apiErrorMessage,
441+ } ;
407442 } catch {
408- return undefined ;
443+ return { } ;
409444 }
410445 }
446+
447+ private extractApiError ( value : unknown ) : { code ?: string ; message ?: string } {
448+ if ( ! isObject ( value ) ) {
449+ return { } ;
450+ }
451+
452+ const topLevelCode = typeof value . code === "string" ? value . code : undefined ;
453+ const topLevelMessage =
454+ typeof value . message === "string" ? value . message : undefined ;
455+
456+ const error = value . error ;
457+ if ( ! isObject ( error ) ) {
458+ return {
459+ code : topLevelCode ,
460+ message : topLevelMessage ,
461+ } ;
462+ }
463+
464+ return {
465+ code : typeof error . code === "string" ? error . code : topLevelCode ,
466+ message :
467+ typeof error . message === "string" ? error . message : topLevelMessage ,
468+ } ;
469+ }
411470}
0 commit comments