forked from microsoft/vscode-pull-request-github
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathloggingOctokit.ts
More file actions
125 lines (113 loc) · 4.8 KB
/
Copy pathloggingOctokit.ts
File metadata and controls
125 lines (113 loc) · 4.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Octokit } from '@octokit/rest';
import { ApolloClient, ApolloQueryResult, FetchResult, MutationOptions, NormalizedCacheObject, OperationVariables, QueryOptions } from 'apollo-boost';
import { RateLimiter } from 'limiter';
import * as vscode from 'vscode';
import Logger from '../common/logger';
import { ITelemetry } from '../common/telemetry';
import { RateLimit } from './graphql';
interface RestResponse {
headers: {
'x-ratelimit-limit': string;
'x-ratelimit-remaining': string;
}
}
export class RateLogger {
private limiter: RateLimiter;
private static ID = 'RateLimit';
private hasLoggedLowRateLimit: boolean = false;
constructor(private readonly telemetry: ITelemetry) {
this.limiter = new RateLimiter({ tokensPerInterval: 100, interval: 'second' });
}
public logAndLimit(): boolean {
if (!this.limiter.tryRemoveTokens(1)) {
Logger.error('API call count has exceeded 100 calls in 1 second.', RateLogger.ID);
// We have hit 100 requests in 1 second. This likely indicates a bug in the extension.
/* __GDPR__
"pr.highApiCallRate" : {}
*/
this.telemetry.sendTelemetryErrorEvent('pr.highApiCallRate');
vscode.window.showErrorMessage(vscode.l10n.t('The GitHub Pull Requests extension is making too many requests to GitHub. This indicates a bug in the extension. Please file an issue on GitHub and include the output from "GitHub Pull Request".'));
return false;
}
Logger.debug(`Extension rate limit remaining: ${this.limiter.getTokensRemaining()}`, RateLogger.ID);
return true;
}
public async logRateLimit(info: string | undefined, result: Promise<{ data: { rateLimit: RateLimit | undefined } | undefined } | undefined>, isRest: boolean = false) {
let rateLimitInfo;
try {
rateLimitInfo = (await result)?.data?.rateLimit;
} catch (e) {
// Ignore errors here since we're just trying to log the rate limit.
return;
}
if ((rateLimitInfo?.limit ?? 5000) < 5000) {
Logger.appendLine(`Unexpectedly low rate limit: ${rateLimitInfo?.limit}`, RateLogger.ID);
}
const remaining = `${isRest ? 'REST' : 'GraphQL'} Rate limit remaining: ${rateLimitInfo?.remaining}, ${info}`;
if ((rateLimitInfo?.remaining ?? 1000) < 1000) {
if (!this.hasLoggedLowRateLimit) {
/* __GDPR__
"pr.lowRateLimitRemaining" : {}
*/
this.telemetry.sendTelemetryErrorEvent('pr.lowRateLimitRemaining');
this.hasLoggedLowRateLimit = true;
}
Logger.warn(remaining, RateLogger.ID);
} else {
Logger.debug(remaining, RateLogger.ID);
}
}
public async logRestRateLimit(info: string | undefined, restResponse: Promise<RestResponse>) {
let result;
try {
result = await restResponse;
} catch (e) {
// Ignore errors here since we're just trying to log the rate limit.
return;
}
const rateLimit: RateLimit = {
cost: -1,
limit: Number(result.headers['x-ratelimit-limit']),
remaining: Number(result.headers['x-ratelimit-remaining']),
resetAt: ''
};
this.logRateLimit(info, Promise.resolve({ data: { rateLimit } }), true);
}
}
export class LoggingApolloClient {
constructor(private readonly _graphql: ApolloClient<NormalizedCacheObject>, private _rateLogger: RateLogger) { };
query<T = any, TVariables = OperationVariables>(options: QueryOptions<TVariables>): Promise<ApolloQueryResult<T>> {
if (this._rateLogger.logAndLimit()) {
const result = this._graphql.query(options);
this._rateLogger.logRateLimit((options.query.definitions[0] as { name: { value: string } | undefined }).name?.value, result as any);
return result;
} else {
throw new Error('API call count has exceeded a rate limit.');
}
}
mutate<T = any, TVariables = OperationVariables>(options: MutationOptions<T, TVariables>): Promise<FetchResult<T>> {
if (this._rateLogger.logAndLimit()) {
const result = this._graphql.mutate(options);
this._rateLogger.logRateLimit(options.context, result as any);
return result;
} else {
throw new Error('API call count has exceeded a rate limit.');
}
}
}
export class LoggingOctokit {
constructor(public readonly api: Octokit, private _rateLogger: RateLogger) { };
async call<T, U>(api: (T) => Promise<U>, args: T): Promise<U> {
if (this._rateLogger.logAndLimit()) {
const result = api(args);
this._rateLogger.logRestRateLimit((api as unknown as { endpoint: { DEFAULTS: { url: string } | undefined } | undefined }).endpoint?.DEFAULTS?.url, result as Promise<unknown> as Promise<RestResponse>);
return result;
} else {
throw new Error('API call count has exceeded a rate limit.');
}
}
}