Skip to content

Commit 2d32e40

Browse files
authored
Refactor telemetry appenders to reduce duplicated code (#152877)
* Reactor telemetry appenders to reduce duplicated code * Update tsconfig * Do not use Object.create(null) because we need object.hasOwnProperty
1 parent 07144d2 commit 2d32e40

File tree

6 files changed

+158
-235
lines changed

6 files changed

+158
-235
lines changed

src/tsconfig.monaco.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"node_modules/*",
3131
"vs/platform/files/browser/htmlFileSystemProvider.ts",
3232
"vs/platform/files/browser/webFileSystemAccess.ts",
33-
"vs/platform/telemetry/browser/*",
33+
"vs/platform/telemetry/*",
3434
"vs/platform/assignment/*"
3535
]
3636
}

src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/u
104104
import { ExtensionsProfileScannerService, IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
105105
import { PolicyChannelClient } from 'vs/platform/policy/common/policyIpc';
106106
import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy';
107-
import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender';
107+
import { OneDataSystemWebAppender } from 'vs/platform/telemetry/browser/1dsAppender';
108108

109109
class SharedProcessMain extends Disposable {
110110

@@ -282,7 +282,7 @@ class SharedProcessMain extends Disposable {
282282
const { installSourcePath } = environmentService;
283283
const internalTesting = configurationService.getValue<boolean>('telemetry.internalTesting');
284284
if (internalTesting && productService.aiConfig?.ariaKey) {
285-
const collectorAppender = new OneDataSystemAppender('monacoworkbench', null, productService.aiConfig.ariaKey);
285+
const collectorAppender = new OneDataSystemWebAppender('monacoworkbench', null, productService.aiConfig.ariaKey);
286286
this._register(toDisposable(() => collectorAppender.flush())); // Ensure the 1DS appender is disposed so that it flushes remaining data
287287
appenders.push(collectorAppender);
288288
} else if (productService.aiConfig && productService.aiConfig.asimovKey) {

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

Lines changed: 7 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -3,125 +3,22 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import type { AppInsightsCore, IExtendedConfiguration } from '@microsoft/1ds-core-js';
7-
import type { PostChannel } from '@microsoft/1ds-post-js';
8-
import { onUnexpectedError } from 'vs/base/common/errors';
9-
import { mixin } from 'vs/base/common/objects';
10-
import { ITelemetryAppender, validateTelemetryData } from 'vs/platform/telemetry/common/telemetryUtils';
6+
import type { AppInsightsCore } from '@microsoft/1ds-core-js';
7+
import { AbstractOneDataSystemAppender } from 'vs/platform/telemetry/common/1dsAppender';
118

12-
const endpointUrl = 'https://mobile.events.data.microsoft.com/OneCollector/1.0';
13-
14-
async function getClient(instrumentationKey: string): Promise<AppInsightsCore> {
15-
const oneDs = await import('@microsoft/1ds-core-js');
16-
const postPlugin = await import('@microsoft/1ds-post-js');
17-
const appInsightsCore = new oneDs.AppInsightsCore();
18-
const collectorChannelPlugin: PostChannel = new postPlugin.PostChannel();
19-
// Configure the app insights core to send to collector++ and disable logging of debug info
20-
const coreConfig: IExtendedConfiguration = {
21-
instrumentationKey,
22-
endpointUrl,
23-
loggingLevelTelemetry: 0,
24-
loggingLevelConsole: 0,
25-
disableCookiesUsage: true,
26-
disableDbgExt: true,
27-
disableInstrumentationKeyValidation: true,
28-
channels: [[
29-
collectorChannelPlugin
30-
]]
31-
};
32-
33-
appInsightsCore.initialize(coreConfig, []);
34-
35-
appInsightsCore.addTelemetryInitializer((envelope) => {
36-
envelope['ext'] = envelope['ext'] ?? {};
37-
envelope['ext']['utc'] = envelope['ext']['utc'] ?? {};
38-
// Sets it to be internal only based on Windows UTC flagging
39-
envelope['ext']['utc']['flags'] = 0x0000811ECD;
40-
});
41-
42-
return appInsightsCore;
43-
}
44-
45-
// TODO @lramos15 maybe make more in line with src/vs/platform/telemetry/browser/appInsightsAppender.ts with caching support
46-
export class OneDataSystemWebAppender implements ITelemetryAppender {
47-
48-
private _aiCoreOrKey: AppInsightsCore | string | undefined;
49-
private _asyncAiCore: Promise<AppInsightsCore> | null;
509

10+
export class OneDataSystemWebAppender extends AbstractOneDataSystemAppender {
5111
constructor(
52-
private _eventPrefix: string,
53-
private _defaultData: { [key: string]: any } | null,
12+
eventPrefix: string,
13+
defaultData: { [key: string]: any } | null,
5414
iKeyOrClientFactory: string | (() => AppInsightsCore), // allow factory function for testing
5515
) {
56-
if (!this._defaultData) {
57-
this._defaultData = Object.create(null);
58-
}
59-
60-
if (typeof iKeyOrClientFactory === 'function') {
61-
this._aiCoreOrKey = iKeyOrClientFactory();
62-
} else {
63-
this._aiCoreOrKey = iKeyOrClientFactory;
64-
}
65-
this._asyncAiCore = null;
16+
super(eventPrefix, defaultData, iKeyOrClientFactory);
6617

6718
// If we cannot fetch the endpoint it means it is down and we should not send any telemetry.
6819
// This is most likely due to ad blockers
69-
fetch(endpointUrl, { method: 'POST' }).catch(err => {
20+
fetch(this.endPointUrl, { method: 'POST' }).catch(err => {
7021
this._aiCoreOrKey = undefined;
7122
});
7223
}
73-
74-
private _withAIClient(callback: (aiCore: AppInsightsCore) => void): void {
75-
if (!this._aiCoreOrKey) {
76-
return;
77-
}
78-
79-
if (typeof this._aiCoreOrKey !== 'string') {
80-
callback(this._aiCoreOrKey);
81-
return;
82-
}
83-
84-
if (!this._asyncAiCore) {
85-
this._asyncAiCore = getClient(this._aiCoreOrKey);
86-
}
87-
88-
this._asyncAiCore.then(
89-
(aiClient) => {
90-
callback(aiClient);
91-
},
92-
(err) => {
93-
onUnexpectedError(err);
94-
console.error(err);
95-
}
96-
);
97-
}
98-
99-
log(eventName: string, data?: any): void {
100-
if (!this._aiCoreOrKey) {
101-
return;
102-
}
103-
data = mixin(data, this._defaultData);
104-
data = validateTelemetryData(data);
105-
106-
try {
107-
this._withAIClient((aiClient) => aiClient.track({
108-
name: this._eventPrefix + '/' + eventName,
109-
data: { ...data.properties, ...data.measurements },
110-
}));
111-
} catch { }
112-
}
113-
114-
flush(): Promise<any> {
115-
if (this._aiCoreOrKey) {
116-
return new Promise(resolve => {
117-
this._withAIClient((aiClient) => {
118-
aiClient.unload(true, () => {
119-
this._aiCoreOrKey = undefined;
120-
resolve(undefined);
121-
});
122-
});
123-
});
124-
}
125-
return Promise.resolve(undefined);
126-
}
12724
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import type { AppInsightsCore, IExtendedConfiguration } from '@microsoft/1ds-core-js';
7+
import type { IChannelConfiguration, IXHROverride, PostChannel } from '@microsoft/1ds-post-js';
8+
import { onUnexpectedError } from 'vs/base/common/errors';
9+
import { mixin } from 'vs/base/common/objects';
10+
import { ITelemetryAppender, validateTelemetryData } from 'vs/platform/telemetry/common/telemetryUtils';
11+
12+
const endpointUrl = 'https://mobile.events.data.microsoft.com/OneCollector/1.0';
13+
14+
async function getClient(instrumentationKey: string, xhrOverride?: IXHROverride): Promise<AppInsightsCore> {
15+
const oneDs = await import('@microsoft/1ds-core-js');
16+
const postPlugin = await import('@microsoft/1ds-post-js');
17+
const appInsightsCore = new oneDs.AppInsightsCore();
18+
const collectorChannelPlugin: PostChannel = new postPlugin.PostChannel();
19+
// Configure the app insights core to send to collector++ and disable logging of debug info
20+
const coreConfig: IExtendedConfiguration = {
21+
instrumentationKey,
22+
endpointUrl,
23+
loggingLevelTelemetry: 0,
24+
loggingLevelConsole: 0,
25+
disableCookiesUsage: true,
26+
disableDbgExt: true,
27+
disableInstrumentationKeyValidation: true,
28+
channels: [[
29+
collectorChannelPlugin
30+
]]
31+
};
32+
33+
if (xhrOverride) {
34+
coreConfig.extensionConfig = {};
35+
// Configure the channel to use a XHR Request override since it's not available in node
36+
const channelConfig: IChannelConfiguration = {
37+
alwaysUseXhrOverride: true,
38+
httpXHROverride: xhrOverride
39+
};
40+
coreConfig.extensionConfig[collectorChannelPlugin.identifier] = channelConfig;
41+
}
42+
43+
appInsightsCore.initialize(coreConfig, []);
44+
45+
appInsightsCore.addTelemetryInitializer((envelope) => {
46+
envelope['ext'] = envelope['ext'] ?? {};
47+
envelope['ext']['utc'] = envelope['ext']['utc'] ?? {};
48+
// Sets it to be internal only based on Windows UTC flagging
49+
envelope['ext']['utc']['flags'] = 0x0000811ECD;
50+
});
51+
52+
return appInsightsCore;
53+
}
54+
55+
// TODO @lramos15 maybe make more in line with src/vs/platform/telemetry/browser/appInsightsAppender.ts with caching support
56+
export abstract class AbstractOneDataSystemAppender implements ITelemetryAppender {
57+
58+
protected _aiCoreOrKey: AppInsightsCore | string | undefined;
59+
private _asyncAiCore: Promise<AppInsightsCore> | null;
60+
protected readonly endPointUrl = endpointUrl;
61+
62+
constructor(
63+
private _eventPrefix: string,
64+
private _defaultData: { [key: string]: any } | null,
65+
iKeyOrClientFactory: string | (() => AppInsightsCore), // allow factory function for testing
66+
private _xhrOverride?: IXHROverride
67+
) {
68+
if (!this._defaultData) {
69+
this._defaultData = {};
70+
}
71+
72+
if (typeof iKeyOrClientFactory === 'function') {
73+
this._aiCoreOrKey = iKeyOrClientFactory();
74+
} else {
75+
this._aiCoreOrKey = iKeyOrClientFactory;
76+
}
77+
this._asyncAiCore = null;
78+
}
79+
80+
private _withAIClient(callback: (aiCore: AppInsightsCore) => void): void {
81+
if (!this._aiCoreOrKey) {
82+
return;
83+
}
84+
85+
if (typeof this._aiCoreOrKey !== 'string') {
86+
callback(this._aiCoreOrKey);
87+
return;
88+
}
89+
90+
if (!this._asyncAiCore) {
91+
this._asyncAiCore = getClient(this._aiCoreOrKey, this._xhrOverride);
92+
}
93+
94+
this._asyncAiCore.then(
95+
(aiClient) => {
96+
callback(aiClient);
97+
},
98+
(err) => {
99+
onUnexpectedError(err);
100+
console.error(err);
101+
}
102+
);
103+
}
104+
105+
log(eventName: string, data?: any): void {
106+
if (!this._aiCoreOrKey) {
107+
return;
108+
}
109+
data = mixin(data, this._defaultData);
110+
data = validateTelemetryData(data);
111+
112+
try {
113+
this._withAIClient((aiClient) => aiClient.track({
114+
name: this._eventPrefix + '/' + eventName,
115+
data,
116+
}));
117+
} catch { }
118+
}
119+
120+
flush(): Promise<any> {
121+
if (this._aiCoreOrKey) {
122+
return new Promise(resolve => {
123+
this._withAIClient((aiClient) => {
124+
aiClient.unload(true, () => {
125+
this._aiCoreOrKey = undefined;
126+
resolve(undefined);
127+
});
128+
});
129+
});
130+
}
131+
return Promise.resolve(undefined);
132+
}
133+
}

src/vs/platform/telemetry/common/telemetryUtils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,10 @@ export interface Measurements {
154154

155155
export function validateTelemetryData(data?: any): { properties: Properties; measurements: Measurements } {
156156

157-
const properties: Properties = Object.create(null);
158-
const measurements: Measurements = Object.create(null);
157+
const properties: Properties = {};
158+
const measurements: Measurements = {};
159159

160-
const flat = Object.create(null);
160+
const flat: Record<string, any> = {};
161161
flatten(data, flat);
162162

163163
for (let prop in flat) {

0 commit comments

Comments
 (0)