Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/api/test/browser/extHostTelemetry.test.ts
5240 views
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 assert from 'assert';
7
import { URI } from '../../../../base/common/uri.js';
8
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
9
import { ExtensionIdentifier, IExtensionDescription, TargetPlatform } from '../../../../platform/extensions/common/extensions.js';
10
import { DEFAULT_LOG_LEVEL, LogLevel } from '../../../../platform/log/common/log.js';
11
import { TelemetryLevel } from '../../../../platform/telemetry/common/telemetry.js';
12
import { TestTelemetryLoggerService } from '../../../../platform/telemetry/test/common/telemetryLogAppender.test.js';
13
import { IExtHostInitDataService } from '../../common/extHostInitDataService.js';
14
import { ExtHostTelemetry, ExtHostTelemetryLogger } from '../../common/extHostTelemetry.js';
15
import { IEnvironment } from '../../../services/extensions/common/extensionHostProtocol.js';
16
import { mock } from '../../../test/common/workbenchTestServices.js';
17
import type { TelemetryLoggerOptions, TelemetrySender } from 'vscode';
18
19
interface TelemetryLoggerSpy {
20
dataArr: any[];
21
exceptionArr: any[];
22
flushCalled: boolean;
23
}
24
25
suite('ExtHostTelemetry', function () {
26
const store = ensureNoDisposablesAreLeakedInTestSuite();
27
28
const mockEnvironment: IEnvironment = {
29
isExtensionDevelopmentDebug: false,
30
extensionDevelopmentLocationURI: undefined,
31
extensionTestsLocationURI: undefined,
32
appRoot: undefined,
33
appName: 'test',
34
isExtensionTelemetryLoggingOnly: false,
35
appHost: 'test',
36
appLanguage: 'en',
37
globalStorageHome: URI.parse('fake'),
38
workspaceStorageHome: URI.parse('fake'),
39
appUriScheme: 'test',
40
};
41
42
const mockTelemetryInfo = {
43
firstSessionDate: '2020-01-01T00:00:00.000Z',
44
sessionId: 'test',
45
machineId: 'test',
46
sqmId: 'test',
47
devDeviceId: 'test'
48
};
49
50
const mockRemote = {
51
authority: 'test',
52
isRemote: false,
53
connectionData: null
54
};
55
56
const mockExtensionIdentifier: IExtensionDescription = {
57
identifier: new ExtensionIdentifier('test-extension'),
58
targetPlatform: TargetPlatform.UNIVERSAL,
59
isBuiltin: true,
60
isUserBuiltin: true,
61
isUnderDevelopment: true,
62
name: 'test-extension',
63
publisher: 'vscode',
64
version: '1.0.0',
65
engines: { vscode: '*' },
66
extensionLocation: URI.parse('fake'),
67
enabledApiProposals: undefined,
68
preRelease: false,
69
};
70
71
const createExtHostTelemetry = () => {
72
const extensionTelemetry = new ExtHostTelemetry(false, new class extends mock<IExtHostInitDataService>() {
73
override environment: IEnvironment = mockEnvironment;
74
override telemetryInfo = mockTelemetryInfo;
75
override remote = mockRemote;
76
}, new TestTelemetryLoggerService(DEFAULT_LOG_LEVEL));
77
store.add(extensionTelemetry);
78
extensionTelemetry.$initializeTelemetryLevel(TelemetryLevel.USAGE, true, { usage: true, error: true });
79
return extensionTelemetry;
80
};
81
82
const createLogger = (functionSpy: TelemetryLoggerSpy, extHostTelemetry?: ExtHostTelemetry, options?: TelemetryLoggerOptions) => {
83
const extensionTelemetry = extHostTelemetry ?? createExtHostTelemetry();
84
// This is the appender which the extension would contribute
85
const appender: TelemetrySender = {
86
sendEventData: (eventName: string, data) => {
87
functionSpy.dataArr.push({ eventName, data });
88
},
89
sendErrorData: (exception, data) => {
90
functionSpy.exceptionArr.push({ exception, data });
91
},
92
flush: () => {
93
functionSpy.flushCalled = true;
94
}
95
};
96
97
if (extHostTelemetry) {
98
store.add(extHostTelemetry);
99
}
100
101
const logger = extensionTelemetry.instantiateLogger(mockExtensionIdentifier, appender, options);
102
store.add(logger);
103
return logger;
104
};
105
106
test('Validate sender instances', function () {
107
// eslint-disable-next-line local/code-no-any-casts
108
assert.throws(() => ExtHostTelemetryLogger.validateSender(<any>null));
109
// eslint-disable-next-line local/code-no-any-casts
110
assert.throws(() => ExtHostTelemetryLogger.validateSender(<any>1));
111
// eslint-disable-next-line local/code-no-any-casts
112
assert.throws(() => ExtHostTelemetryLogger.validateSender(<any>{}));
113
assert.throws(() => {
114
// eslint-disable-next-line local/code-no-any-casts
115
ExtHostTelemetryLogger.validateSender(<any>{
116
sendErrorData: () => { },
117
sendEventData: true
118
});
119
});
120
assert.throws(() => {
121
// eslint-disable-next-line local/code-no-any-casts
122
ExtHostTelemetryLogger.validateSender(<any>{
123
sendErrorData: 123,
124
sendEventData: () => { },
125
});
126
});
127
assert.throws(() => {
128
// eslint-disable-next-line local/code-no-any-casts
129
ExtHostTelemetryLogger.validateSender(<any>{
130
sendErrorData: () => { },
131
sendEventData: () => { },
132
flush: true
133
});
134
});
135
});
136
137
test('Ensure logger gets proper telemetry level during initialization', function () {
138
const extensionTelemetry = createExtHostTelemetry();
139
let config = extensionTelemetry.getTelemetryDetails();
140
assert.strictEqual(config.isCrashEnabled, true);
141
assert.strictEqual(config.isUsageEnabled, true);
142
assert.strictEqual(config.isErrorsEnabled, true);
143
144
// Initialize would never be called twice, but this is just for testing
145
extensionTelemetry.$initializeTelemetryLevel(TelemetryLevel.ERROR, true, { usage: true, error: true });
146
config = extensionTelemetry.getTelemetryDetails();
147
assert.strictEqual(config.isCrashEnabled, true);
148
assert.strictEqual(config.isUsageEnabled, false);
149
assert.strictEqual(config.isErrorsEnabled, true);
150
151
extensionTelemetry.$initializeTelemetryLevel(TelemetryLevel.CRASH, true, { usage: true, error: true });
152
config = extensionTelemetry.getTelemetryDetails();
153
assert.strictEqual(config.isCrashEnabled, true);
154
assert.strictEqual(config.isUsageEnabled, false);
155
assert.strictEqual(config.isErrorsEnabled, false);
156
157
extensionTelemetry.$initializeTelemetryLevel(TelemetryLevel.USAGE, true, { usage: false, error: true });
158
config = extensionTelemetry.getTelemetryDetails();
159
assert.strictEqual(config.isCrashEnabled, true);
160
assert.strictEqual(config.isUsageEnabled, false);
161
assert.strictEqual(config.isErrorsEnabled, true);
162
extensionTelemetry.dispose();
163
});
164
165
test('Simple log event to TelemetryLogger', function () {
166
const functionSpy: TelemetryLoggerSpy = { dataArr: [], exceptionArr: [], flushCalled: false };
167
168
const logger = createLogger(functionSpy);
169
170
logger.logUsage('test-event', { 'test-data': 'test-data' });
171
assert.strictEqual(functionSpy.dataArr.length, 1);
172
assert.strictEqual(functionSpy.dataArr[0].eventName, `${mockExtensionIdentifier.name}/test-event`);
173
assert.strictEqual(functionSpy.dataArr[0].data['test-data'], 'test-data');
174
175
logger.logUsage('test-event', { 'test-data': 'test-data' });
176
assert.strictEqual(functionSpy.dataArr.length, 2);
177
178
logger.logError('test-event', { 'test-data': 'test-data' });
179
assert.strictEqual(functionSpy.dataArr.length, 3);
180
181
logger.logError(new Error('test-error'), { 'test-data': 'test-data' });
182
assert.strictEqual(functionSpy.dataArr.length, 3);
183
assert.strictEqual(functionSpy.exceptionArr.length, 1);
184
185
186
// Assert not flushed
187
assert.strictEqual(functionSpy.flushCalled, false);
188
189
// Call flush and assert that flush occurs
190
logger.dispose();
191
assert.strictEqual(functionSpy.flushCalled, true);
192
193
});
194
195
test('Simple log event to TelemetryLogger with options', function () {
196
const functionSpy: TelemetryLoggerSpy = { dataArr: [], exceptionArr: [], flushCalled: false };
197
198
const logger = createLogger(functionSpy, undefined, { additionalCommonProperties: { 'common.foo': 'bar' } });
199
200
logger.logUsage('test-event', { 'test-data': 'test-data' });
201
assert.strictEqual(functionSpy.dataArr.length, 1);
202
assert.strictEqual(functionSpy.dataArr[0].eventName, `${mockExtensionIdentifier.name}/test-event`);
203
assert.strictEqual(functionSpy.dataArr[0].data['test-data'], 'test-data');
204
assert.strictEqual(functionSpy.dataArr[0].data['common.foo'], 'bar');
205
206
logger.logUsage('test-event', { 'test-data': 'test-data' });
207
assert.strictEqual(functionSpy.dataArr.length, 2);
208
209
logger.logError('test-event', { 'test-data': 'test-data' });
210
assert.strictEqual(functionSpy.dataArr.length, 3);
211
212
logger.logError(new Error('test-error'), { 'test-data': 'test-data' });
213
assert.strictEqual(functionSpy.dataArr.length, 3);
214
assert.strictEqual(functionSpy.exceptionArr.length, 1);
215
216
217
// Assert not flushed
218
assert.strictEqual(functionSpy.flushCalled, false);
219
220
// Call flush and assert that flush occurs
221
logger.dispose();
222
assert.strictEqual(functionSpy.flushCalled, true);
223
224
});
225
226
test('Log error should get common properties #193205', function () {
227
const functionSpy: TelemetryLoggerSpy = { dataArr: [], exceptionArr: [], flushCalled: false };
228
229
const logger = createLogger(functionSpy, undefined, { additionalCommonProperties: { 'common.foo': 'bar' } });
230
logger.logError(new Error('Test error'));
231
assert.strictEqual(functionSpy.exceptionArr.length, 1);
232
assert.strictEqual(functionSpy.exceptionArr[0].data['common.foo'], 'bar');
233
assert.strictEqual(functionSpy.exceptionArr[0].data['common.product'], 'test');
234
235
logger.logError('test-error-event');
236
assert.strictEqual(functionSpy.dataArr.length, 1);
237
assert.strictEqual(functionSpy.dataArr[0].data['common.foo'], 'bar');
238
assert.strictEqual(functionSpy.dataArr[0].data['common.product'], 'test');
239
240
logger.logError('test-error-event', { 'test-data': 'test-data' });
241
assert.strictEqual(functionSpy.dataArr.length, 2);
242
assert.strictEqual(functionSpy.dataArr[1].data['common.foo'], 'bar');
243
assert.strictEqual(functionSpy.dataArr[1].data['common.product'], 'test');
244
245
logger.logError('test-error-event', { properties: { 'test-data': 'test-data' } });
246
assert.strictEqual(functionSpy.dataArr.length, 3);
247
assert.strictEqual(functionSpy.dataArr[2].data.properties['common.foo'], 'bar');
248
assert.strictEqual(functionSpy.dataArr[2].data.properties['common.product'], 'test');
249
250
logger.dispose();
251
assert.strictEqual(functionSpy.flushCalled, true);
252
});
253
254
255
test('Ensure logger properly cleans PII', function () {
256
const functionSpy: TelemetryLoggerSpy = { dataArr: [], exceptionArr: [], flushCalled: false };
257
258
const logger = createLogger(functionSpy);
259
260
// Log an event with a bunch of PII, this should all get cleaned out
261
logger.logUsage('test-event', {
262
'fake-password': 'pwd=123',
263
'fake-email': '[email protected]',
264
'fake-token': 'token=123',
265
'fake-slack-token': 'xoxp-123',
266
'fake-path': '/Users/username/.vscode/extensions',
267
});
268
269
assert.strictEqual(functionSpy.dataArr.length, 1);
270
assert.strictEqual(functionSpy.dataArr[0].eventName, `${mockExtensionIdentifier.name}/test-event`);
271
assert.strictEqual(functionSpy.dataArr[0].data['fake-password'], '<REDACTED: Generic Secret>');
272
assert.strictEqual(functionSpy.dataArr[0].data['fake-email'], '<REDACTED: Email>');
273
assert.strictEqual(functionSpy.dataArr[0].data['fake-token'], '<REDACTED: Generic Secret>');
274
assert.strictEqual(functionSpy.dataArr[0].data['fake-slack-token'], '<REDACTED: Slack Token>');
275
assert.strictEqual(functionSpy.dataArr[0].data['fake-path'], '<REDACTED: user-file-path>');
276
});
277
278
test('Ensure output channel is logged to', function () {
279
280
// Have to re-duplicate code here because I the logger service isn't exposed in the simple setup functions
281
const loggerService = new TestTelemetryLoggerService(LogLevel.Trace);
282
const extensionTelemetry = new ExtHostTelemetry(false, new class extends mock<IExtHostInitDataService>() {
283
override environment: IEnvironment = mockEnvironment;
284
override telemetryInfo = mockTelemetryInfo;
285
override remote = mockRemote;
286
}, loggerService);
287
extensionTelemetry.$initializeTelemetryLevel(TelemetryLevel.USAGE, true, { usage: true, error: true });
288
289
const functionSpy: TelemetryLoggerSpy = { dataArr: [], exceptionArr: [], flushCalled: false };
290
291
const logger = createLogger(functionSpy, extensionTelemetry);
292
293
// Ensure headers are logged on instantiation
294
assert.strictEqual(loggerService.createLogger().logs.length, 0);
295
296
logger.logUsage('test-event', { 'test-data': 'test-data' });
297
// Initial header is logged then the event
298
assert.strictEqual(loggerService.createLogger().logs.length, 1);
299
assert.ok(loggerService.createLogger().logs[0].startsWith('test-extension/test-event'));
300
});
301
});
302
303