Skip to content

Commit

Permalink
Merge pull request #93 from aeisenberg/aeisenberg/replacements
Browse files Browse the repository at this point in the history
Add ReplacementOptions to filter telemetry events
  • Loading branch information
lramos15 authored Apr 5, 2022
2 parents 4408ada + 33328e6 commit 5328bf1
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 17 deletions.
20 changes: 19 additions & 1 deletion lib/telemetryReporter.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,32 @@ export interface RawTelemetryEventProperties {
export interface TelemetryEventMeasurements {
readonly [key: string]: number;
}

/**
* A replacement option for the app insights client. This allows the appender to filter out any sensitive or unnecessary information from the telemetry server.
*/
export interface ReplacementOption {

/**
* A regular expression matching any property to be removed or replaced from the telemetry server.
*/
lookup: RegExp;

/**
* The replacement value for the property. If not present or undefined, the property will be removed.
*/
replacementString?: string;
}

export default class TelemetryReporter {
/**
* @param extensionId The id of your extension
* @param extensionVersion The version of your extension
* @param key The app insights key
* @param firstParty Whether or not the telemetry is first party (i.e from Microsoft / GitHub)
* @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.
*/
constructor(extensionId: string, extensionVersion: string, key: string, firstParty?: boolean);
constructor(extensionId: string, extensionVersion: string, key: string, firstParty?: boolean, replacementOptions?: ReplacementOption[]);

/**
* A string representation of the current level of telemetry being collected
Expand Down
24 changes: 16 additions & 8 deletions src/browser/telemetryReporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

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


const webAppInsightsClientFactory = async (key: string): Promise<BaseTelemetryClient> => {
const webAppInsightsClientFactory = async (key: string, replacementOptions?: ReplacementOption[]): Promise<BaseTelemetryClient> => {
let appInsightsClient: ApplicationInsights | undefined;
try {
const web = await import("@microsoft/applicationinsights-web");
Expand Down Expand Up @@ -43,16 +43,24 @@ const webAppInsightsClientFactory = async (key: string): Promise<BaseTelemetryCl
// Sets the appinsights client into a standardized form
const telemetryClient: BaseTelemetryClient = {
logEvent: (eventName: string, data?: AppenderData) => {
const properties = { ...data?.properties, ...data?.measurements };
if (replacementOptions?.length) {
applyReplacements(properties, replacementOptions);
}
appInsightsClient?.trackEvent(
{ name: eventName },
{ ...data?.properties, ...data?.measurements }
properties
);
},
logException: (exception: Error, data?: AppenderData) => {
const properties = { ...data?.properties, ...data?.measurements };
if (replacementOptions?.length) {
applyReplacements(properties, replacementOptions);
}
appInsightsClient?.trackException(
{
exception,
properties: { ...data?.properties, ...data?.measurements }
properties
});
},
flush: async () => {
Expand All @@ -63,8 +71,8 @@ const webAppInsightsClientFactory = async (key: string): Promise<BaseTelemetryCl
};

export default class TelemetryReporter extends BaseTelemetryReporter {
constructor(extensionId: string, extensionVersion: string, key: string, firstParty?: boolean) {
const appender = new BaseTelemetryAppender(key, webAppInsightsClientFactory);
constructor(extensionId: string, extensionVersion: string, key: string, firstParty?: boolean, replacementOptions?: ReplacementOption[]) {
const appender = new BaseTelemetryAppender(key, key => webAppInsightsClientFactory(key, replacementOptions));
if (key && key.indexOf("AIF-") === 0) {
firstParty = true;
}
Expand All @@ -74,4 +82,4 @@ export default class TelemetryReporter extends BaseTelemetryReporter {
architecture: "web",
}, firstParty);
}
}
}
18 changes: 17 additions & 1 deletion src/common/baseTelemetryReporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,22 @@ export interface ITelemetryAppender {
instantiateAppender(): void;
}

/**
* A replacement option for the app insights client. This allows the appender to filter out any sensitive or unnecessary information from the telemetry server.
*/
export interface ReplacementOption {

/**
* A regular expression matching any property to be removed or replaced from the telemetry server.
*/
lookup: RegExp;

/**
* The replacement value for the property. If not present or undefined, the property will be removed.
*/
replacementString?: string;
}

export class BaseTelemetryReporter {
private firstParty = false;
private userOptIn = false;
Expand Down Expand Up @@ -352,4 +368,4 @@ export class BaseTelemetryReporter {
this.telemetryAppender.flush();
return Promise.all(this.disposables.map(d => d.dispose()));
}
}
}
17 changes: 16 additions & 1 deletion src/common/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*--------------------------------------------------------*/

import * as vscode from "vscode";
import { ReplacementOption } from "./baseTelemetryReporter";

export const enum TelemetryLevel {
ON = "on",
Expand Down Expand Up @@ -34,4 +35,18 @@ export function getTelemetryLevel(): TelemetryLevel {
const enabled = config.get<boolean>(TELEMETRY_CONFIG_ENABLED_ID);
return enabled ? TelemetryLevel.ON : TelemetryLevel.OFF;
}
}
}

