Skip to content

Commit 26d0bbd

Browse files
authored
Initial Strict Typing Support for Telemetry Events (microsoft#75785)
* Added command line information to display details about collected telemetry * Telemetry tooling exploration * Changing telemetry calls to be strongly typed * Fixed an event definition * Removed telemetry command * More removing of telemetry command * Fixed compilation errors * Forgotten property * Updated typings so diff was aligned
1 parent 71bd9c6 commit 26d0bbd

25 files changed

Lines changed: 199 additions & 256 deletions

File tree

src/vs/base/common/actions.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ export interface ITelemetryData {
1212
[key: string]: any;
1313
}
1414

15+
export type WBActionExecutedClassification = {
16+
id: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
17+
from: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
18+
};
19+
20+
export type WBActionExecutedEvent = {
21+
id: string;
22+
from: string;
23+
};
24+
1525
export interface IAction extends IDisposable {
1626
id: string;
1727
label: string;

src/vs/code/electron-browser/issue/issueReporterMain.ts

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -676,12 +676,14 @@ export class IssueReporter extends Disposable {
676676

677677
private logSearchError(error: Error) {
678678
this.logService.warn('issueReporter#search ', error.message);
679-
/* __GDPR__
680-
"issueReporterSearchError" : {
681-
"message" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }
682-
}
683-
*/
684-
this.telemetryService.publicLog('issueReporterSearchError', { message: error.message });
679+
type IssueReporterSearchErrorClassification = {
680+
message: { classification: 'CallstackOrException', purpose: 'PerformanceAndHealth' }
681+
};
682+
683+
type IssueReporterSearchError = {
684+
message: string;
685+
};
686+
this.telemetryService.publicLog2<IssueReporterSearchError, IssueReporterSearchErrorClassification>('issueReporterSearchError', { message: error.message });
685687
}
686688

687689
private setUpTypes(): void {
@@ -873,13 +875,15 @@ export class IssueReporter extends Disposable {
873875
return false;
874876
}
875877

876-
/* __GDPR__
877-
"issueReporterSubmit" : {
878-
"issueType" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
879-
"numSimilarIssuesDisplayed" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
880-
}
881-
*/
882-
this.telemetryService.publicLog('issueReporterSubmit', { issueType: this.issueReporterModel.getData().issueType, numSimilarIssuesDisplayed: this.numberOfSearchResultsDisplayed });
878+
type IssueReporterSubmitClassification = {
879+
issueType: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
880+
numSimilarIssuesDisplayed: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
881+
};
882+
type IssueReporterSubmitEvent = {
883+
issueType: any;
884+
numSimilarIssuesDisplayed: number;
885+
};
886+
this.telemetryService.publicLog2<IssueReporterSubmitEvent, IssueReporterSubmitClassification>('issueReporterSubmit', { issueType: this.issueReporterModel.getData().issueType, numSimilarIssuesDisplayed: this.numberOfSearchResultsDisplayed });
883887
this.hasBeenSubmitted = true;
884888

885889
const baseUrl = this.getIssueUrlWithTitle((<HTMLInputElement>this.getElementById('issue-title')).value);
@@ -1106,11 +1110,7 @@ export class IssueReporter extends Disposable {
11061110
// Exclude right click
11071111
if (event.which < 3) {
11081112
shell.openExternal((<HTMLAnchorElement>event.target).href);
1109-
1110-
/* __GDPR__
1111-
"issueReporterViewSimilarIssue" : { }
1112-
*/
1113-
this.telemetryService.publicLog('issueReporterViewSimilarIssue');
1113+
this.telemetryService.publicLog2('issueReporterViewSimilarIssue');
11141114
}
11151115
}
11161116

@@ -1121,12 +1121,13 @@ export class IssueReporter extends Disposable {
11211121
} else {
11221122
const error = new Error(`${elementId} not found.`);
11231123
this.logService.error(error);
1124-
/* __GDPR__
1125-
"issueReporterGetElementError" : {
1126-
"message" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }
1127-
}
1128-
*/
1129-
this.telemetryService.publicLog('issueReporterGetElementError', { message: error.message });
1124+
type IssueReporterGetElementErrorClassification = {
1125+
message: { classification: 'CallstackOrException', purpose: 'PerformanceAndHealth' };
1126+
};
1127+
type IssueReporterGetElementErrorEvent = {
1128+
message: string;
1129+
};
1130+
this.telemetryService.publicLog2<IssueReporterGetElementErrorEvent, IssueReporterGetElementErrorClassification>('issueReporterGetElementError', { message: error.message });
11301131

11311132
return undefined;
11321133
}

src/vs/code/electron-main/windows.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1709,14 +1709,13 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
17091709

17101710
private onWindowError(window: ICodeWindow, error: WindowError): void {
17111711
this.logService.error(error === WindowError.CRASHED ? '[VS Code]: render process crashed!' : '[VS Code]: detected unresponsive');
1712-
1713-
/* __GDPR__
1714-
"windowerror" : {
1715-
"type" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }
1716-
}
1717-
*/
1718-
this.telemetryService.publicLog('windowerror', { type: error });
1719-
1712+
type WindowErrorClassification = {
1713+
type: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
1714+
};
1715+
type WindowErrorEvent = {
1716+
type: WindowError;
1717+
};
1718+
this.telemetryService.publicLog2<WindowErrorEvent, WindowErrorClassification>('windowerror', { type: error });
17201719
// Unresponsive
17211720
if (error === WindowError.UNRESPONSIVE) {
17221721
if (window.isExtensionDevelopmentHost || window.isExtensionTestHost || (window.win && window.win.webContents && window.win.webContents.isDevToolsOpened())) {

src/vs/platform/contextview/browser/contextMenuHandler.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import 'vs/css!./contextMenuHandler';
77

88
import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
9-
import { ActionRunner, IRunEvent } from 'vs/base/common/actions';
9+
import { ActionRunner, IRunEvent, WBActionExecutedEvent, WBActionExecutedClassification } from 'vs/base/common/actions';
1010
import { Menu } from 'vs/base/browser/ui/menu/menu';
1111
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
1212
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
@@ -132,13 +132,7 @@ export class ContextMenuHandler {
132132

133133
private onActionRun(e: IRunEvent): void {
134134
if (this.telemetryService) {
135-
/* __GDPR__
136-
"workbenchActionExecuted" : {
137-
"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
138-
"from": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
139-
}
140-
*/
141-
this.telemetryService.publicLog('workbenchActionExecuted', { id: e.action.id, from: 'contextMenu' });
135+
this.telemetryService.publicLog2<WBActionExecutedEvent, WBActionExecutedClassification>('workbenchActionExecuted', { id: e.action.id, from: 'contextMenu' });
142136
}
143137

144138
this.contextViewService.hideContextView(false);

src/vs/platform/extensionManagement/node/extensionGalleryService.ts

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -647,21 +647,26 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
647647
}
648648

649649
const message = getErrorMessage(err);
650-
/* __GDPR__
651-
"galleryService:requestError" : {
652-
"url" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
653-
"cdn": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
654-
"message": { "classification": "CallstackOrException", "purpose": "FeatureInsight" }
655-
}
656-
*/
657-
this.telemetryService.publicLog('galleryService:requestError', { url, cdn: true, message });
658-
/* __GDPR__
659-
"galleryService:cdnFallback" : {
660-
"url" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
661-
"message": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
662-
}
663-
*/
664-
this.telemetryService.publicLog('galleryService:cdnFallback', { url, message });
650+
type GalleryServiceREClassification = {
651+
url: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
652+
cdn: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
653+
message: { classification: 'CallstackOrException', purpose: 'FeatureInsight' };
654+
};
655+
type GalleryServiceREServiceEvent = {
656+
url: string;
657+
cdn: boolean;
658+
message: string;
659+
};
660+
this.telemetryService.publicLog2<GalleryServiceREServiceEvent, GalleryServiceREClassification>('galleryService:requestError', { url, cdn: true, message });
661+
type GalleryServiceCDNFBClassification = {
662+
url: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
663+
message: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
664+
};
665+
type GalleryServiceCDNFBEvent = {
666+
url: string;
667+
message: string;
668+
};
669+
this.telemetryService.publicLog2<GalleryServiceCDNFBEvent, GalleryServiceCDNFBClassification>('galleryService:cdnFallback', { url, message });
665670

666671
const fallbackOptions = assign({}, options, { url: fallbackUrl });
667672
return this.requestService.request(fallbackOptions, token).then(undefined, err => {
@@ -670,14 +675,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
670675
}
671676

672677
const message = getErrorMessage(err);
673-
/* __GDPR__
674-
"galleryService:requestError" : {
675-
"url" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
676-
"cdn": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
677-
"message": { "classification": "CallstackOrException", "purpose": "FeatureInsight" }
678-
}
679-
*/
680-
this.telemetryService.publicLog('galleryService:requestError', { url: fallbackUrl, cdn: false, message });
678+
this.telemetryService.publicLog2<GalleryServiceREServiceEvent, GalleryServiceREClassification>('galleryService:requestError', { url: fallbackUrl, cdn: false, message });
681679
return Promise.reject(err);
682680
});
683681
});

src/vs/platform/keybinding/common/abstractKeybindingService.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { IResolveResult, KeybindingResolver } from 'vs/platform/keybinding/commo
1616
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
1717
import { INotificationService } from 'vs/platform/notification/common/notification';
1818
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
19+
import { WBActionExecutedEvent, WBActionExecutedClassification } from 'vs/base/common/actions';
1920

2021
interface CurrentChord {
2122
keypress: string;
@@ -195,13 +196,7 @@ export abstract class AbstractKeybindingService extends Disposable implements IK
195196
} else {
196197
this._commandService.executeCommand(resolveResult.commandId, resolveResult.commandArgs).then(undefined, err => this._notificationService.warn(err));
197198
}
198-
/* __GDPR__
199-
"workbenchActionExecuted" : {
200-
"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
201-
"from": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
202-
}
203-
*/
204-
this._telemetryService.publicLog('workbenchActionExecuted', { id: resolveResult.commandId, from: 'keybinding' });
199+
this._telemetryService.publicLog2<WBActionExecutedEvent, WBActionExecutedClassification>('workbenchActionExecuted', { id: resolveResult.commandId, from: 'keybinding' });
205200
}
206201

207202
return shouldPreventDefault;

src/vs/platform/menubar/electron-main/menubar.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { IMenubarData, IMenubarKeybinding, MenubarMenuItem, isMenubarMenuItemSep
2121
import { URI } from 'vs/base/common/uri';
2222
import { IStateService } from 'vs/platform/state/common/state';
2323
import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
24+
import { WBActionExecutedEvent, WBActionExecutedClassification } from 'vs/base/common/actions';
2425

2526
const telemetryFrom = 'menu';
2627

@@ -783,13 +784,7 @@ export class Menubar {
783784
}
784785

785786
private reportMenuActionTelemetry(id: string): void {
786-
/* __GDPR__
787-
"workbenchActionExecuted" : {
788-
"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
789-
"from": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
790-
}
791-
*/
792-
this.telemetryService.publicLog('workbenchActionExecuted', { id, from: telemetryFrom });
787+
this.telemetryService.publicLog2<WBActionExecutedEvent, WBActionExecutedClassification>('workbenchActionExecuted', { id, from: telemetryFrom });
793788
}
794789

795790
private mnemonicLabel(label: string): string {

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

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,16 @@ import { dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
99
import { safeStringify } from 'vs/base/common/objects';
1010
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
1111

12-
/* __GDPR__FRAGMENT__
13-
"ErrorEvent" : {
14-
"stack": { "classification": "CustomerContent", "purpose": "PerformanceAndHealth" },
15-
"message" : { "classification": "CustomerContent", "purpose": "PerformanceAndHealth" },
16-
"filename" : { "classification": "CustomerContent", "purpose": "PerformanceAndHealth" },
17-
"callstack": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
18-
"msg" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
19-
"file" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
20-
"line": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth", "isMeasurement": true },
21-
"column": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth", "isMeasurement": true },
22-
"uncaught_error_name": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
23-
"uncaught_error_msg": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
24-
"count": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth", "isMeasurement": true }
25-
}
26-
*/
12+
type ErrorEventFragment = {
13+
callstack: { classification: 'CallstackOrException', purpose: 'PerformanceAndHealth' };
14+
msg?: { classification: 'CallstackOrException', purpose: 'PerformanceAndHealth' };
15+
file?: { classification: 'CallstackOrException', purpose: 'PerformanceAndHealth' };
16+
line?: { classification: 'CallstackOrException', purpose: 'PerformanceAndHealth', isMeasurement: true };
17+
column?: { classification: 'CallstackOrException', purpose: 'PerformanceAndHealth', isMeasurement: true };
18+
uncaught_error_name?: { classification: 'CallstackOrException', purpose: 'PerformanceAndHealth' };
19+
uncaught_error_msg?: { classification: 'CallstackOrException', purpose: 'PerformanceAndHealth' };
20+
count?: { classification: 'CallstackOrException', purpose: 'PerformanceAndHealth', isMeasurement: true };
21+
};
2722
export interface ErrorEvent {
2823
callstack: string;
2924
msg?: string;
@@ -124,12 +119,8 @@ export default abstract class BaseErrorTelemetry {
124119

125120
private _flushBuffer(): void {
126121
for (let error of this._buffer) {
127-
/* __GDPR__
128-
"UnhandledError" : {
129-
"${include}": [ "${ErrorEvent}" ]
130-
}
131-
*/
132-
this._telemetryService.publicLog('UnhandledError', error, true);
122+
type UnhandledErrorClassification = {} & ErrorEventFragment;
123+
this._telemetryService.publicLog2<ErrorEvent, UnhandledErrorClassification>('UnhandledError', error, true);
133124
}
134125
this._buffer.length = 0;
135126
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@ export type StrictPropertyCheckError = 'Type of classified event does not match
2323

2424
export type StrictPropertyCheck<T extends IGDPRProperty, E> = StrictPropertyChecker<E, ClassifiedEvent<T>, StrictPropertyCheckError>;
2525

26-
export type GDPRClassification<T> = { [_ in keyof T]: IPropertyData | IGDPRProperty | undefined };
26+
export type GDPRClassification<T> = { [_ in keyof T]: IPropertyData | IGDPRProperty | undefined };

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,13 @@ export class TelemetryService implements ITelemetryService {
5757
if (this._configurationService) {
5858
this._updateUserOptIn();
5959
this._configurationService.onDidChangeConfiguration(this._updateUserOptIn, this, this._disposables);
60-
/* __GDPR__
61-
"optInStatus" : {
62-
"optIn" : { "classification": "SystemMetaData", "purpose": "BusinessInsight", "isMeasurement": true }
63-
}
64-
*/
65-
this.publicLog('optInStatus', { optIn: this._userOptIn });
60+
type OptInClass = {
61+
optIn: { classification: 'SystemMetaData', purpose: 'BusinessInsight', isMeasurement: true };
62+
};
63+
type OptInEvent = {
64+
optIn: boolean;
65+
};
66+
this.publicLog2<OptInEvent, OptInClass>('optInStatus', { optIn: this._userOptIn });
6667

6768
this._commonProperties.then(values => {
6869
const isHashedId = /^[a-f0-9]+$/i.test(values['common.machineId']);

0 commit comments

Comments
 (0)