microsoft/vscode-react-native

Public

mirrored fromhttps://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2d2a33644976c159bb50bad9df3cb949f150a8ec

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

src/common/telemetryHelper.ts

238lines · modecode

1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT license. See LICENSE file in the project root for details.
3
4import * as Q from "q";
5import {Telemetry} from "./telemetry";
6
7export interface ITelemetryPropertyInfo {
8 value: any;
9 isPii: boolean;
10}
11
12export interface ICommandTelemetryProperties {
13 [propertyName: string]: ITelemetryPropertyInfo;
14}
15
16export interface IExternalTelemetryProvider {
17 sendTelemetry: (event: string, props: Telemetry.ITelemetryProperties, error?: Error) => void;
18}
19
20interface IDictionary<T> {
21 [key: string]: T;
22}
23
24interface IHasErrorCode {
25 errorCode: number;
26}
27
28export abstract class TelemetryGeneratorBase {
29 protected telemetryProperties: ICommandTelemetryProperties = {};
30 private componentName: string;
31 private currentStepStartTime: number[];
32 private currentStep: string = "initialStep";
33 private errorIndex: number = -1; // In case we have more than one error (We start at -1 because we increment it before using it)
34
35 constructor(componentName: string) {
36 this.componentName = componentName;
37 this.currentStepStartTime = process.hrtime();
38 }
39
40 protected abstract sendTelemetryEvent(telemetryEvent: Telemetry.TelemetryEvent): void;
41
42 public add(baseName: string, value: any, isPii: boolean): TelemetryGeneratorBase {
43 return this.addWithPiiEvaluator(baseName, value, () => isPii);
44 }
45
46 public addWithPiiEvaluator(baseName: string, value: any, piiEvaluator: { (value: string, name: string): boolean }): TelemetryGeneratorBase {
47 // We have 3 cases:
48 // * Object is an array, we add each element as baseNameNNN
49 // * Object is a hash, we add each element as baseName.KEY
50 // * Object is a value, we add the element as baseName
51 try {
52 if (Array.isArray(value)) {
53 this.addArray(baseName, <any[]> value, piiEvaluator);
54 } else if (!!value && (typeof value === "object" || typeof value === "function")) {
55 this.addHash(baseName, <IDictionary<any>> value, piiEvaluator);
56 } else {
57 this.addString(baseName, String(value), piiEvaluator);
58 }
59 } catch (error) {
60 // We don"t want to crash the functionality if the telemetry fails.
61 // This error message will be a javascript error message, so it"s not pii
62 this.addString("telemetryGenerationError." + baseName, String(error), () => false);
63 }
64
65 return this;
66 }
67
68 public addError(error: Error): TelemetryGeneratorBase {
69 this.add("error.message" + ++this.errorIndex, error.message, /*isPii*/ true);
70 let errorWithErrorCode: IHasErrorCode = <IHasErrorCode> <Object> error;
71 if (errorWithErrorCode.errorCode) {
72 this.add("error.code" + this.errorIndex, errorWithErrorCode.errorCode, /*isPii*/ false);
73 }
74
75 return this;
76 }
77
78 public time<T>(name: string, codeToMeasure: { (): Thenable<T> }): Q.Promise<T> {
79 let startTime: number[] = process.hrtime();
80 return Q(codeToMeasure())
81 .finally(() => this.finishTime(name, startTime))
82 .fail((reason: any): Q.Promise<T> => {
83 this.addError(reason);
84 return Q.reject<T>(reason);
85 });
86 }
87
88 public step(name: string): TelemetryGeneratorBase {
89 // First we finish measuring this step time, and we send a telemetry event for this step
90 this.finishTime(this.currentStep, this.currentStepStartTime);
91 this.sendCurrentStep();
92
93 // Then we prepare to start gathering information about the next step
94 this.currentStep = name;
95 this.telemetryProperties = {};
96 this.currentStepStartTime = process.hrtime();
97 return this;
98 }
99
100 public send(): void {
101 if (this.currentStep) {
102 this.add("lastStepExecuted", this.currentStep, /*isPii*/ false);
103 }
104
105 this.step(null); // Send the last step
106 }
107
108 private sendCurrentStep(): void {
109 this.add("step", this.currentStep, /*isPii*/ false);
110 let telemetryEvent: Telemetry.TelemetryEvent = new Telemetry.TelemetryEvent(Telemetry.appName + "/" + this.componentName);
111 TelemetryHelper.addTelemetryEventProperties(telemetryEvent, this.telemetryProperties);
112 this.sendTelemetryEvent(telemetryEvent);
113 }
114
115 private addArray(baseName: string, array: any[], piiEvaluator: { (value: string, name: string): boolean }): void {
116 // Object is an array, we add each element as baseNameNNN
117 let elementIndex: number = 1; // We send telemetry properties in a one-based index
118 array.forEach((element: any) => this.addWithPiiEvaluator(baseName + elementIndex++, element, piiEvaluator));
119 }
120
121 private addHash(baseName: string, hash: IDictionary<any>, piiEvaluator: { (value: string, name: string): boolean }): void {
122 // Object is a hash, we add each element as baseName.KEY
123 Object.keys(hash).forEach((key: string) => this.addWithPiiEvaluator(baseName + "." + key, hash[key], piiEvaluator));
124 }
125
126 private addString(name: string, value: string, piiEvaluator: { (value: string, name: string): boolean }): void {
127 this.telemetryProperties[name] = TelemetryHelper.telemetryProperty(value, piiEvaluator(value, name));
128 }
129
130 private combine(...components: string[]): string {
131 let nonNullComponents: string[] = components.filter((component: string) => component !== null);
132 return nonNullComponents.join(".");
133 }
134
135 private finishTime(name: string, startTime: number[]): void {
136 let endTime: number[] = process.hrtime(startTime);
137 this.add(this.combine(name, "time"), String(endTime[0] * 1000 + endTime[1] / 1000000), /*isPii*/ false);
138 }
139}
140
141export class TelemetryGenerator extends TelemetryGeneratorBase {
142 protected sendTelemetryEvent(telemetryEvent: Telemetry.TelemetryEvent): void {
143 Telemetry.send(telemetryEvent);
144 }
145}
146
147export class TelemetryHelper {
148 public static sendSimpleEvent(eventName: string, properties?: Telemetry.ITelemetryProperties): void {
149 const event = TelemetryHelper.createTelemetryEvent(eventName, properties);
150 Telemetry.send(event);
151 }
152 public static createTelemetryEvent(eventName: string, properties?: Telemetry.ITelemetryProperties): Telemetry.TelemetryEvent {
153 return new Telemetry.TelemetryEvent(Telemetry.appName + "/" + eventName, properties);
154 }
155
156 public static telemetryProperty(propertyValue: any, pii?: boolean): ITelemetryPropertyInfo {
157 return { value: String(propertyValue), isPii: pii || false };
158 }
159
160 public static addTelemetryEventProperties(event: Telemetry.TelemetryEvent, properties: ICommandTelemetryProperties): void {
161 if (!properties) {
162 return;
163 }
164
165 Object.keys(properties).forEach(function (propertyName: string): void {
166 TelemetryHelper.addTelemetryEventProperty(event, propertyName, properties[propertyName].value, properties[propertyName].isPii);
167 });
168 }
169
170 public static sendCommandSuccessTelemetry(commandName: string, commandProperties: ICommandTelemetryProperties, args: string[] = null): void {
171 let successEvent: Telemetry.TelemetryEvent = TelemetryHelper.createBasicCommandTelemetry(commandName, args);
172
173 TelemetryHelper.addTelemetryEventProperties(successEvent, commandProperties);
174
175 Telemetry.send(successEvent);
176 }
177
178 public static addTelemetryEventProperty(event: Telemetry.TelemetryEvent, propertyName: string, propertyValue: any, isPii: boolean): void {
179 if (Array.isArray(propertyValue)) {
180 TelemetryHelper.addMultiValuedTelemetryEventProperty(event, propertyName, propertyValue, isPii);
181 } else {
182 TelemetryHelper.setTelemetryEventProperty(event, propertyName, propertyValue, isPii);
183 }
184 }
185
186 public static addPropertiesFromOptions(telemetryProperties: ICommandTelemetryProperties, knownOptions: any, commandOptions: {[flag: string]: any}, nonPiiOptions: string[] = []): ICommandTelemetryProperties {
187 // We parse only the known options, to avoid potential private information that may appear on the command line
188 let unknownOptionIndex: number = 1;
189 Object.keys(commandOptions).forEach((key: string) => {
190 let value: any = commandOptions[key];
191 if (Object.keys(knownOptions).indexOf(key) >= 0) {
192 // This is a known option. We"ll check the list to decide if it"s pii or not
193 if (typeof (value) !== "undefined") {
194 // We encrypt all options values unless they are specifically marked as nonPii
195 telemetryProperties["options." + key] = this.telemetryProperty(value, nonPiiOptions.indexOf(key) < 0);
196 }
197 } else {
198 // This is a not known option. We"ll assume that both the option and the value are pii
199 telemetryProperties["unknownOption" + unknownOptionIndex + ".name"] = this.telemetryProperty(key, /*isPii*/ true);
200 telemetryProperties["unknownOption" + unknownOptionIndex++ + ".value"] = this.telemetryProperty(value, /*isPii*/ true);
201 }
202 });
203 return telemetryProperties;
204 }
205
206 public static generate<T>(name: string, codeGeneratingTelemetry: { (telemetry: TelemetryGenerator): Thenable<T> }): Q.Promise<T> {
207 let generator: TelemetryGenerator = new TelemetryGenerator(name);
208 return generator.time(null, () => codeGeneratingTelemetry(generator)).finally(() => generator.send());
209 }
210
211 private static createBasicCommandTelemetry(commandName: string, args: string[] = null): Telemetry.TelemetryEvent {
212 let commandEvent: Telemetry.TelemetryEvent = new Telemetry.TelemetryEvent(Telemetry.appName + "/" + (commandName || "command"));
213
214 if (!commandName && args && args.length > 0) {
215 commandEvent.setPiiProperty("command", args[0]);
216 }
217
218 if (args) {
219 TelemetryHelper.addTelemetryEventProperty(commandEvent, "argument", args, true);
220 }
221
222 return commandEvent;
223 }
224
225 private static setTelemetryEventProperty(event: Telemetry.TelemetryEvent, propertyName: string, propertyValue: string, isPii: boolean): void {
226 if (isPii) {
227 event.setPiiProperty(propertyName, String(propertyValue));
228 } else {
229 event.properties[propertyName] = String(propertyValue);
230 }
231 }
232
233 private static addMultiValuedTelemetryEventProperty(event: Telemetry.TelemetryEvent, propertyName: string, propertyValue: string, isPii: boolean): void {
234 for (let i: number = 0; i < propertyValue.length; i++) {
235 TelemetryHelper.setTelemetryEventProperty(event, propertyName + i, propertyValue[i], isPii);
236 }
237 }
238};