1
1
// Copyright (c) Microsoft Corporation. All rights reserved.
2
2
// Licensed under the MIT License.
3
-
3
+ // tslint:disable:no-reference no-any import-name no-any function-name
4
+ /// <reference path="./vscode-extension-telemetry.d.ts" />
4
5
import type { JSONObject } from '@phosphor/coreutils' ;
6
+ import { basename as pathBasename , sep as pathSep } from 'path' ;
5
7
import * as stackTrace from 'stack-trace' ;
6
- // tslint:disable-next-line: import-name
7
- import TelemetryReporter from 'vscode-extension-telemetry/lib/telemetryReporter' ;
8
+ import TelemetryReporter from 'vscode-extension-telemetry' ;
8
9
9
10
import { DiagnosticCodes } from '../application/diagnostics/constants' ;
10
11
import { IWorkspaceService } from '../common/application/types' ;
11
- import { AppinsightsKey , isTestExecution , PVSC_EXTENSION_ID } from '../common/constants' ;
12
+ import { AppinsightsKey , EXTENSION_ROOT_DIR , isTestExecution , PVSC_EXTENSION_ID } from '../common/constants' ;
12
13
import { traceError , traceInfo } from '../common/logger' ;
13
14
import { TerminalShellType } from '../common/terminal/types' ;
14
15
import { StopWatch } from '../common/utils/stopWatch' ;
@@ -27,12 +28,10 @@ import { TestProvider } from '../testing/common/types';
27
28
import { EventName , PlatformErrors } from './constants' ;
28
29
import { LinterTrigger , TestTool } from './types' ;
29
30
30
- // tslint:disable: no-any
31
-
32
31
/**
33
32
* Checks whether telemetry is supported.
34
33
* Its possible this function gets called within Debug Adapter, vscode isn't available in there.
35
- * Within DA, there's a completely different way to send telemetry.
34
+ * Withiin DA, there's a completely different way to send telemetry.
36
35
* @returns {boolean }
37
36
*/
38
37
function isTelemetrySupported ( ) : boolean {
@@ -64,12 +63,14 @@ function getTelemetryReporter() {
64
63
const extensionId = PVSC_EXTENSION_ID ;
65
64
// tslint:disable-next-line:no-require-imports
66
65
const extensions = ( require ( 'vscode' ) as typeof import ( 'vscode' ) ) . extensions ;
66
+ // tslint:disable-next-line:no-non-null-assertion
67
67
const extension = extensions . getExtension ( extensionId ) ! ;
68
+ // tslint:disable-next-line:no-unsafe-any
68
69
const extensionVersion = extension . packageJSON . version ;
69
70
70
71
// tslint:disable-next-line:no-require-imports
71
72
const reporter = require ( 'vscode-extension-telemetry' ) . default as typeof TelemetryReporter ;
72
- return ( telemetryReporter = new reporter ( extensionId , extensionVersion , AppinsightsKey , true ) ) ;
73
+ return ( telemetryReporter = new reporter ( extensionId , extensionVersion , AppinsightsKey ) ) ;
73
74
}
74
75
75
76
export function clearTelemetryReporter ( ) {
@@ -87,46 +88,45 @@ export function sendTelemetryEvent<P extends IEventNamePropertyMapping, E extend
87
88
}
88
89
const reporter = getTelemetryReporter ( ) ;
89
90
const measures = typeof durationMs === 'number' ? { duration : durationMs } : durationMs ? durationMs : undefined ;
90
- let customProperties : Record < string , string > = { } ;
91
- let eventNameSent = eventName as string ;
92
91
93
- if ( ex ) {
94
- // When sending telemetry events for exceptions no need to send custom properties.
92
+ if ( ex && ( eventName as any ) !== 'ERROR' ) {
93
+ // When sending `ERROR` telemetry event no need to send custom properties.
95
94
// Else we have to review all properties every time as part of GDPR.
96
95
// Assume we have 10 events all with their own properties.
97
96
// As we have errors for each event, those properties are treated as new data items.
98
97
// Hence they need to be classified as part of the GDPR process, and thats unnecessary and onerous.
99
- eventNameSent = 'ERROR' ;
100
- customProperties = { originalEventName : eventName as string , stackTrace : serializeStackTrace ( ex ) } ;
101
- reporter . sendTelemetryErrorEvent ( eventNameSent , customProperties , measures , [ ] ) ;
102
- } else {
103
- if ( properties ) {
104
- const data = properties as any ;
105
- Object . getOwnPropertyNames ( data ) . forEach ( ( prop ) => {
106
- if ( data [ prop ] === undefined || data [ prop ] === null ) {
107
- return ;
108
- }
109
- try {
110
- // If there are any errors in serializing one property, ignore that and move on.
111
- // Else nothing will be sent.
112
- customProperties [ prop ] =
113
- typeof data [ prop ] === 'string'
114
- ? data [ prop ]
115
- : typeof data [ prop ] === 'object'
116
- ? 'object'
117
- : data [ prop ] . toString ( ) ;
118
- } catch ( ex ) {
119
- traceError ( `Failed to serialize ${ prop } for ${ eventName } ` , ex ) ;
120
- }
121
- } ) ;
122
- }
123
-
124
- reporter . sendTelemetryEvent ( eventNameSent , customProperties , measures ) ;
98
+ const props : Record < string , string > = { } ;
99
+ props . stackTrace = getStackTrace ( ex ) ;
100
+ props . originalEventName = ( eventName as any ) as string ;
101
+ reporter . sendTelemetryEvent ( 'ERROR' , props , measures ) ;
125
102
}
126
-
103
+ const customProperties : Record < string , string > = { } ;
104
+ if ( properties ) {
105
+ // tslint:disable-next-line:prefer-type-cast no-any
106
+ const data = properties as any ;
107
+ Object . getOwnPropertyNames ( data ) . forEach ( ( prop ) => {
108
+ if ( data [ prop ] === undefined || data [ prop ] === null ) {
109
+ return ;
110
+ }
111
+ try {
112
+ // If there are any errors in serializing one property, ignore that and move on.
113
+ // Else nothign will be sent.
114
+ // tslint:disable-next-line:prefer-type-cast no-any no-unsafe-any
115
+ ( customProperties as any ) [ prop ] =
116
+ typeof data [ prop ] === 'string'
117
+ ? data [ prop ]
118
+ : typeof data [ prop ] === 'object'
119
+ ? 'object'
120
+ : data [ prop ] . toString ( ) ;
121
+ } catch ( ex ) {
122
+ traceError ( `Failed to serialize ${ prop } for ${ eventName } ` , ex ) ;
123
+ }
124
+ } ) ;
125
+ }
126
+ reporter . sendTelemetryEvent ( ( eventName as any ) as string , customProperties , measures ) ;
127
127
if ( process . env && process . env . VSC_PYTHON_LOG_TELEMETRY ) {
128
128
traceInfo (
129
- `Telemetry Event : ${ eventNameSent } Measures: ${ JSON . stringify ( measures ) } Props: ${ JSON . stringify (
129
+ `Telemetry Event : ${ eventName } Measures: ${ JSON . stringify ( measures ) } Props: ${ JSON . stringify (
130
130
customProperties
131
131
) } `
132
132
) ;
@@ -246,12 +246,32 @@ export function sendTelemetryWhenDone<P extends IEventNamePropertyMapping, E ext
246
246
}
247
247
}
248
248
249
- function serializeStackTrace ( ex : Error ) : string {
250
- // We aren't showing the error message (ex.message) since it might contain PII.
249
+ function sanitizeFilename ( filename : string ) : string {
250
+ if ( filename . startsWith ( EXTENSION_ROOT_DIR ) ) {
251
+ filename = `<pvsc>${ filename . substring ( EXTENSION_ROOT_DIR . length ) } ` ;
252
+ } else {
253
+ // We don't really care about files outside our extension.
254
+ filename = `<hidden>${ pathSep } ${ pathBasename ( filename ) } ` ;
255
+ }
256
+ return filename ;
257
+ }
258
+
259
+ function sanitizeName ( name : string ) : string {
260
+ if ( name . indexOf ( '/' ) === - 1 && name . indexOf ( '\\' ) === - 1 ) {
261
+ return name ;
262
+ } else {
263
+ return '<hidden>' ;
264
+ }
265
+ }
266
+
267
+ function getStackTrace ( ex : Error ) : string {
268
+ // We aren't showing the error message (ex.message) since it might
269
+ // contain PII.
251
270
let trace = '' ;
252
271
for ( const frame of stackTrace . parse ( ex ) ) {
253
- const filename = frame . getFileName ( ) ;
272
+ let filename = frame . getFileName ( ) ;
254
273
if ( filename ) {
274
+ filename = sanitizeFilename ( filename ) ;
255
275
const lineno = frame . getLineNumber ( ) ;
256
276
const colno = frame . getColumnNumber ( ) ;
257
277
trace += `\n\tat ${ getCallsite ( frame ) } ${ filename } :${ lineno } :${ colno } ` ;
@@ -277,7 +297,7 @@ function getCallsite(frame: stackTrace.StackFrame) {
277
297
parts . push ( frame . getFunctionName ( ) ) ;
278
298
}
279
299
}
280
- return parts . join ( '.' ) ;
300
+ return parts . map ( sanitizeName ) . join ( '.' ) ;
281
301
}
282
302
283
303
// Map all events to their properties
0 commit comments