Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support proxies in the telemetry service #187952

Merged
merged 1 commit into from
Jul 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/vs/code/node/cliProcessMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,8 @@ class CliMain extends Disposable {
services.set(IUriIdentityService, new UriIdentityService(fileService));

// Request
services.set(IRequestService, new SyncDescriptor(RequestService, undefined, true));
const requestService = new RequestService(configurationService, environmentService, logService, loggerService);
services.set(IRequestService, requestService);

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

const config: ITelemetryServiceConfig = {
Expand Down
5 changes: 3 additions & 2 deletions src/vs/code/node/sharedProcess/sharedProcessMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,8 @@ class SharedProcessMain extends Disposable {
services.set(IUriIdentityService, uriIdentityService);

// Request
services.set(IRequestService, new RequestChannelClient(mainProcessService.getChannel('request')));
const requestService = new RequestChannelClient(mainProcessService.getChannel('request'));
services.set(IRequestService, requestService);

// Checksum
services.set(IChecksumService, new SyncDescriptor(ChecksumService, undefined, false /* proxied to other processes */));
Expand Down Expand Up @@ -279,7 +280,7 @@ class SharedProcessMain extends Disposable {
const logAppender = new TelemetryLogAppender(logService, loggerService, environmentService, productService);
appenders.push(logAppender);
if (productService.aiConfig?.ariaKey) {
const collectorAppender = new OneDataSystemAppender(internalTelemetry, 'monacoworkbench', null, productService.aiConfig.ariaKey);
const collectorAppender = new OneDataSystemAppender(requestService, internalTelemetry, 'monacoworkbench', null, productService.aiConfig.ariaKey);
this._register(toDisposable(() => collectorAppender.flush())); // Ensure the 1DS appender is disposed so that it flushes remaining data
appenders.push(collectorAppender);
}
Expand Down
89 changes: 74 additions & 15 deletions src/vs/platform/telemetry/node/1dsAppender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,100 @@
*--------------------------------------------------------------------------------------------*/

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

/**
* Completes a request to submit telemetry to the server utilizing the request service
* @param options The options which will be used to make the request
* @param requestService The request service
* @returns An object containing the headers, statusCode, and responseData
*/
async function makeTelemetryRequest(options: IRequestOptions, requestService: IRequestService) {
const response = await requestService.request(options, CancellationToken.None);
const responseData = (await streamToBuffer(response.stream)).toString();
const statusCode = response.res.statusCode ?? 200;
const headers = response.res.headers as Record<string, any>;
return {
headers,
statusCode,
responseData
};
}

/**
* Complete a request to submit telemetry to the server utilizing the https module. Only used when the request service is not available
* @param options The options which will be used to make the request
* @param httpsModule The https node module
* @returns An object containing the headers, statusCode, and responseData
*/
function makeLegacyTelemetryRequest(options: IRequestOptions, httpsModule: typeof import('https')) {
const httpsOptions = {
method: options.type,
headers: options.headers
};
const req = httpsModule.request(options.url ?? '', httpsOptions, res => {
res.on('data', function (responseData) {
return {
headers: res.headers as Record<string, any>,
statusCode: res.statusCode ?? 200,
responseData: responseData.toString()
};
});
// On response with error send status of 0 and a blank response to oncomplete so we can retry events
res.on('error', function (err) {
throw err;
});
});
req.write(options.data);
req.end();
return;
}


export class OneDataSystemAppender extends AbstractOneDataSystemAppender {

constructor(
requestService: IRequestService | undefined,
isInternalTelemetry: boolean,
eventPrefix: string,
defaultData: { [key: string]: any } | null,
iKeyOrClientFactory: string | (() => IAppInsightsCore), // allow factory function for testing
) {
let httpsModule: typeof import('https') | undefined;
if (!requestService) {
httpsModule = require('https');
}
// Override the way events get sent since node doesn't have XHTMLRequest
const customHttpXHROverride: IXHROverride = {
sendPOST: (payload: IPayloadData, oncomplete) => {
const options = {
method: 'POST',

const telemetryRequestData = typeof payload.data === 'string' ? payload.data : new TextDecoder().decode(payload.data);
const requestOptions: IRequestOptions = {
type: 'POST',
headers: {
...payload.headers,
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(payload.data)
}
'Content-Length': Buffer.byteLength(payload.data).toString()
},
url: payload.urlString,
data: telemetryRequestData
};

try {
const req = https.request(payload.urlString, options, res => {
res.on('data', function (responseData) {
oncomplete(res.statusCode ?? 200, res.headers as Record<string, any>, responseData.toString());
if (requestService) {
makeTelemetryRequest(requestOptions, requestService).then(({ statusCode, headers, responseData }) => {
oncomplete(statusCode, headers, responseData);
});
// On response with error send status of 0 and a blank response to oncomplete so we can retry events
res.on('error', function (err) {
oncomplete(0, {});
});
});
req.write(payload.data);
req.end();
} else {
if (!httpsModule) {
throw new Error('https module is undefined');
}
makeLegacyTelemetryRequest(requestOptions, httpsModule);
}
} catch {
// If it errors out, send status of 0 and a blank response to oncomplete so we can retry events
oncomplete(0, {});
Expand Down
5 changes: 3 additions & 2 deletions src/vs/server/node/serverServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,14 @@ export async function setupServerServices(connectionToken: ServerConnectionToken
services.set(IExtensionHostStatusService, extensionHostStatusService);

// Request
services.set(IRequestService, new SyncDescriptor(RequestService));
const requestService = new RequestService(configurationService, environmentService, logService, loggerService);
services.set(IRequestService, requestService);

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

Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/contrib/debug/node/telemetryApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Server } from 'vs/base/parts/ipc/node/ipc.cp';
import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc';
import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender';

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

const channel = new TelemetryAppenderChannel([appender]);
Expand Down