UNPKG

8.19 kBJavaScriptView Raw
1//
2// Copyright (c) Microsoft and contributors. All rights reserved.
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//
13// See the License for the specific language governing permissions and
14// limitations under the License.
15//
16
17var os = require('os');
18var fs = require('fs');
19var crypto = require('crypto');
20var _appInsights = require('applicationinsights');
21var profile = require('./profile');
22var utilsCore = require('./utilsCore');
23var Constants = require('./constants');
24var userAgentCore = require('./userAgentCore');
25
26var _event;
27var _isEnabled = false;
28var _currentCommand;
29var _rawCommand;
30var _subscription;
31
32var _Data = (function() {
33 var Data = function () {
34 };
35 return Data;
36})();
37
38var _PageViewData = (function() {
39 var PageViewData = function () {
40 this.ver = 2;
41 this.properties = {};
42 this.measurements = {};
43 };
44 return PageViewData;
45})();
46
47var _AzureCliQosEvent = function() {
48 return {
49 startTime: Date.now(),
50 duration: 0,
51 isSuccess: true,
52 commandName: '',
53 command: '',
54 mode: '', // arm or asm
55 nodeVersion: '',
56 userId: '',
57 userType: '',
58 installationType: 'NONE', //NONE, INSTALLER or NPM
59 osType: os && os.type(),
60 osVersion: os && os.release()
61 };
62};
63
64var _getInstallationType = function (command) {
65 var type = 'NONE';
66 var osType = os.type();
67 if (osType === 'Windows_NT') {
68 if (command) {
69 type = command.indexOf('Microsoft SDKs\\Azure') > -1 ? 'INSTALLER' : 'NPM';
70 }
71 } else if (osType === 'Darwin') {
72 try {
73 // If azure-cli is installed using npm, '/usr/local/bin/azure' is a symbolic link to node_modules
74 // If installed by installer, '/usr/local/bin/azure' is an executable file instead.
75 var lstat = fs.lstatSync('/usr/local/bin/azure');
76 type = lstat.isSymbolicLink() ? 'NPM' : 'INSTALLER';
77 } catch (e) {
78 // Not able to figure out installation type.
79 }
80 } else {
81 // On Linux, no installer provided currently.
82 type = 'NPM';
83 }
84 return type;
85};
86
87var _getCurrentSubscription = function() {
88 var thumbprint = function(cert) {
89 if (!cert) {
90 // default value
91 return 'none';
92 }
93 return crypto.createHash('sha1')
94 .update(new Buffer(cert, 'base64').toString('binary'))
95 .digest('hex');
96 };
97
98 var subscription = {
99 id: 'none',
100 user: {
101 id: 'none',
102 type: 'none'
103 }
104 };
105
106 var sub;
107 try {
108 sub = profile.current.getSubscription();
109 } catch (e) {
110
111 }
112
113 if (sub) {
114 subscription.id = sub.id;
115 if (sub.user) {
116 subscription.user.id = crypto.createHash('sha256').update(sub.user.name).digest('hex');
117 subscription.user.type = sub.user.type;
118 } else if (sub.managementCertificate) {
119 subscription.user.id = thumbprint(sub.managementCertificate.cert);
120 subscription.user.type = 'managementCertificate';
121 }
122 }
123 return subscription;
124};
125
126var _filterCommand = function (commandName, rawCommand) {
127 var outCmd = '';
128 if (rawCommand && commandName) {
129 outCmd = commandName;
130 // Starting from 3rd argv is the command
131 var filterStartIndex = 2 + outCmd.split(/\s+/).length;
132 for (var i = filterStartIndex; i < rawCommand.length; i++) {
133 var token = rawCommand[i];
134 if (!utilsCore.stringStartsWith(token, '-')) {
135 token = (token.length > 40) ? '***' : token.replace(/./g, '*');
136 }
137 outCmd += ' ' + token;
138 }
139 }
140 return outCmd;
141};
142
143var _stop = function (qosEvent) {
144 if (qosEvent) {
145 qosEvent.duration = Date.now() - qosEvent.startTime;
146 }
147};
148
149// helper for Application Insights
150var _msToTimeSpan = function (totalms) {
151 if (isNaN(totalms) || totalms < 0) {
152 totalms = 0;
153 }
154 var ms = '' + totalms % 1000;
155 var sec = '' + Math.floor(totalms / 1000) % 60;
156 var min = '' + Math.floor(totalms / (1000 * 60)) % 60;
157 var hour = '' + Math.floor(totalms / (1000 * 60 * 60)) % 24;
158 ms = ms.length === 1 ? '00' + ms : ms.length === 2 ? '0' + ms : ms;
159 sec = sec.length < 2 ? '0' + sec : sec;
160 min = min.length < 2 ? '0' + min : min;
161 hour = hour.length < 2 ? '0' + hour : hour;
162 return hour + ':' + min + ':' + sec + '.' + ms;
163};
164
165var _trackPageView = function (data) {
166 var pageView = new _PageViewData();
167 pageView.name = data.commandName;
168 if (!isNaN(data.duration)) {
169 pageView.duration = _msToTimeSpan(data.duration);
170 }
171 pageView.properties = data;
172 var _data = new _Data();
173 _data.baseType = 'PageViewData';
174 _data.baseData = pageView;
175 _appInsights.client.track(_data);
176};
177
178var _flush = function (callback) {
179 if (_isEnabled) {
180 _appInsights.client.sendPendingData(callback);
181 }
182};
183
184var _stripUsername = function(str) {
185 if (str) {
186 var re = /(.*users[\\|\/])(.*?)([\\|\/].*)/gi;
187 return str.replace(re, '$1***$3');
188 } else {
189 return str;
190 }
191};
192
193 /*
194 * initialize app insights telemetry only if telemetry is
195 * enabled by the end user. But do populate common info which is
196 * required to construct user agent string for request headers.
197 */
198exports.init = function (isEnabled) {
199 _isEnabled = isEnabled;
200 _subscription = _getCurrentSubscription();
201
202 if (_isEnabled) {
203 _appInsights.setup(Constants.TELEMETRY_INSTRUMENTATION_KEY)
204 .setAutoCollectRequests(false)
205 .setAutoCollectPerformance(false)
206 .setAutoCollectExceptions(false);
207
208 // Overwrite appinsight default values.
209 var context = _appInsights.client.context;
210 context.tags[context.keys.userId] = _subscription.user.id;
211
212 _appInsights.start();
213 }
214};
215
216exports.currentCommand = function (command) {
217 if (command && typeof command === 'object') {
218 _currentCommand = command;
219
220 if (_event) {
221 _event.commandName = command.fullName();
222 _event.command = _filterCommand(_event.commandName, _rawCommand);
223
224 // update user agent info about the current command
225 userAgentCore.setCommandInfo(_event.commandName, _filterCommand(' ', _rawCommand));
226 }
227 }
228};
229
230 /*
231 * populate command info and system diagnostics. This data is common
232 * to app insights telemetry and user agent string that goes into the request headers.
233 */
234exports.start = function (command) {
235 if (command) {
236 _rawCommand = command;
237 }
238 _event = _AzureCliQosEvent();
239 _event.installationType = _getInstallationType(command);
240 _event.nodeVersion = process.version;
241 _event['Azure.Subscription.Id'] = _subscription.id;
242 _event.userId = _subscription.user.id;
243 _event.userType = _subscription.user.type;
244
245 // set user agent information.
246 userAgentCore.setUserAgentData(constructUserAgentData());
247};
248
249exports.setAppInsights = function (appInsights) {
250 _appInsights = appInsights;
251};
252
253exports.setMode = function (mode) {
254 if (_event) {
255 _event.mode = mode;
256
257 // update user agent info.
258 userAgentCore.setMode(mode);
259 }
260};
261
262exports.onError = function (err, callback) {
263 if (_isEnabled && _event) {
264 _stop(_event);
265 _event.isSuccess = false;
266 _event.stacktrace = _stripUsername(err.stack);
267 _event.errorCategory = err.statusCode ? 'HTTP_Error_' + err.statusCode : 'CLI_Error';
268 _appInsights.client.trackEvent('CmdletError', _event);
269 _flush(callback);
270 } else {
271 callback();
272 }
273};
274
275 /*
276 * report telemetry only if it is enabled.
277 */
278exports.onFinish = function (callback) {
279 if (_isEnabled && _event) {
280 _stop(_event);
281 _trackPageView(_event);
282 _flush(callback);
283 } else {
284 callback();
285 }
286};
287
288 /*
289 * construct user agent info for request header.
290 */
291function constructUserAgentData() {
292 if (_event) {
293 return {
294 osType: _event.osType,
295 osVersion: _event.osVersion,
296 nodeVersion: _event.nodeVersion,
297 installationType: _event.installationType,
298 userId: _event.userId,
299 subscriptionId: _event['Azure.Subscription.Id'],
300 userType: _event.userType
301 };
302 }
303}