Skip to content

Commit 5328bf1

Browse files
authored
Merge pull request #93 from aeisenberg/aeisenberg/replacements
Add ReplacementOptions to filter telemetry events
2 parents 4408ada + 33328e6 commit 5328bf1

File tree

5 files changed

+109
-17
lines changed

5 files changed

+109
-17
lines changed

lib/telemetryReporter.d.ts

+19-1
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,32 @@ export interface RawTelemetryEventProperties {
1313
export interface TelemetryEventMeasurements {
1414
readonly [key: string]: number;
1515
}
16+
17+
/**
18+
* A replacement option for the app insights client. This allows the appender to filter out any sensitive or unnecessary information from the telemetry server.
19+
*/
20+
export interface ReplacementOption {
21+
22+
/**
23+
* A regular expression matching any property to be removed or replaced from the telemetry server.
24+
*/
25+
lookup: RegExp;
26+
27+
/**
28+
* The replacement value for the property. If not present or undefined, the property will be removed.
29+
*/
30+
replacementString?: string;
31+
}
32+
1633
export default class TelemetryReporter {
1734
/**
1835
* @param extensionId The id of your extension
1936
* @param extensionVersion The version of your extension
2037
* @param key The app insights key
2138
* @param firstParty Whether or not the telemetry is first party (i.e from Microsoft / GitHub)
39+
* @param replacementOptions A list of replacement options for the app insights client. This allows the appender to filter out any sensitive or unnecessary information from the telemetry server.
2240
*/
23-
constructor(extensionId: string, extensionVersion: string, key: string, firstParty?: boolean);
41+
constructor(extensionId: string, extensionVersion: string, key: string, firstParty?: boolean, replacementOptions?: ReplacementOption[]);
2442

2543
/**
2644
* A string representation of the current level of telemetry being collected

src/browser/telemetryReporter.ts

+16-8
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44

55
import type { ApplicationInsights } from "@microsoft/applicationinsights-web";
66
import { BaseTelemetryAppender, BaseTelemetryClient } from "../common/baseTelemetryAppender";
7-
import { AppenderData, BaseTelemetryReporter } from "../common/baseTelemetryReporter";
8-
import { getTelemetryLevel, TelemetryLevel } from "../common/util";
7+
import { AppenderData, BaseTelemetryReporter, ReplacementOption } from "../common/baseTelemetryReporter";
8+
import { applyReplacements, getTelemetryLevel, TelemetryLevel } from "../common/util";
99

1010

11-
const webAppInsightsClientFactory = async (key: string): Promise<BaseTelemetryClient> => {
11+
const webAppInsightsClientFactory = async (key: string, replacementOptions?: ReplacementOption[]): Promise<BaseTelemetryClient> => {
1212
let appInsightsClient: ApplicationInsights | undefined;
1313
try {
1414
const web = await import("@microsoft/applicationinsights-web");
@@ -43,16 +43,24 @@ const webAppInsightsClientFactory = async (key: string): Promise<BaseTelemetryCl
4343
// Sets the appinsights client into a standardized form
4444
const telemetryClient: BaseTelemetryClient = {
4545
logEvent: (eventName: string, data?: AppenderData) => {
46+
const properties = { ...data?.properties, ...data?.measurements };
47+
if (replacementOptions?.length) {
48+
applyReplacements(properties, replacementOptions);
49+
}
4650
appInsightsClient?.trackEvent(
4751
{ name: eventName },
48-
{ ...data?.properties, ...data?.measurements }
52+
properties
4953
);
5054
},
5155
logException: (exception: Error, data?: AppenderData) => {
56+
const properties = { ...data?.properties, ...data?.measurements };
57+
if (replacementOptions?.length) {
58+
applyReplacements(properties, replacementOptions);
59+
}
5260
appInsightsClient?.trackException(
5361
{
5462
exception,
55-
properties: { ...data?.properties, ...data?.measurements }
63+
properties
5664
});
5765
},
5866
flush: async () => {
@@ -63,8 +71,8 @@ const webAppInsightsClientFactory = async (key: string): Promise<BaseTelemetryCl
6371
};
6472

6573
export default class TelemetryReporter extends BaseTelemetryReporter {
66-
constructor(extensionId: string, extensionVersion: string, key: string, firstParty?: boolean) {
67-
const appender = new BaseTelemetryAppender(key, webAppInsightsClientFactory);
74+
constructor(extensionId: string, extensionVersion: string, key: string, firstParty?: boolean, replacementOptions?: ReplacementOption[]) {
75+
const appender = new BaseTelemetryAppender(key, key => webAppInsightsClientFactory(key, replacementOptions));
6876
if (key && key.indexOf("AIF-") === 0) {
6977
firstParty = true;
7078
}
@@ -74,4 +82,4 @@ export default class TelemetryReporter extends BaseTelemetryReporter {
7482
architecture: "web",
7583
}, firstParty);
7684
}
77-
}
85+
}

src/common/baseTelemetryReporter.ts

+17-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,22 @@ export interface ITelemetryAppender {
1717
instantiateAppender(): void;
1818
}
1919

20+
/**
21+
* A replacement option for the app insights client. This allows the appender to filter out any sensitive or unnecessary information from the telemetry server.
22+
*/
23+
export interface ReplacementOption {
24+
25+
/**
26+
* A regular expression matching any property to be removed or replaced from the telemetry server.
27+
*/
28+
lookup: RegExp;
29+
30+
/**
31+
* The replacement value for the property. If not present or undefined, the property will be removed.
32+
*/
33+
replacementString?: string;
34+
}
35+
2036
export class BaseTelemetryReporter {
2137
private firstParty = false;
2238
private userOptIn = false;
@@ -352,4 +368,4 @@ export class BaseTelemetryReporter {
352368
this.telemetryAppender.flush();
353369
return Promise.all(this.disposables.map(d => d.dispose()));
354370
}
355-
}
371+
}

src/common/util.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*--------------------------------------------------------*/
44

55
import * as vscode from "vscode";
6+
import { ReplacementOption } from "./baseTelemetryReporter";
67

78
export const enum TelemetryLevel {
89
ON = "on",
@@ -34,4 +35,18 @@ export function getTelemetryLevel(): TelemetryLevel {
3435
const enabled = config.get<boolean>(TELEMETRY_CONFIG_ENABLED_ID);
3536
return enabled ? TelemetryLevel.ON : TelemetryLevel.OFF;
3637
}
37-
}
38+
}
39+
40+
export function applyReplacements(data: Record<string, any>, replacementOptions: ReplacementOption[]) {
41+
for (const key of Object.keys(data)) {
42+
for (const option of replacementOptions) {
43+
if (option.lookup.test(key)) {
44+
if (option.replacementString !== undefined) {
45+
data[key] = option.replacementString;
46+
} else {
47+
delete data[key];
48+
}
49+
}
50+
}
51+
}
52+
}

