Clean up extension telemetry (#5596)

This commit is contained in:
Charles Gagnon
2019-05-23 15:29:28 -07:00
committed by GitHub
parent 776e2cf6e7
commit fbbf767700
3 changed files with 53 additions and 32 deletions

View File

@@ -8,7 +8,7 @@ import * as path from 'path';
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { Telemetry } from './telemetry';
import { doubleEscapeSingleQuotes, backEscapeDoubleQuotes } from './utils';
import { doubleEscapeSingleQuotes, backEscapeDoubleQuotes, getTelemetryErrorType } from './utils';
import { ChildProcess, exec } from 'child_process';
const localize = nls.loadMessageBundle();
const ssmsMinVer = JSON.parse(JSON.stringify(require('./config.json'))).version;
@@ -66,7 +66,6 @@ export interface LaunchSsmsDialogParams {
export async function activate(context: vscode.ExtensionContext): Promise<void> {
// This is for Windows-specific support so do nothing on other platforms
if (process.platform === 'win32') {
Telemetry.sendTelemetryEvent('startup/ExtensionActivated');
exePath = path.join(context.extensionPath, 'ssmsmin', 'Windows', ssmsMinVer, 'ssmsmin.exe');
registerCommands(context);
}
@@ -76,7 +75,7 @@ export async function deactivate(): Promise<void> {
// If the extension is being deactivated we want to kill all processes that are still currently
// running otherwise they will continue to run as orphan processes. We use taskkill here in case
// they started off child processes of their own
runningProcesses.forEach(p => exec('taskkill /pid ' + p.pid + ' /T /F'));
runningProcesses.forEach(p => exec(`taskkill /pid ${p.pid} /T /F`));
}
/**
@@ -96,6 +95,7 @@ function registerCommands(context: vscode.ExtensionContext): void {
*/
async function handleLaunchSsmsMinPropertiesDialogCommand(connectionContext?: azdata.ObjectExplorerContext): Promise<void> {
if (!connectionContext) {
Telemetry.sendTelemetryEventForError('NoConnectionContext', { action: 'Properties' });
vscode.window.showErrorMessage(localize('adminToolExtWin.noConnectionContextForProp', 'No ConnectionContext provided for handleLaunchSsmsMinPropertiesDialogCommand'));
return;
}
@@ -106,9 +106,9 @@ async function handleLaunchSsmsMinPropertiesDialogCommand(connectionContext?: az
}
else if (connectionContext.nodeInfo) {
nodeType = connectionContext.nodeInfo.nodeType;
}
else {
vscode.window.showErrorMessage(localize('adminToolExtWin.noOeNode', 'Could not determine NodeType for handleLaunchSsmsMinPropertiesDialogCommand with context {0}', JSON.stringify(connectionContext)));
} else {
Telemetry.sendTelemetryEventForError('NoOENode', { action: 'Properties' });
vscode.window.showErrorMessage(localize('adminToolExtWin.noOENode', 'Could not determine Object Explorer node from connectionContext : {0}', JSON.stringify(connectionContext)));
return;
}
@@ -122,12 +122,14 @@ async function handleLaunchSsmsMinPropertiesDialogCommand(connectionContext?: az
* @param connectionId The connection context from the command
*/
async function handleLaunchSsmsMinGswDialogCommand(connectionContext?: azdata.ObjectExplorerContext): Promise<void> {
const action = 'GenerateScripts';
if (!connectionContext) {
Telemetry.sendTelemetryEventForError('NoConnectionContext', { action: action });
vscode.window.showErrorMessage(localize('adminToolExtWin.noConnectionContextForGsw', 'No ConnectionContext provided for handleLaunchSsmsMinPropertiesDialogCommand'));
}
launchSsmsDialog(
'GenerateScripts',
action,
connectionContext);
}
@@ -139,6 +141,7 @@ async function handleLaunchSsmsMinGswDialogCommand(connectionContext?: azdata.Ob
*/
async function launchSsmsDialog(action: string, connectionContext: azdata.ObjectExplorerContext): Promise<void> {
if (!connectionContext.connectionProfile) {
Telemetry.sendTelemetryEventForError('NoConnectionProfile', { action: action });
vscode.window.showErrorMessage(localize('adminToolExtWin.noConnectionProfile', 'No connectionProfile provided from connectionContext : {0}', JSON.stringify(connectionContext)));
return;
}
@@ -152,6 +155,7 @@ async function launchSsmsDialog(action: string, connectionContext: azdata.Object
oeNode = await azdata.objectexplorer.getNode(connectionContext.connectionProfile.id, connectionContext.nodeInfo.nodePath);
}
else {
Telemetry.sendTelemetryEventForError('NoOENode', { action: action });
vscode.window.showErrorMessage(localize('adminToolExtWin.noOENode', 'Could not determine Object Explorer node from connectionContext : {0}', JSON.stringify(connectionContext)));
return;
}
@@ -175,7 +179,12 @@ async function launchSsmsDialog(action: string, connectionContext: azdata.Object
const args = buildSsmsMinCommandArgs(params);
Telemetry.sendTelemetryEvent('LaunchSsmsDialog', { 'action': action });
Telemetry.sendTelemetryEvent('LaunchSsmsDialog',
{
action: action,
nodeType: oeNode ? oeNode.nodeType : 'Server',
authType: connectionContext.connectionProfile.authenticationType
});
vscode.window.setStatusBarMessage(localize('adminToolExtWin.launchingDialogStatus', 'Launching dialog...'), 3000);
@@ -186,11 +195,12 @@ async function launchSsmsDialog(action: string, connectionContext: azdata.Object
(execException, stdout, stderr) => {
// Process has exited so remove from map of running processes
runningProcesses.delete(proc.pid);
const err = stderr.toString();
Telemetry.sendTelemetryEvent('LaunchSsmsDialogResult', {
'action': params.action,
'returnCode': execException && execException.code ? execException.code.toString() : '0'
action: params.action,
returnCode: execException && execException.code ? execException.code.toString() : '0',
errorType: getTelemetryErrorType(err)
});
let err = stderr.toString();
if (err !== '') {
vscode.window.showErrorMessage(localize(
'adminToolExtWin.ssmsMinError',

View File

@@ -62,27 +62,11 @@ export class Telemetry {
}
/**
* Send a telemetry event for an exception
* Send a telemetry event for a general error
* @param err The error to log
*/
public static sendTelemetryEventForException(
err: any, methodName: string, extensionConfigName: string): void {
try {
let stackArray: string[];
let firstLine: string = '';
if (err !== undefined && err.stack !== undefined) {
stackArray = err.stack.split('\n');
if (stackArray !== undefined && stackArray.length >= 2) {
firstLine = stackArray[1]; // The first line is the error message and we don't want to send that telemetry event
firstLine = filterErrorPath(firstLine);
}
}
// Only adding the method name and the fist line of the stack trace. We don't add the error message because it might have PII
this.sendTelemetryEvent('Exception', { methodName: methodName, errorLine: firstLine });
} catch (telemetryErr) {
// If sending telemetry event fails ignore it so it won't break the extension
console.error('Failed to send telemetry event. error: ' + telemetryErr, extensionConfigName);
}
public static sendTelemetryEventForError(err: string, properties?: ITelemetryEventProperties): void {
this.sendTelemetryEvent('Error', { error: err, ...properties });
}
/**
@@ -106,7 +90,12 @@ export class Telemetry {
properties = {};
}
this.reporter.sendTelemetryEvent(eventName, properties, measures);
try {
this.reporter.sendTelemetryEvent(eventName, properties, measures);
} catch (telemetryErr) {
// If sending telemetry event fails ignore it so it won't break the extension
console.error('Failed to send telemetry event. error: ' + telemetryErr);
}
}
}

View File

@@ -36,4 +36,26 @@ export function doubleEscapeSingleQuotes(value: string): string {
*/
export function backEscapeDoubleQuotes(value: string): string {
return value.replace(/"/g, '\\"');
}
/**
* Map an error message into a GDPR-Compliant short name for the type of error.
* @param msg The error message to map
*/
export function getTelemetryErrorType(msg: string): string {
if (msg.indexOf('is not recognized as an internal or external command') !== -1) {
return 'ExeNotFound';
}
else if (msg.indexOf('Unknown Action') !== -1) {
return 'UnknownAction';
}
else if (msg.indexOf('No Action Provided') !== -1) {
return 'NoActionProvided';
}
else if (msg.indexOf('Run exception') !== -1) {
return 'RunException';
}
else {
return 'Other';
}
}