From 4bccd08a65ab124822034bb66ceefa2490994149 Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Mon, 27 Apr 2026 14:54:40 +0200 Subject: [PATCH] Add error telemetry for ever GH API call --- src/github/githubRepository.ts | 48 +----------------------- src/github/loggingOctokit.ts | 68 ++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 47 deletions(-) diff --git a/src/github/githubRepository.ts b/src/github/githubRepository.ts index 74ab4cae05..6f7f791eab 100644 --- a/src/github/githubRepository.ts +++ b/src/github/githubRepository.ts @@ -55,7 +55,7 @@ import { User, } from './interface'; import { IssueChangeEvent, IssueModel } from './issueModel'; -import { LoggingOctokit } from './loggingOctokit'; +import { getErrorCode, LoggingOctokit } from './loggingOctokit'; import { PullRequestModel } from './pullRequestModel'; import defaultSchema from './queries.gql'; import * as extraSchema from './queriesExtra.gql'; @@ -144,52 +144,6 @@ export function isRateLimitError(e: unknown): boolean { return false; } -function isObject(value: unknown): value is Record { - return typeof value === 'object' && value !== null; -} - -export function getErrorCode(e: unknown): string | undefined { - if (!isObject(e)) { - return undefined; - } - - if (e.status !== undefined) { - return String(e.status); - } - - const networkError = e.networkError; - if (isObject(networkError) && networkError.statusCode !== undefined) { - return String(networkError.statusCode); - } - - const graphQLErrors = e.graphQLErrors; - if (Array.isArray(graphQLErrors)) { - const firstGraphQLError = graphQLErrors[0]; - if (isObject(firstGraphQLError)) { - const extensions = firstGraphQLError.extensions; - if (isObject(extensions) && extensions.code !== undefined) { - return String(extensions.code); - } - } - } - - if (e.code !== undefined) { - return String(e.code); - } - - if (typeof e.name === 'string' && e.name) { - const message = typeof e.message === 'string' ? e.message : ''; - if (e.name !== 'Error') { - return message ? `${e.name}: ${message}` : e.name; - } - if (message) { - return message; - } - } - - return undefined; -} - export enum TeamReviewerRefreshKind { None, Try, diff --git a/src/github/loggingOctokit.ts b/src/github/loggingOctokit.ts index 59d6afa8a8..5fc39ba634 100644 --- a/src/github/loggingOctokit.ts +++ b/src/github/loggingOctokit.ts @@ -28,6 +28,52 @@ interface RateLimitResult { } | undefined; } +function isObject(value: unknown): value is Record { + return typeof value === 'object' && value !== null; +} + +export function getErrorCode(e: unknown): string | undefined { + if (!isObject(e)) { + return undefined; + } + + if (e.status !== undefined) { + return String(e.status); + } + + const networkError = e.networkError; + if (isObject(networkError) && networkError.statusCode !== undefined) { + return String(networkError.statusCode); + } + + const graphQLErrors = e.graphQLErrors; + if (Array.isArray(graphQLErrors)) { + const firstGraphQLError = graphQLErrors[0]; + if (isObject(firstGraphQLError)) { + const extensions = firstGraphQLError.extensions; + if (isObject(extensions) && extensions.code !== undefined) { + return String(extensions.code); + } + } + } + + if (e.code !== undefined) { + return String(e.code); + } + + if (typeof e.name === 'string' && e.name) { + const message = typeof e.message === 'string' ? e.message : ''; + if (e.name !== 'Error') { + return message ? `${e.name}: ${message}` : e.name; + } + if (message) { + return message; + } + } + + return undefined; +} + export class RateLogger { private bulkhead: BulkheadPolicy = bulkhead(140); private static ID = 'RateLimit'; @@ -111,6 +157,25 @@ export class RateLogger { } } + public logApiError(info: string | undefined, apiResult: Promise): void { + apiResult.catch(e => { + const properties: { operation: string; errorCode?: string } = { + operation: RateLogger.sanitizeOperationName(info ?? 'unknown'), + }; + const errorCode = getErrorCode(e); + if (errorCode) { + properties.errorCode = errorCode; + } + /* __GDPR__ + "pr.apiCallFailed" : { + "operation": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "errorCode": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } + */ + this.telemetry.sendTelemetryErrorEvent('pr.apiCallFailed', properties); + }); + } + public async logRestRateLimit(info: string | undefined, restResponse: Promise) { let result; try { @@ -139,6 +204,7 @@ export class LoggingApolloClient { throw new Error('API call count has exceeded a rate limit.'); } this._rateLogger.logRateLimit(logInfo, result as Promise); + this._rateLogger.logApiError(logInfo, result); return result; } @@ -149,6 +215,7 @@ export class LoggingApolloClient { throw new Error('API call count has exceeded a rate limit.'); } this._rateLogger.logRateLimit(logInfo, result as Promise); + this._rateLogger.logApiError(logInfo, result); return result; } } @@ -163,6 +230,7 @@ export class LoggingOctokit { throw new Error('API call count has exceeded a rate limit.'); } this._rateLogger.logRestRateLimit(logInfo, result as Promise as Promise); + this._rateLogger.logApiError(logInfo, result); return result; } }