src/node/telemetryReporter.ts

+41-6
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,20 @@
55
import * as os from "os";
66
import * as vscode from "vscode";
77
import type { TelemetryClient } from "applicationinsights";
8-
import { AppenderData, BaseTelemetryReporter } from "../common/baseTelemetryReporter";
8+
import { AppenderData, BaseTelemetryReporter, ReplacementOption } from "../common/baseTelemetryReporter";
99
import { BaseTelemetryAppender, BaseTelemetryClient } from "../common/baseTelemetryAppender";
10+
import { applyReplacements } from "../common/util";
1011

1112
/**
12-
* A factory function which creates a telemetry client to be used by an appender to send telemetry
13+
* A factory function which creates a telemetry client to be used by an appender to send telemetry in a node application.
14+
*
1315
* @param key The app insights key
16+
* @param replacementOptions Optional list of {@link ReplacementOption replacements} to apply to the telemetry client. This allows
17+
* the appender to filter out any sensitive or unnecessary information from the telemetry server.
18+
*
1419
* @returns A promise which resolves to the telemetry client or rejects upon error
1520
*/
16-
const appInsightsClientFactory = async (key: string): Promise<BaseTelemetryClient> => {
21+
const appInsightsClientFactory = async (key: string, replacementOptions?: ReplacementOption[]): Promise<BaseTelemetryClient> => {
1722
let appInsightsClient: TelemetryClient | undefined;
1823
try {
1924
process.env["APPLICATION_INSIGHTS_NO_DIAGNOSTIC_CHANNEL"] = "1";
@@ -49,6 +54,11 @@ const appInsightsClientFactory = async (key: string): Promise<BaseTelemetryClien
4954
} catch (e: any) {
5055
return Promise.reject("Failed to initialize app insights!\n" + e.message);
5156
}
57+
58+
if (replacementOptions?.length) {
59+
addReplacementOptions(appInsightsClient, replacementOptions);
60+
}
61+
5262
// Sets the appinsights client into a standardized form
5363
const telemetryClient: BaseTelemetryClient = {
5464
logEvent: (eventName: string, data?: AppenderData) => {
@@ -84,9 +94,34 @@ const appInsightsClientFactory = async (key: string): Promise<BaseTelemetryClien
8494
return telemetryClient;
8595
};
8696

97+
/**
98+
* Adds replacement options to this {@link TelemetryClient}.
99+
*
100+
* If any replacement options are specified, this function will search through any event about to be
101+
* sent to the telemetry server and replace any matches with the specified replacement string. Both
102+
* the envelope and the base data will be searched.
103+
*
104+
* @param appInsightsClient The {@link TelemetryClient} to add the filters to.
105+
* @param replacementOptions The replacement options to add.
106+
*/
107+
function addReplacementOptions(appInsightsClient: TelemetryClient, replacementOptions: ReplacementOption[]) {
108+
appInsightsClient.addTelemetryProcessor((event) => {
109+
if (Array.isArray(event.tags)) {
110+
event.tags.forEach(tag => applyReplacements(tag, replacementOptions));
111+
} else if (event.tags) {
112+
applyReplacements(event.tags, replacementOptions);
113+
}
114+
115+
if (event.data.baseData) {
116+
applyReplacements(event.data.baseData, replacementOptions);
117+
}
118+
return true;
119+
});
120+
}
121+
87122
export default class TelemetryReporter extends BaseTelemetryReporter {
88-
constructor(extensionId: string, extensionVersion: string, key: string, firstParty?: boolean) {
89-
const appender = new BaseTelemetryAppender(key, appInsightsClientFactory);
123+
constructor(extensionId: string, extensionVersion: string, key: string, firstParty?: boolean, replacementOptions?: ReplacementOption[]) {
124+
const appender = new BaseTelemetryAppender(key, (key) => appInsightsClientFactory(key, replacementOptions));
90125
if (key && key.indexOf("AIF-") === 0) {
91126
firstParty = true;
92127
}
@@ -96,4 +131,4 @@ export default class TelemetryReporter extends BaseTelemetryReporter {
96131
architecture: os.arch(),
97132
}, firstParty);
98133
}
99-
}
134+
}

0 commit comments

Comments
 (0)