1+ /*---------------------------------------------------------
2+ * Copyright (C) Microsoft Corporation. All rights reserved.
3+ *--------------------------------------------------------*/
4+ // Edited to fix the user id reporting
5+
6+ 'use strict' ;
7+
8+ process . env [ 'APPLICATION_INSIGHTS_NO_DIAGNOSTIC_CHANNEL' ] = 'true' ;
9+
10+ import * as appInsights from 'applicationinsights' ;
11+ import * as fs from 'fs' ;
12+ import * as os from 'os' ;
13+ import * as path from 'path' ;
14+ import * as vscode from 'vscode' ;
15+
16+ export default class TelemetryReporter extends vscode . Disposable {
17+ private appInsightsClient : appInsights . TelemetryClient | undefined ;
18+ private userOptIn : boolean = false ;
19+ private toDispose : vscode . Disposable [ ] = [ ] ;
20+
21+ private static TELEMETRY_CONFIG_ID = 'telemetry' ;
22+ private static TELEMETRY_CONFIG_ENABLED_ID = 'enableTelemetry' ;
23+
24+ private logStream : fs . WriteStream | undefined ;
25+
26+ constructor ( private extensionId : string , private extensionVersion : string , key : string ) {
27+ super ( ( ) => this . toDispose . forEach ( ( d ) => d && d . dispose ( ) ) ) ;
28+ let logFilePath = process . env [ 'VSCODE_LOGS' ] || '' ;
29+ if ( logFilePath && extensionId && process . env [ 'VSCODE_LOG_LEVEL' ] === 'trace' ) {
30+ logFilePath = path . join ( logFilePath , `${ extensionId } .txt` ) ;
31+ this . logStream = fs . createWriteStream ( logFilePath , { flags : 'a' , encoding : 'utf8' , autoClose : true } ) ;
32+ }
33+ this . updateUserOptIn ( key ) ;
34+ this . toDispose . push ( vscode . workspace . onDidChangeConfiguration ( ( ) => this . updateUserOptIn ( key ) ) ) ;
35+ }
36+
37+ private updateUserOptIn ( key : string ) : void {
38+ const config = vscode . workspace . getConfiguration ( TelemetryReporter . TELEMETRY_CONFIG_ID ) ;
39+ if ( this . userOptIn !== config . get < boolean > ( TelemetryReporter . TELEMETRY_CONFIG_ENABLED_ID , true ) ) {
40+ this . userOptIn = config . get < boolean > ( TelemetryReporter . TELEMETRY_CONFIG_ENABLED_ID , true ) ;
41+ if ( this . userOptIn ) {
42+ this . createAppInsightsClient ( key ) ;
43+ } else {
44+ this . dispose ( ) ;
45+ }
46+ }
47+ }
48+
49+ private createAppInsightsClient ( key : string ) {
50+ // Check if another instance is already initialized
51+ if ( appInsights . defaultClient ) {
52+ this . appInsightsClient = new appInsights . TelemetryClient ( key ) ;
53+ // No other way to enable offline mode
54+ this . appInsightsClient . channel . setUseDiskRetryCaching ( true ) ;
55+ } else {
56+ appInsights . setup ( key )
57+ . setAutoCollectRequests ( false )
58+ . setAutoCollectPerformance ( false )
59+ . setAutoCollectExceptions ( false )
60+ . setAutoCollectDependencies ( false )
61+ . setAutoDependencyCorrelation ( false )
62+ . setAutoCollectConsole ( false )
63+ . setUseDiskRetryCaching ( true )
64+ . start ( ) ;
65+ this . appInsightsClient = appInsights . defaultClient ;
66+ }
67+
68+ this . appInsightsClient . commonProperties = this . getCommonProperties ( ) ;
69+
70+ // Add the user and session id's to the context so that the analytics will correctly assign users
71+ if ( vscode && vscode . env ) {
72+ this . appInsightsClient . context . tags [ this . appInsightsClient . context . keys . userId ] = vscode . env . machineId ;
73+ this . appInsightsClient . context . tags [ this . appInsightsClient . context . keys . sessionId ] = vscode . env . sessionId ;
74+ }
75+
76+ // Check if it's an Asimov key to change the endpoint
77+ if ( key && key . indexOf ( 'AIF-' ) === 0 ) {
78+ this . appInsightsClient . config . endpointUrl = 'https://vortex.data.microsoft.com/collect/v1' ;
79+ }
80+ }
81+
82+ // __GDPR__COMMON__ "common.os" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
83+ // __GDPR__COMMON__ "common.platformversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
84+ // __GDPR__COMMON__ "common.extname" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
85+ // __GDPR__COMMON__ "common.extversion" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
86+ // __GDPR__COMMON__ "common.vscodemachineid" : { "endPoint": "MacAddressHash", "classification": "EndUserPseudonymizedInformation", "purpose": "FeatureInsight" }
87+ // __GDPR__COMMON__ "common.vscodesessionid" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
88+ // __GDPR__COMMON__ "common.vscodeversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
89+ private getCommonProperties ( ) : { [ key : string ] : string } {
90+ // tslint:disable-next-line:no-null-keyword
91+ const commonProperties = Object . create ( null ) ;
92+ commonProperties [ 'common.os' ] = os . platform ( ) ;
93+ commonProperties [ 'common.platformversion' ] = ( os . release ( ) || '' ) . replace ( / ^ ( \d + ) ( \. \d + ) ? ( \. \d + ) ? ( .* ) / , '$1$2$3' ) ;
94+ commonProperties [ 'common.extname' ] = this . extensionId ;
95+ commonProperties [ 'common.extversion' ] = this . extensionVersion ;
96+ if ( vscode && vscode . env ) {
97+ commonProperties [ 'common.vscodemachineid' ] = vscode . env . machineId ;
98+ commonProperties [ 'common.vscodesessionid' ] = vscode . env . sessionId ;
99+ commonProperties [ 'common.vscodeversion' ] = vscode . version ;
100+ }
101+ return commonProperties ;
102+ }
103+
104+ public sendTelemetryEvent ( eventName : string , properties ?: { [ key : string ] : string } , measurements ?: { [ key : string ] : number } ) : void {
105+ if ( this . userOptIn && eventName && this . appInsightsClient ) {
106+ this . appInsightsClient . trackEvent ( {
107+ name : `${ this . extensionId } /${ eventName } ` ,
108+ properties : properties ,
109+ measurements : measurements
110+ } ) ;
111+
112+ if ( this . logStream ) {
113+ this . logStream . write ( `telemetry/${ eventName } ${ JSON . stringify ( { properties, measurements } ) } \n` ) ;
114+ }
115+ }
116+ }
117+
118+ public dispose ( ) : Promise < any > {
119+ const flushEventsToLogger = new Promise < any > ( resolve => {
120+ if ( ! this . logStream ) {
121+ return resolve ( void 0 ) ;
122+ }
123+ this . logStream . on ( 'finish' , resolve ) ;
124+ this . logStream . end ( ) ;
125+ } ) ;
126+
127+ const flushEventsToAI = new Promise < any > ( resolve => {
128+ if ( this . appInsightsClient ) {
129+ this . appInsightsClient . flush ( {
130+ callback : ( ) => {
131+ // All data flushed
132+ this . appInsightsClient = undefined ;
133+ resolve ( void 0 ) ;
134+ }
135+ } ) ;
136+ } else {
137+ resolve ( void 0 ) ;
138+ }
139+ } ) ;
140+ return Promise . all ( [ flushEventsToAI , flushEventsToLogger ] ) ;
141+ }
142+ }
0 commit comments