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
3296 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
assert.throws(() => ExtHostTelemetryLogger.validateSender(<any>null));
108
assert.throws(() => ExtHostTelemetryLogger.validateSender(<any>1));
109
assert.throws(() => ExtHostTelemetryLogger.validateSender(<any>{}));
110
assert.throws(() => {
111
ExtHostTelemetryLogger.validateSender(<any>{
112
sendErrorData: () => { },
113
sendEventData: true
114
});
115
});
116
assert.throws(() => {
117
ExtHostTelemetryLogger.validateSender(<any>{
118
sendErrorData: 123,
119
sendEventData: () => { },
120
});
121
});
122
assert.throws(() => {
123
ExtHostTelemetryLogger.validateSender(<any>{
124
sendErrorData: () => { },
125
sendEventData: () => { },
126
flush: true
127
});
128
});
129
});
130
131
test('Ensure logger gets proper telemetry level during initialization', function () {
132
const extensionTelemetry = createExtHostTelemetry();
133
let config = extensionTelemetry.getTelemetryDetails();
134
assert.strictEqual(config.isCrashEnabled, true);
135
assert.strictEqual(config.isUsageEnabled, true);
136
assert.strictEqual(config.isErrorsEnabled, true);
137
138
// Initialize would never be called twice, but this is just for testing
139
extensionTelemetry.$initializeTelemetryLevel(TelemetryLevel.ERROR, true, { usage: true, error: true });
140
config = extensionTelemetry.getTelemetryDetails();
141
assert.strictEqual(config.isCrashEnabled, true);
142
assert.strictEqual(config.isUsageEnabled, false);
143
assert.strictEqual(config.isErrorsEnabled, true);
144
145
extensionTelemetry.$initializeTelemetryLevel(TelemetryLevel.CRASH, 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, false);
150
151
extensionTelemetry.$initializeTelemetryLevel(TelemetryLevel.USAGE, true, { usage: false, error: true });
152
config = extensionTelemetry.getTelemetryDetails();
153
assert.strictEqual(config.isCrashEnabled, true);
154
assert.strictEqual(config.isUsageEnabled, false);
155
assert.strictEqual(config.isErrorsEnabled, true);
156
extensionTelemetry.dispose();
157
});
158
159
test('Simple log event to TelemetryLogger', function () {
160
const functionSpy: TelemetryLoggerSpy = { dataArr: [], exceptionArr: [], flushCalled: false };
161
162
const logger = createLogger(functionSpy);
163
164
logger.logUsage('test-event', { 'test-data': 'test-data' });
165
assert.strictEqual(functionSpy.dataArr.length, 1);
166
assert.strictEqual(functionSpy.dataArr[0].eventName, `${mockExtensionIdentifier.name}/test-event`);
167
assert.strictEqual(functionSpy.dataArr[0].data['test-data'], 'test-data');
168
169
logger.logUsage('test-event', { 'test-data': 'test-data' });
170
assert.strictEqual(functionSpy.dataArr.length, 2);
171
172
logger.logError('test-event', { 'test-data': 'test-data' });
173
assert.strictEqual(functionSpy.dataArr.length, 3);
174
175
logger.logError(new Error('test-error'), { 'test-data': 'test-data' });
176
assert.strictEqual(functionSpy.dataArr.length, 3);
177
assert.strictEqual(functionSpy.exceptionArr.length, 1);
178
179
180
// Assert not flushed
181
assert.strictEqual(functionSpy.flushCalled, false);
182
183
// Call flush and assert that flush occurs
184
logger.dispose();
185
assert.strictEqual(functionSpy.flushCalled, true);
186
187
});
188
189
test('Simple log event to TelemetryLogger with options', function () {
190
const functionSpy: TelemetryLoggerSpy = { dataArr: [], exceptionArr: [], flushCalled: false };
191
192
const logger = createLogger(functionSpy, undefined, { additionalCommonProperties: { 'common.foo': 'bar' } });
193
194
logger.logUsage('test-event', { 'test-data': 'test-data' });
195
assert.strictEqual(functionSpy.dataArr.length, 1);
196
assert.strictEqual(functionSpy.dataArr[0].eventName, `${mockExtensionIdentifier.name}/test-event`);
197
assert.strictEqual(functionSpy.dataArr[0].data['test-data'], 'test-data');
198
assert.strictEqual(functionSpy.dataArr[0].data['common.foo'], 'bar');
199
200
logger.logUsage('test-event', { 'test-data': 'test-data' });
201
assert.strictEqual(functionSpy.dataArr.length, 2);
202
203
logger.logError('test-event', { 'test-data': 'test-data' });
204
assert.strictEqual(functionSpy.dataArr.length, 3);
205
206
logger.logError(new Error('test-error'), { 'test-data': 'test-data' });
207
assert.strictEqual(functionSpy.dataArr.length, 3);
208
assert.strictEqual(functionSpy.exceptionArr.length, 1);
209
210
211
// Assert not flushed
212
assert.strictEqual(functionSpy.flushCalled, false);
213
214
// Call flush and assert that flush occurs
215
logger.dispose();
216
assert.strictEqual(functionSpy.flushCalled, true);
217
218
});
219
220
test('Log error should get common properties #193205', function () {
221
const functionSpy: TelemetryLoggerSpy = { dataArr: [], exceptionArr: [], flushCalled: false };
222
223
const logger = createLogger(functionSpy, undefined, { additionalCommonProperties: { 'common.foo': 'bar' } });
224
logger.logError(new Error('Test error'));
225
assert.strictEqual(functionSpy.exceptionArr.length, 1);
226
assert.strictEqual(functionSpy.exceptionArr[0].data['common.foo'], 'bar');
227
assert.strictEqual(functionSpy.exceptionArr[0].data['common.product'], 'test');
228
229
logger.logError('test-error-event');
230
assert.strictEqual(functionSpy.dataArr.length, 1);
231
assert.strictEqual(functionSpy.dataArr[0].data['common.foo'], 'bar');
232
assert.strictEqual(functionSpy.dataArr[0].data['common.product'], 'test');
233
234
logger.logError('test-error-event', { 'test-data': 'test-data' });
235
assert.strictEqual(functionSpy.dataArr.length, 2);
236
assert.strictEqual(functionSpy.dataArr[1].data['common.foo'], 'bar');
237
assert.strictEqual(functionSpy.dataArr[1].data['common.product'], 'test');
238
239
logger.logError('test-error-event', { properties: { 'test-data': 'test-data' } });
240
assert.strictEqual(functionSpy.dataArr.length, 3);
241
assert.strictEqual(functionSpy.dataArr[2].data.properties['common.foo'], 'bar');
242
assert.strictEqual(functionSpy.dataArr[2].data.properties['common.product'], 'test');
243
244
logger.dispose();
245
assert.strictEqual(functionSpy.flushCalled, true);
246
});
247
248
249
test('Ensure logger properly cleans PII', function () {
250
const functionSpy: TelemetryLoggerSpy = { dataArr: [], exceptionArr: [], flushCalled: false };
251
252
const logger = createLogger(functionSpy);
253
254
// Log an event with a bunch of PII, this should all get cleaned out
255
logger.logUsage('test-event', {
256
'fake-password': 'pwd=123',
257
'fake-email': '[email protected]',
258
'fake-token': 'token=123',
259
'fake-slack-token': 'xoxp-123',
260
'fake-path': '/Users/username/.vscode/extensions',
261
});
262
263
assert.strictEqual(functionSpy.dataArr.length, 1);
264
assert.strictEqual(functionSpy.dataArr[0].eventName, `${mockExtensionIdentifier.name}/test-event`);
265
assert.strictEqual(functionSpy.dataArr[0].data['fake-password'], '<REDACTED: Generic Secret>');
266
assert.strictEqual(functionSpy.dataArr[0].data['fake-email'], '<REDACTED: Email>');
267
assert.strictEqual(functionSpy.dataArr[0].data['fake-token'], '<REDACTED: Generic Secret>');
268
assert.strictEqual(functionSpy.dataArr[0].data['fake-slack-token'], '<REDACTED: Slack Token>');
269
assert.strictEqual(functionSpy.dataArr[0].data['fake-path'], '<REDACTED: user-file-path>');
270
});
271
272
test('Ensure output channel is logged to', function () {
273
274
// Have to re-duplicate code here because I the logger service isn't exposed in the simple setup functions
275
const loggerService = new TestTelemetryLoggerService(LogLevel.Trace);
276
const extensionTelemetry = new ExtHostTelemetry(false, new class extends mock<IExtHostInitDataService>() {
277
override environment: IEnvironment = mockEnvironment;
278
override telemetryInfo = mockTelemetryInfo;
279
override remote = mockRemote;
280
}, loggerService);
281
extensionTelemetry.$initializeTelemetryLevel(TelemetryLevel.USAGE, true, { usage: true, error: true });
282
283
const functionSpy: TelemetryLoggerSpy = { dataArr: [], exceptionArr: [], flushCalled: false };
284
285
const logger = createLogger(functionSpy, extensionTelemetry);
286
287
// Ensure headers are logged on instantiation
288
assert.strictEqual(loggerService.createLogger().logs.length, 0);
289
290
logger.logUsage('test-event', { 'test-data': 'test-data' });
291
// Initial header is logged then the event
292
assert.strictEqual(loggerService.createLogger().logs.length, 1);
293
assert.ok(loggerService.createLogger().logs[0].startsWith('test-extension/test-event'));
294
});
295
});
296
297