Skip to content

Commit ef42b49

Browse files
authored
Support proxies in the telemetry service (microsoft#187952)
1 parent 5eb82ae commit ef42b49

File tree

5 files changed

+84
-22
lines changed

5 files changed

+84
-22
lines changed

src/vs/code/node/cliProcessMain.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,8 @@ class CliMain extends Disposable {
192192
services.set(IUriIdentityService, new UriIdentityService(fileService));
193193

194194
// Request
195-
services.set(IRequestService, new SyncDescriptor(RequestService, undefined, true));
195+
const requestService = new RequestService(configurationService, environmentService, logService, loggerService);
196+
services.set(IRequestService, requestService);
196197

197198
// Download Service
198199
services.set(IDownloadService, new SyncDescriptor(DownloadService, undefined, true));
@@ -212,7 +213,7 @@ class CliMain extends Disposable {
212213
const isInternal = isInternalTelemetry(productService, configurationService);
213214
if (supportsTelemetry(productService, environmentService)) {
214215
if (productService.aiConfig && productService.aiConfig.ariaKey) {
215-
appenders.push(new OneDataSystemAppender(isInternal, 'monacoworkbench', null, productService.aiConfig.ariaKey));
216+
appenders.push(new OneDataSystemAppender(requestService, isInternal, 'monacoworkbench', null, productService.aiConfig.ariaKey));
216217
}
217218

218219
const config: ITelemetryServiceConfig = {

src/vs/code/node/sharedProcess/sharedProcessMain.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,8 @@ class SharedProcessMain extends Disposable {
251251
services.set(IUriIdentityService, uriIdentityService);
252252

253253
// Request
254-
services.set(IRequestService, new RequestChannelClient(mainProcessService.getChannel('request')));
254+
const requestService = new RequestChannelClient(mainProcessService.getChannel('request'));
255+
services.set(IRequestService, requestService);
255256

256257
// Checksum
257258
services.set(IChecksumService, new SyncDescriptor(ChecksumService, undefined, false /* proxied to other processes */));
@@ -279,7 +280,7 @@ class SharedProcessMain extends Disposable {
279280
const logAppender = new TelemetryLogAppender(logService, loggerService, environmentService, productService);
280281
appenders.push(logAppender);
281282
if (productService.aiConfig?.ariaKey) {
282-
const collectorAppender = new OneDataSystemAppender(internalTelemetry, 'monacoworkbench', null, productService.aiConfig.ariaKey);
283+
const collectorAppender = new OneDataSystemAppender(requestService, internalTelemetry, 'monacoworkbench', null, productService.aiConfig.ariaKey);
283284
this._register(toDisposable(() => collectorAppender.flush())); // Ensure the 1DS appender is disposed so that it flushes remaining data
284285
appenders.push(collectorAppender);
285286
}

src/vs/platform/telemetry/node/1dsAppender.ts

Lines changed: 74 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,100 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import type { IPayloadData, IXHROverride } from '@microsoft/1ds-post-js';
7-
import * as https from 'https';
7+
import { streamToBuffer } from 'vs/base/common/buffer';
8+
import { CancellationToken } from 'vs/base/common/cancellation';
9+
import { IRequestOptions } from 'vs/base/parts/request/common/request';
10+
import { IRequestService } from 'vs/platform/request/common/request';
811
import { AbstractOneDataSystemAppender, IAppInsightsCore } from 'vs/platform/telemetry/common/1dsAppender';
912

13+
/**
14+
* Completes a request to submit telemetry to the server utilizing the request service
15+
* @param options The options which will be used to make the request
16+
* @param requestService The request service
17+
* @returns An object containing the headers, statusCode, and responseData
18+
*/
19+
async function makeTelemetryRequest(options: IRequestOptions, requestService: IRequestService) {
20+
const response = await requestService.request(options, CancellationToken.None);
21+
const responseData = (await streamToBuffer(response.stream)).toString();
22+
const statusCode = response.res.statusCode ?? 200;
23+
const headers = response.res.headers as Record<string, any>;
24+
return {
25+
headers,
26+
statusCode,
27+
responseData
28+
};
29+
}
30+
31+
/**
32+
* Complete a request to submit telemetry to the server utilizing the https module. Only used when the request service is not available
33+
* @param options The options which will be used to make the request
34+
* @param httpsModule The https node module
35+
* @returns An object containing the headers, statusCode, and responseData
36+
*/
37+
function makeLegacyTelemetryRequest(options: IRequestOptions, httpsModule: typeof import('https')) {
38+
const httpsOptions = {
39+
method: options.type,
40+
headers: options.headers
41+
};
42+
const req = httpsModule.request(options.url ?? '', httpsOptions, res => {
43+
res.on('data', function (responseData) {
44+
return {
45+
headers: res.headers as Record<string, any>,
46+
statusCode: res.statusCode ?? 200,
47+
responseData: responseData.toString()
48+
};
49+
});
50+
// On response with error send status of 0 and a blank response to oncomplete so we can retry events
51+
res.on('error', function (err) {
52+
throw err;
53+
});
54+
});
55+
req.write(options.data);
56+
req.end();
57+
return;
58+
}
59+
1060

1161
export class OneDataSystemAppender extends AbstractOneDataSystemAppender {
1262

1363
constructor(
64+
requestService: IRequestService | undefined,
1465
isInternalTelemetry: boolean,
1566
eventPrefix: string,
1667
defaultData: { [key: string]: any } | null,
1768
iKeyOrClientFactory: string | (() => IAppInsightsCore), // allow factory function for testing
1869
) {
70+
let httpsModule: typeof import('https') | undefined;
71+
if (!requestService) {
72+
httpsModule = require('https');
73+
}
1974
// Override the way events get sent since node doesn't have XHTMLRequest
2075
const customHttpXHROverride: IXHROverride = {
2176
sendPOST: (payload: IPayloadData, oncomplete) => {
22-
const options = {
23-
method: 'POST',
77+
78+
const telemetryRequestData = typeof payload.data === 'string' ? payload.data : new TextDecoder().decode(payload.data);
79+
const requestOptions: IRequestOptions = {
80+
type: 'POST',
2481
headers: {
2582
...payload.headers,
2683
'Content-Type': 'application/json',
27-
'Content-Length': Buffer.byteLength(payload.data)
28-
}
84+
'Content-Length': Buffer.byteLength(payload.data).toString()
85+
},
86+
url: payload.urlString,
87+
data: telemetryRequestData
2988
};
89+
3090
try {
31-
const req = https.request(payload.urlString, options, res => {
32-
res.on('data', function (responseData) {
33-
oncomplete(res.statusCode ?? 200, res.headers as Record<string, any>, responseData.toString());
91+
if (requestService) {
92+
makeTelemetryRequest(requestOptions, requestService).then(({ statusCode, headers, responseData }) => {
93+
oncomplete(statusCode, headers, responseData);
3494
});
35-
// On response with error send status of 0 and a blank response to oncomplete so we can retry events
36-
res.on('error', function (err) {
37-
oncomplete(0, {});
38-
});
39-
});
40-
req.write(payload.data);
41-
req.end();
95+
} else {
96+
if (!httpsModule) {
97+
throw new Error('https module is undefined');
98+
}
99+
makeLegacyTelemetryRequest(requestOptions, httpsModule);
100+
}
42101
} catch {
43102
// If it errors out, send status of 0 and a blank response to oncomplete so we can retry events
44103
oncomplete(0, {});

src/vs/server/node/serverServices.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,14 @@ export async function setupServerServices(connectionToken: ServerConnectionToken
144144
services.set(IExtensionHostStatusService, extensionHostStatusService);
145145

146146
// Request
147-
services.set(IRequestService, new SyncDescriptor(RequestService));
147+
const requestService = new RequestService(configurationService, environmentService, logService, loggerService);
148+
services.set(IRequestService, requestService);
148149

149150
let oneDsAppender: ITelemetryAppender = NullAppender;
150151
const isInternal = isInternalTelemetry(productService, configurationService);
151152
if (supportsTelemetry(productService, environmentService)) {
152153
if (productService.aiConfig && productService.aiConfig.ariaKey) {
153-
oneDsAppender = new OneDataSystemAppender(isInternal, eventPrefix, null, productService.aiConfig.ariaKey);
154+
oneDsAppender = new OneDataSystemAppender(requestService, isInternal, eventPrefix, null, productService.aiConfig.ariaKey);
154155
disposables.add(toDisposable(() => oneDsAppender?.flush())); // Ensure the AI appender is disposed so that it flushes remaining data
155156
}
156157

src/vs/workbench/contrib/debug/node/telemetryApp.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Server } from 'vs/base/parts/ipc/node/ipc.cp';
77
import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc';
88
import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender';
99

10-
const appender = new OneDataSystemAppender(false, process.argv[2], JSON.parse(process.argv[3]), process.argv[4]);
10+
const appender = new OneDataSystemAppender(undefined, false, process.argv[2], JSON.parse(process.argv[3]), process.argv[4]);
1111
process.once('exit', () => appender.flush());
1212

1313
const channel = new TelemetryAppenderChannel([appender]);

0 commit comments

Comments
 (0)