Skip to content

Commit 2b3479a

Browse files
authored
Use sendTelemetryErrorEvent, preserve properties but redact errors (#14453) (#14525)
1 parent a8ea839 commit 2b3479a

File tree

2 files changed

+58
-50
lines changed

2 files changed

+58
-50
lines changed

src/client/telemetry/index.ts

Lines changed: 45 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -116,53 +116,56 @@ export function sendTelemetryEvent<P extends IEventNamePropertyMapping, E extend
116116
}
117117
const reporter = getTelemetryReporter();
118118
const measures = typeof durationMs === 'number' ? { duration: durationMs } : durationMs ? durationMs : undefined;
119-
let customProperties: Record<string, string> = {};
119+
const customProperties: Record<string, string> = {};
120120
const eventNameSent = eventName as string;
121121

122-
if (ex) {
123-
// When sending telemetry events for exceptions no need to send custom properties.
124-
// Else we have to review all properties every time as part of GDPR.
125-
// Assume we have 10 events all with their own properties.
126-
// As we have errors for each event, those properties are treated as new data items.
127-
// Hence they need to be classified as part of the GDPR process, and thats unnecessary and onerous.
128-
customProperties = { originalEventName: eventName as string };
129-
reporter.sendTelemetryException(ex, customProperties, measures);
130-
} else {
131-
if (properties) {
132-
const data = properties as any;
133-
Object.getOwnPropertyNames(data).forEach((prop) => {
134-
if (data[prop] === undefined || data[prop] === null) {
135-
return;
136-
}
137-
try {
138-
// If there are any errors in serializing one property, ignore that and move on.
139-
// Else nothing will be sent.
140-
customProperties[prop] =
141-
typeof data[prop] === 'string'
142-
? data[prop]
143-
: typeof data[prop] === 'object'
144-
? 'object'
145-
: data[prop].toString();
146-
} catch (ex) {
147-
traceError(`Failed to serialize ${prop} for ${eventName}`, ex);
148-
}
149-
});
150-
}
151-
152-
// Add shared properties to telemetry props (we may overwrite existing ones).
153-
Object.assign(customProperties, sharedProperties);
154-
155-
// Remove shared DS properties from core extension telemetry.
156-
Object.keys(sharedProperties).forEach((shareProperty) => {
157-
if (
158-
customProperties[shareProperty] &&
159-
shareProperty.startsWith('ds_') &&
160-
!(eventNameSent.startsWith('DS_') || eventNameSent.startsWith('DATASCIENCE'))
161-
) {
162-
delete customProperties[shareProperty];
122+
if (properties) {
123+
const data = properties as any;
124+
Object.getOwnPropertyNames(data).forEach((prop) => {
125+
if (data[prop] === undefined || data[prop] === null) {
126+
return;
127+
}
128+
try {
129+
// If there are any errors in serializing one property, ignore that and move on.
130+
// Else nothing will be sent.
131+
customProperties[prop] =
132+
typeof data[prop] === 'string'
133+
? data[prop]
134+
: typeof data[prop] === 'object'
135+
? 'object'
136+
: data[prop].toString();
137+
} catch (ex) {
138+
traceError(`Failed to serialize ${prop} for ${eventName}`, ex);
163139
}
164140
});
141+
}
142+
143+
// Add shared properties to telemetry props (we may overwrite existing ones).
144+
Object.assign(customProperties, sharedProperties);
145+
146+
// Remove shared DS properties from core extension telemetry.
147+
Object.keys(sharedProperties).forEach((shareProperty) => {
148+
if (
149+
customProperties[shareProperty] &&
150+
shareProperty.startsWith('ds_') &&
151+
!(eventNameSent.startsWith('DS_') || eventNameSent.startsWith('DATASCIENCE'))
152+
) {
153+
delete customProperties[shareProperty];
154+
}
155+
});
165156

157+
if (ex) {
158+
const errorProps = {
159+
errorName: ex.name,
160+
errorMessage: ex.message,
161+
errorStack: ex.stack ?? ''
162+
};
163+
Object.assign(customProperties, errorProps);
164+
165+
// To avoid hardcoding the names and forgetting to update later.
166+
const errorPropNames = Object.getOwnPropertyNames(errorProps);
167+
reporter.sendTelemetryErrorEvent(eventNameSent, customProperties, measures, errorPropNames);
168+
} else {
166169
reporter.sendTelemetryEvent(eventNameSent, customProperties, measures);
167170
}
168171

src/test/telemetry/index.unit.test.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,8 @@ suite('Telemetry', () => {
4646
this.sendTelemetryEvent(eventName, properties, measures);
4747
Reporter.errorProps = errorProps;
4848
}
49-
public sendTelemetryException(error: Error, properties?: {}, measures?: {}): void {
50-
this.sendTelemetryEvent('Exception', properties, measures);
51-
Reporter.exception = error;
49+
public sendTelemetryException(_error: Error, _properties?: {}, _measures?: {}): void {
50+
throw new Error('sendTelemetryException is unsupported');
5251
}
5352
}
5453

@@ -166,16 +165,22 @@ suite('Telemetry', () => {
166165
rewiremock('vscode-extension-telemetry').with({ default: Reporter });
167166

168167
const eventName = 'Testing';
168+
const measures = { start: 123, end: 987 };
169169
const properties = { hello: 'world', foo: 'bar' };
170170

171171
// tslint:disable-next-line:no-any
172-
sendTelemetryEvent(eventName as any, {}, properties as any, error);
172+
sendTelemetryEvent(eventName as any, measures, properties as any, error);
173173

174-
const expectedErrorProperties = {
175-
originalEventName: eventName
174+
const expectedProperties = {
175+
...properties,
176+
errorName: error.name,
177+
errorMessage: error.message,
178+
errorStack: error.stack
176179
};
177180

178-
expect(Reporter.properties).to.deep.equal([expectedErrorProperties]);
179-
expect(Reporter.exception).to.deep.equal(error);
181+
expect(Reporter.eventName).to.deep.equal([eventName]);
182+
expect(Reporter.properties).to.deep.equal([expectedProperties]);
183+
expect(Reporter.measures).to.deep.equal([measures]);
184+
expect(Reporter.errorProps).to.deep.equal(['errorName', 'errorMessage', 'errorStack']);
180185
});
181186
});

0 commit comments

Comments
 (0)