microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
e034b2f8ed9e645a9f8ffe314b40f4e0b988e473

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/common/telemetryHelper.ts

234lines · 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 createTelemetryEvent(eventName: string): Telemetry.TelemetryEvent {
149 return new Telemetry.TelemetryEvent(Telemetry.appName + "/" + eventName);
150 }
151
152 public static telemetryProperty(propertyValue: any, pii?: boolean): ITelemetryPropertyInfo {
153 return { value: String(propertyValue), isPii: pii || false };
154 }
155
156 public static addTelemetryEventProperties(event: Telemetry.TelemetryEvent, properties: ICommandTelemetryProperties): void {
157 if (!properties) {
158 return;
159 }
160
161 Object.keys(properties).forEach(function (propertyName: string): void {
162 TelemetryHelper.addTelemetryEventProperty(event, propertyName, properties[propertyName].value, properties[propertyName].isPii);
163 });
164 }
165
166 public static sendCommandSuccessTelemetry(commandName: string, commandProperties: ICommandTelemetryProperties, args: string[] = null): void {
167 let successEvent: Telemetry.TelemetryEvent = TelemetryHelper.createBasicCommandTelemetry(commandName, args);
168
169 TelemetryHelper.addTelemetryEventProperties(successEvent, commandProperties);
170
171 Telemetry.send(successEvent);
172 }
173
174 public static addTelemetryEventProperty(event: Telemetry.TelemetryEvent, propertyName: string, propertyValue: any, isPii: boolean): void {
175 if (Array.isArray(propertyValue)) {
176 TelemetryHelper.addMultiValuedTelemetryEventProperty(event, propertyName, propertyValue, isPii);
177 } else {
178 TelemetryHelper.setTelemetryEventProperty(event, propertyName, propertyValue, isPii);
179 }
180 }
181
182 public static addPropertiesFromOptions(telemetryProperties: ICommandTelemetryProperties, knownOptions: any, commandOptions: {[flag: string]: any}, nonPiiOptions: string[] = []): ICommandTelemetryProperties {
183 // We parse only the known options, to avoid potential private information that may appear on the command line
184 let unknownOptionIndex: number = 1;
185 Object.keys(commandOptions).forEach((key: string) => {
186 let value: any = commandOptions[key];
187 if (Object.keys(knownOptions).indexOf(key) >= 0) {
188 // This is a known option. We"ll check the list to decide if it"s pii or not
189 if (typeof (value) !== "undefined") {
190 // We encrypt all options values unless they are specifically marked as nonPii
191 telemetryProperties["options." + key] = this.telemetryProperty(value, nonPiiOptions.indexOf(key) < 0);
192 }
193 } else {
194 // This is a not known option. We"ll assume that both the option and the value are pii
195 telemetryProperties["unknownOption" + unknownOptionIndex + ".name"] = this.telemetryProperty(key, /*isPii*/ true);
196 telemetryProperties["unknownOption" + unknownOptionIndex++ + ".value"] = this.telemetryProperty(value, /*isPii*/ true);
197 }
198 });
199 return telemetryProperties;
200 }
201
202 public static generate<T>(name: string, codeGeneratingTelemetry: { (telemetry: TelemetryGenerator): Thenable<T> }): Q.Promise<T> {
203 let generator: TelemetryGenerator = new TelemetryGenerator(name);
204 return generator.time(null, () => codeGeneratingTelemetry(generator)).finally(() => generator.send());
205 }
206
207 private static createBasicCommandTelemetry(commandName: string, args: string[] = null): Telemetry.TelemetryEvent {
208 let commandEvent: Telemetry.TelemetryEvent = new Telemetry.TelemetryEvent(Telemetry.appName + "/" + (commandName || "command"));
209
210 if (!commandName && args && args.length > 0) {
211 commandEvent.setPiiProperty("command", args[0]);
212 }
213
214 if (args) {
215 TelemetryHelper.addTelemetryEventProperty(commandEvent, "argument", args, true);
216 }
217
218 return commandEvent;
219 }
220
221 private static setTelemetryEventProperty(event: Telemetry.TelemetryEvent, propertyName: string, propertyValue: string, isPii: boolean): void {
222 if (isPii) {
223 event.setPiiProperty(propertyName, String(propertyValue));
224 } else {
225 event.properties[propertyName] = String(propertyValue);
226 }
227 }
228
229 private static addMultiValuedTelemetryEventProperty(event: Telemetry.TelemetryEvent, propertyName: string, propertyValue: string, isPii: boolean): void {
230 for (let i: number = 0; i < propertyValue.length; i++) {
231 TelemetryHelper.setTelemetryEventProperty(event, propertyName + i, propertyValue[i], isPii);
232 }
233 }
234};