export function applyReplacements(data: Record<string, any>, replacementOptions: ReplacementOption[]) {
for (const key of Object.keys(data)) {
for (const option of replacementOptions) {
if (option.lookup.test(key)) {
if (option.replacementString !== undefined) {
data[key] = option.replacementString;
} else {
delete data[key];
}
}
}
}
}
47 changes: 41 additions & 6 deletions src/node/telemetryReporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@
import * as os from "os";
import * as vscode from "vscode";
import type { TelemetryClient } from "applicationinsights";
import { AppenderData, BaseTelemetryReporter } from "../common/baseTelemetryReporter";
import { AppenderData, BaseTelemetryReporter, ReplacementOption } from "../common/baseTelemetryReporter";
import { BaseTelemetryAppender, BaseTelemetryClient } from "../common/baseTelemetryAppender";
import { applyReplacements } from "../common/util";

/**
* A factory function which creates a telemetry client to be used by an appender to send telemetry
* A factory function which creates a telemetry client to be used by an appender to send telemetry in a node application.
*
* @param key The app insights key
* @param replacementOptions Optional list of {@link ReplacementOption replacements} to apply to the telemetry client. This allows
* the appender to filter out any sensitive or unnecessary information from the telemetry server.
*
* @returns A promise which resolves to the telemetry client or rejects upon error
*/
const appInsightsClientFactory = async (key: string): Promise<BaseTelemetryClient> => {
const appInsightsClientFactory = async (key: string, replacementOptions?: ReplacementOption[]): Promise<BaseTelemetryClient> => {
let appInsightsClient: TelemetryClient | undefined;
try {
process.env["APPLICATION_INSIGHTS_NO_DIAGNOSTIC_CHANNEL"] = "1";
Expand Down Expand Up @@ -49,6 +54,11 @@ const appInsightsClientFactory = async (key: string): Promise<BaseTelemetryClien
} catch (e: any) {
return Promise.reject("Failed to initialize app insights!\n" + e.message);
}

if (replacementOptions?.length) {
addReplacementOptions(appInsightsClient, replacementOptions);
}

// Sets the appinsights client into a standardized form
const telemetryClient: BaseTelemetryClient = {
logEvent: (eventName: string, data?: AppenderData) => {
Expand Down Expand Up @@ -84,9 +94,34 @@ const appInsightsClientFactory = async (key: string): Promise<BaseTelemetryClien
return telemetryClient;
};

/**
* Adds replacement options to this {@link TelemetryClient}.
*
* If any replacement options are specified, this function will search through any event about to be
* sent to the telemetry server and replace any matches with the specified replacement string. Both
* the envelope and the base data will be searched.
*
* @param appInsightsClient The {@link TelemetryClient} to add the filters to.
* @param replacementOptions The replacement options to add.
*/
function addReplacementOptions(appInsightsClient: TelemetryClient, replacementOptions: ReplacementOption[]) {
appInsightsClient.addTelemetryProcessor((event) => {
if (Array.isArray(event.tags)) {
event.tags.forEach(tag => applyReplacements(tag, replacementOptions));
} else if (event.tags) {
applyReplacements(event.tags, replacementOptions);
}

if (event.data.baseData) {
applyReplacements(event.data.baseData, replacementOptions);
}
return true;
});
}

export default class TelemetryReporter extends BaseTelemetryReporter {
constructor(extensionId: string, extensionVersion: string, key: string, firstParty?: boolean) {
const appender = new BaseTelemetryAppender(key, appInsightsClientFactory);
constructor(extensionId: string, extensionVersion: string, key: string, firstParty?: boolean, replacementOptions?: ReplacementOption[]) {
const appender = new BaseTelemetryAppender(key, (key) => appInsightsClientFactory(key, replacementOptions));
if (key && key.indexOf("AIF-") === 0) {
firstParty = true;
}
Expand All @@ -96,4 +131,4 @@ export default class TelemetryReporter extends BaseTelemetryReporter {
architecture: os.arch(),
}, firstParty);
}
}
}

0 comments on commit 5328bf1

Please sign in to comment.