From e17852af41badcca12feed3fbccd8d9405365677 Mon Sep 17 00:00:00 2001 From: Xinyi Ye Date: Wed, 27 Sep 2023 07:53:08 +1300 Subject: [PATCH] feat: diagnostic track --- .../analytics-core/src/plugins/destination.ts | 80 ++++++++++++------- 1 file changed, 53 insertions(+), 27 deletions(-) diff --git a/packages/analytics-core/src/plugins/destination.ts b/packages/analytics-core/src/plugins/destination.ts index 0461efc09..f68217d83 100644 --- a/packages/analytics-core/src/plugins/destination.ts +++ b/packages/analytics-core/src/plugins/destination.ts @@ -134,6 +134,7 @@ export class Destination implements DestinationPlugin { async send(list: Context[], useRetry = true) { if (!this.config.apiKey) { + (this.config.diagnosticProvider as Diagnostic).track(list.length, 400, 'missing API key'); return this.fulfillRequest(list, 400, MISSING_API_KEY_MESSAGE); } @@ -153,26 +154,20 @@ export class Destination implements DestinationPlugin { const { serverUrl } = createServerConfig(this.config.serverUrl, this.config.serverZone, this.config.useBatch); const res = await this.config.transportProvider.send(serverUrl, payload); if (res === null) { + (this.config.diagnosticProvider as Diagnostic).track(list.length, 0, 'unexpected error'); this.fulfillRequest(list, 0, UNEXPECTED_ERROR_MESSAGE); return; } - if (!useRetry) { - if ('body' in res) { - this.fulfillRequest(list, res.statusCode, `${res.status}: ${getResponseBodyString(res)}`); - } else { - this.fulfillRequest(list, res.statusCode, res.status); - } - return; - } - this.handleResponse(res, list); + this.handleResponse(res, list, useRetry); } catch (e) { const errorMessage = getErrorMessage(e); this.config.loggerProvider.error(errorMessage); this.fulfillRequest(list, 0, errorMessage); + (this.config.diagnosticProvider as Diagnostic).track(list.length, 0, 'unexpected error'); } } - handleResponse(res: Response, list: Context[]) { + handleResponse(res: Response, list: Context[], useRetry: boolean) { const { status } = res; switch (status) { @@ -181,22 +176,22 @@ export class Destination implements DestinationPlugin { break; } case Status.Invalid: { - this.handleInvalidResponse(res, list); + this.handleInvalidResponse(res, list, useRetry); break; } case Status.PayloadTooLarge: { - this.handlePayloadTooLargeResponse(res, list); + this.handlePayloadTooLargeResponse(res, list, useRetry); break; } case Status.RateLimit: { - this.handleRateLimitResponse(res, list); + this.handleRateLimitResponse(res, list, useRetry); break; } default: { // log intermediate event status before retry this.config.loggerProvider.warn(`{code: 0, error: "Status '${status}' provided for ${list.length} events"}`); - this.handleOtherResponse(list); + this.handleOtherResponse(list, useRetry); break; } } @@ -206,8 +201,21 @@ export class Destination implements DestinationPlugin { this.fulfillRequest(list, res.statusCode, SUCCESS_MESSAGE); } - handleInvalidResponse(res: InvalidResponse, list: Context[]) { - if (res.body.missingField || res.body.error.startsWith(INVALID_API_KEY)) { + handleInvalidResponse(res: InvalidResponse, list: Context[], useRetry: boolean) { + if (res.body.missingField) { + (this.config.diagnosticProvider as Diagnostic).track(list.length, 400, 'missing fields'); + this.fulfillRequest(list, res.statusCode, res.body.error); + return; + } + + if (res.body.error.startsWith(INVALID_API_KEY)) { + (this.config.diagnosticProvider as Diagnostic).track(list.length, 400, 'invalid API key'); + this.fulfillRequest(list, res.statusCode, res.body.error); + return; + } + + if (!useRetry) { + (this.config.diagnosticProvider as Diagnostic).track(list.length, 400, 'xxx'); this.fulfillRequest(list, res.statusCode, res.body.error); return; } @@ -219,6 +227,7 @@ export class Destination implements DestinationPlugin { ...res.body.silencedEvents, ].flat(); const dropIndexSet = new Set(dropIndex); + (this.config.diagnosticProvider as Diagnostic).track(dropIndexSet.size, 400, 'event error'); const retry = list.filter((context, index) => { if (dropIndexSet.has(index)) { @@ -235,8 +244,9 @@ export class Destination implements DestinationPlugin { this.addToQueue(...retry); } - handlePayloadTooLargeResponse(res: PayloadTooLargeResponse, list: Context[]) { - if (list.length === 1) { + handlePayloadTooLargeResponse(res: PayloadTooLargeResponse, list: Context[], useRetry: boolean) { + if (list.length === 1 || !useRetry) { + (this.config.diagnosticProvider as Diagnostic).track(list.length, 413, 'payload too large'); this.fulfillRequest(list, res.statusCode, res.body.error); return; } @@ -248,7 +258,13 @@ export class Destination implements DestinationPlugin { this.addToQueue(...list); } - handleRateLimitResponse(res: RateLimitResponse, list: Context[]) { + handleRateLimitResponse(res: RateLimitResponse, list: Context[], useRetry: boolean) { + if (!useRetry) { + (this.config.diagnosticProvider as Diagnostic).track(list.length, 429, 'exceeded daily quota users or devices'); + this.fulfillRequest(list, res.statusCode, res.body.error); + return; + } + const dropUserIds = Object.keys(res.body.exceededDailyQuotaUsers); const dropDeviceIds = Object.keys(res.body.exceededDailyQuotaDevices); const throttledIndex = res.body.throttledEvents; @@ -270,6 +286,15 @@ export class Destination implements DestinationPlugin { return true; }); + const dropEvents = retry.filter((element) => !retry.includes(element)); + if (dropEvents.length > 0) { + (this.config.diagnosticProvider as Diagnostic).track( + dropEvents.length, + 429, + 'exceeded daily quota users or devices', + ); + } + if (retry.length > 0) { // log intermediate event status before retry this.config.loggerProvider.warn(getResponseBodyString(res)); @@ -278,17 +303,18 @@ export class Destination implements DestinationPlugin { this.addToQueue(...retry); } - handleOtherResponse(list: Context[]) { - this.addToQueue( - ...list.map((context) => { - context.timeout = context.attempts * this.retryTimeout; - return context; - }), - ); + handleOtherResponse(list: Context[], useRetry: boolean) { + if (useRetry) { + this.addToQueue( + ...list.map((context) => { + context.timeout = context.attempts * this.retryTimeout; + return context; + }), + ); + } } fulfillRequest(list: Context[], code: number, message: string) { - (this.config.diagnosticProvider as Diagnostic).track(list.length, code, message); this.saveEvents(); list.forEach((context) => context.callback(buildResult(context.event, code, message))); }