Skip to content

Commit f2befce

Browse files
authored
Add error telemetry for ever GH API call (#8706)
1 parent c3a2cf7 commit f2befce

2 files changed

Lines changed: 69 additions & 47 deletions

File tree

src/github/githubRepository.ts

Lines changed: 1 addition & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ import {
5555
User,
5656
} from './interface';
5757
import { IssueChangeEvent, IssueModel } from './issueModel';
58-
import { LoggingOctokit } from './loggingOctokit';
58+
import { getErrorCode, LoggingOctokit } from './loggingOctokit';
5959
import { PullRequestModel } from './pullRequestModel';
6060
import defaultSchema from './queries.gql';
6161
import * as extraSchema from './queriesExtra.gql';
@@ -144,52 +144,6 @@ export function isRateLimitError(e: unknown): boolean {
144144
return false;
145145
}
146146

147-
function isObject(value: unknown): value is Record<string, unknown> {
148-
return typeof value === 'object' && value !== null;
149-
}
150-
151-
export function getErrorCode(e: unknown): string | undefined {
152-
if (!isObject(e)) {
153-
return undefined;
154-
}
155-
156-
if (e.status !== undefined) {
157-
return String(e.status);
158-
}
159-
160-
const networkError = e.networkError;
161-
if (isObject(networkError) && networkError.statusCode !== undefined) {
162-
return String(networkError.statusCode);
163-
}
164-
165-
const graphQLErrors = e.graphQLErrors;
166-
if (Array.isArray(graphQLErrors)) {
167-
const firstGraphQLError = graphQLErrors[0];
168-
if (isObject(firstGraphQLError)) {
169-
const extensions = firstGraphQLError.extensions;
170-
if (isObject(extensions) && extensions.code !== undefined) {
171-
return String(extensions.code);
172-
}
173-
}
174-
}
175-
176-
if (e.code !== undefined) {
177-
return String(e.code);
178-
}
179-
180-
if (typeof e.name === 'string' && e.name) {
181-
const message = typeof e.message === 'string' ? e.message : '';
182-
if (e.name !== 'Error') {
183-
return message ? `${e.name}: ${message}` : e.name;
184-
}
185-
if (message) {
186-
return message;
187-
}
188-
}
189-
190-
return undefined;
191-
}
192-
193147
export enum TeamReviewerRefreshKind {
194148
None,
195149
Try,

src/github/loggingOctokit.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,52 @@ interface RateLimitResult {
2828
} | undefined;
2929
}
3030

31+
function isObject(value: unknown): value is Record<string, unknown> {
32+
return typeof value === 'object' && value !== null;
33+
}
34+
35+
export function getErrorCode(e: unknown): string | undefined {
36+
if (!isObject(e)) {
37+
return undefined;
38+
}
39+
40+
if (e.status !== undefined) {
41+
return String(e.status);
42+
}
43+
44+
const networkError = e.networkError;
45+
if (isObject(networkError) && networkError.statusCode !== undefined) {
46+
return String(networkError.statusCode);
47+
}
48+
49+
const graphQLErrors = e.graphQLErrors;
50+
if (Array.isArray(graphQLErrors)) {
51+
const firstGraphQLError = graphQLErrors[0];
52+
if (isObject(firstGraphQLError)) {
53+
const extensions = firstGraphQLError.extensions;
54+
if (isObject(extensions) && extensions.code !== undefined) {
55+
return String(extensions.code);
56+
}
57+
}
58+
}
59+
60+
if (e.code !== undefined) {
61+
return String(e.code);
62+
}
63+
64+
if (typeof e.name === 'string' && e.name) {
65+
const message = typeof e.message === 'string' ? e.message : '';
66+
if (e.name !== 'Error') {
67+
return message ? `${e.name}: ${message}` : e.name;
68+
}
69+
if (message) {
70+
return message;
71+
}
72+
}
73+
74+
return undefined;
75+
}
76+
3177
export class RateLogger {
3278
private bulkhead: BulkheadPolicy = bulkhead(140);
3379
private static ID = 'RateLimit';
@@ -111,6 +157,25 @@ export class RateLogger {
111157
}
112158
}
113159

160+
public logApiError(info: string | undefined, apiResult: Promise<unknown>): void {
161+
apiResult.catch(e => {
162+
const properties: { operation: string; errorCode?: string } = {
163+
operation: RateLogger.sanitizeOperationName(info ?? 'unknown'),
164+
};
165+
const errorCode = getErrorCode(e);
166+
if (errorCode) {
167+
properties.errorCode = errorCode;
168+
}
169+
/* __GDPR__
170+
"pr.apiCallFailed" : {
171+
"operation": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
172+
"errorCode": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
173+
}
174+
*/
175+
this.telemetry.sendTelemetryErrorEvent('pr.apiCallFailed', properties);
176+
});
177+
}
178+
114179
public async logRestRateLimit(info: string | undefined, restResponse: Promise<RestResponse>) {
115180
let result;
116181
try {
@@ -139,6 +204,7 @@ export class LoggingApolloClient {
139204
throw new Error('API call count has exceeded a rate limit.');
140205
}
141206
this._rateLogger.logRateLimit(logInfo, result as Promise<RateLimitResult>);
207+
this._rateLogger.logApiError(logInfo, result);
142208
return result;
143209
}
144210

@@ -149,6 +215,7 @@ export class LoggingApolloClient {
149215
throw new Error('API call count has exceeded a rate limit.');
150216
}
151217
this._rateLogger.logRateLimit(logInfo, result as Promise<RateLimitResult>);
218+
this._rateLogger.logApiError(logInfo, result);
152219
return result;
153220
}
154221
}
@@ -163,6 +230,7 @@ export class LoggingOctokit {
163230
throw new Error('API call count has exceeded a rate limit.');
164231
}
165232
this._rateLogger.logRestRateLimit(logInfo, result as Promise<unknown> as Promise<RestResponse>);
233+
this._rateLogger.logApiError(logInfo, result);
166234
return result;
167235
}
168236
}

0 commit comments

Comments
 (0)