Skip to content

Commit db0d41a

Browse files
Introduce showAndLogExceptionWithTelemetry
1 parent 07f5d2b commit db0d41a

22 files changed

Lines changed: 338 additions & 111 deletions

extensions/ql-vscode/src/astViewer.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ import {
2525
} from "./pure/bqrs-utils";
2626
import { commandRunner } from "./commandRunner";
2727
import { DisposableObject } from "./pure/disposable-object";
28-
import { showAndLogErrorMessage } from "./helpers";
28+
import { showAndLogExceptionWithTelemetry } from "./helpers";
29+
import { asError } from "./pure/helpers-pure";
2930

3031
export interface AstItem {
3132
id: BqrsId;
@@ -146,7 +147,8 @@ export class AstViewer extends DisposableObject {
146147
() => {
147148
/**/
148149
},
149-
(err) => showAndLogErrorMessage(err),
150+
(error: unknown) =>
151+
showAndLogExceptionWithTelemetry(asError(error), "AST_viewer_reveal"),
150152
);
151153
}
152154

@@ -204,7 +206,11 @@ export class AstViewer extends DisposableObject {
204206
() => {
205207
/**/
206208
},
207-
(err) => showAndLogErrorMessage(err),
209+
(error: unknown) =>
210+
showAndLogExceptionWithTelemetry(
211+
asError(error),
212+
"AST_viewer_reveal",
213+
),
208214
);
209215
}
210216
}

extensions/ql-vscode/src/commandRunner.ts

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import {
66
Disposable,
77
ProgressLocation,
88
} from "vscode";
9-
import { showAndLogErrorMessage, showAndLogWarningMessage } from "./helpers";
9+
import {
10+
showAndLogExceptionWithTelemetry,
11+
showAndLogWarningMessage,
12+
} from "./helpers";
1013
import { extLogger } from "./common";
1114
import { asError, getErrorMessage, getErrorStack } from "./pure/helpers-pure";
1215
import { telemetryListener } from "./telemetry";
@@ -125,23 +128,27 @@ export function commandRunner(
125128
try {
126129
return await task(...args);
127130
} catch (e) {
128-
const errorMessage = `${getErrorMessage(e) || e} (${commandId})`;
131+
const notificationMessage = `${getErrorMessage(e) || e} (${commandId})`;
129132
error = asError(e);
130133
const errorStack = getErrorStack(e);
131134
if (e instanceof UserCancellationException) {
132135
// User has cancelled this action manually
133136
if (e.silent) {
134-
void extLogger.log(errorMessage);
137+
void extLogger.log(notificationMessage);
135138
} else {
136-
void showAndLogWarningMessage(errorMessage);
139+
void showAndLogWarningMessage(notificationMessage);
137140
}
138141
} else {
139142
// Include the full stack in the error log only.
140143
const fullMessage = errorStack
141-
? `${errorMessage}\n${errorStack}`
142-
: errorMessage;
143-
void showAndLogErrorMessage(errorMessage, {
144+
? `${notificationMessage}\n${errorStack}`
145+
: notificationMessage;
146+
void showAndLogExceptionWithTelemetry(error, "command_failed", {
147+
notificationMessage,
144148
fullMessage,
149+
extraTelemetryProperties: {
150+
command: commandId,
151+
},
145152
});
146153
}
147154
return undefined;
@@ -178,24 +185,28 @@ export function commandRunnerWithProgress<R>(
178185
try {
179186
return await withProgress(progressOptionsWithDefaults, task, ...args);
180187
} catch (e) {
181-
const errorMessage = `${getErrorMessage(e) || e} (${commandId})`;
188+
const notificationMessage = `${getErrorMessage(e) || e} (${commandId})`;
182189
error = asError(e);
183190
const errorStack = getErrorStack(e);
184191
if (e instanceof UserCancellationException) {
185192
// User has cancelled this action manually
186193
if (e.silent) {
187-
void outputLogger.log(errorMessage);
194+
void outputLogger.log(notificationMessage);
188195
} else {
189-
void showAndLogWarningMessage(errorMessage, { outputLogger });
196+
void showAndLogWarningMessage(notificationMessage, { outputLogger });
190197
}
191198
} else {
192199
// Include the full stack in the error log only.
193200
const fullMessage = errorStack
194-
? `${errorMessage}\n${errorStack}`
195-
: errorMessage;
196-
void showAndLogErrorMessage(errorMessage, {
201+
? `${notificationMessage}\n${errorStack}`
202+
: notificationMessage;
203+
void showAndLogExceptionWithTelemetry(error, "command_failed", {
197204
outputLogger,
205+
notificationMessage,
198206
fullMessage,
207+
extraTelemetryProperties: {
208+
command: commandId,
209+
},
199210
});
200211
}
201212
return undefined;

extensions/ql-vscode/src/contextual/queryResolver.ts

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import {
77
getPrimaryDbscheme,
88
getQlPackForDbscheme,
99
getOnDiskWorkspaceFolders,
10-
showAndLogErrorMessage,
1110
QlPacksForLanguage,
11+
showAndLogExceptionWithTelemetry,
1212
} from "../helpers";
1313
import { KeyType, kindOfKeyType, nameOfKeyType, tagOfKeyType } from "./keyType";
1414
import { CodeQLCliServer } from "../cli";
@@ -88,22 +88,17 @@ export async function resolveQueries(
8888
}
8989

9090
// No queries found. Determine the correct error message for the various scenarios.
91-
const errorMessage = `No ${nameOfKeyType(
92-
keyType,
93-
)} queries (tagged "${tagOfKeyType(
94-
keyType,
95-
)}") could be found in the current library path. \
96-
Try upgrading the CodeQL libraries. If that doesn't work, then ${nameOfKeyType(
97-
keyType,
98-
)} queries are not yet available \
91+
const keyTypeName = nameOfKeyType(keyType);
92+
const keyTypeTag = tagOfKeyType(keyType);
93+
const joinedPacksToSearch = packsToSearch.join(", ");
94+
const errorMessage = `No ${keyTypeName} queries (tagged "${keyTypeTag}") could be found in the \
95+
current library path (tried searching the following packs: ${joinedPacksToSearch}). \
96+
Try upgrading the CodeQL libraries. If that doesn't work, then ${keyTypeName} queries are not yet available \
9997
for this language.`;
10098

101-
void showAndLogErrorMessage(errorMessage);
102-
throw new Error(
103-
`Couldn't find any queries tagged ${tagOfKeyType(
104-
keyType,
105-
)} in any of the following packs: ${packsToSearch.join(", ")}.`,
106-
);
99+
const e = new Error(errorMessage);
100+
void showAndLogExceptionWithTelemetry(e, "resolve_queries");
101+
throw e;
107102
}
108103

109104
async function resolveContextualQuery(

extensions/ql-vscode/src/databases-ui.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,15 @@ import {
2929
isLikelyDatabaseRoot,
3030
isLikelyDbLanguageFolder,
3131
showAndLogErrorMessage,
32+
showAndLogExceptionWithTelemetry,
3233
} from "./helpers";
3334
import { extLogger } from "./common";
3435
import {
3536
importArchiveDatabase,
3637
promptImportGithubDatabase,
3738
promptImportInternetDatabase,
3839
} from "./databaseFetcher";
39-
import { asyncFilter, getErrorMessage } from "./pure/helpers-pure";
40+
import { asError, asyncFilter, getErrorMessage } from "./pure/helpers-pure";
4041
import { QueryRunner } from "./queryRunner";
4142
import { isCanary } from "./config";
4243
import { App } from "./common/app";
@@ -344,7 +345,10 @@ export class DatabaseUI extends DisposableObject {
344345
try {
345346
await this.chooseAndSetDatabase(true, progress, token);
346347
} catch (e) {
347-
void showAndLogErrorMessage(getErrorMessage(e));
348+
void showAndLogExceptionWithTelemetry(
349+
asError(e),
350+
"databases_ui_choose_and_set_database",
351+
);
348352
}
349353
};
350354

@@ -393,6 +397,10 @@ export class DatabaseUI extends DisposableObject {
393397
void extLogger.log(`Deleting orphaned database '${dbDir}'.`);
394398
await remove(dbDir);
395399
} catch (e) {
400+
void showAndLogExceptionWithTelemetry(
401+
asError(e),
402+
"databases_ui_remove_orphaned_database",
403+
);
396404
failures.push(`${basename(dbDir)}`);
397405
}
398406
}),
@@ -414,8 +422,11 @@ export class DatabaseUI extends DisposableObject {
414422
): Promise<void> => {
415423
try {
416424
await this.chooseAndSetDatabase(false, progress, token);
417-
} catch (e) {
418-
void showAndLogErrorMessage(getErrorMessage(e));
425+
} catch (e: unknown) {
426+
void showAndLogExceptionWithTelemetry(
427+
asError(e),
428+
"databases_ui_choose_and_set_database",
429+
);
419430
}
420431
};
421432

extensions/ql-vscode/src/databases.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import * as vscode from "vscode";
55
import * as cli from "./cli";
66
import { ExtensionContext } from "vscode";
77
import {
8-
showAndLogErrorMessage,
98
showAndLogWarningMessage,
109
showAndLogInformationMessage,
1110
isLikelyDatabaseRoot,
11+
showAndLogExceptionWithTelemetry,
1212
} from "./helpers";
1313
import { ProgressCallback, withProgress } from "./commandRunner";
1414
import {
@@ -794,8 +794,14 @@ export class DatabaseManager extends DisposableObject {
794794
await this.updatePersistedDatabaseList();
795795
} catch (e) {
796796
// database list had an unexpected type - nothing to be done?
797-
void showAndLogErrorMessage(
798-
`Database list loading failed: ${getErrorMessage(e)}`,
797+
void showAndLogExceptionWithTelemetry(
798+
asError(e),
799+
"databases_load_persisted_state",
800+
{
801+
notificationMessage: `Database list loading failed: ${getErrorMessage(
802+
e,
803+
)}`,
804+
},
799805
);
800806
}
801807

extensions/ql-vscode/src/eval-log-viewer.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import {
1010
} from "vscode";
1111
import { commandRunner } from "./commandRunner";
1212
import { DisposableObject } from "./pure/disposable-object";
13-
import { showAndLogErrorMessage } from "./helpers";
13+
import { showAndLogExceptionWithTelemetry } from "./helpers";
14+
import { asError } from "./pure/helpers-pure";
1415

1516
export interface EvalLogTreeItem {
1617
label?: string;
@@ -104,7 +105,11 @@ export class EvalLogViewer extends DisposableObject {
104105
() => {
105106
/**/
106107
},
107-
(err) => showAndLogErrorMessage(err),
108+
(err: unknown) =>
109+
showAndLogExceptionWithTelemetry(
110+
asError(err),
111+
"eval_log_viewer_reveal",
112+
),
108113
);
109114
}
110115
}

extensions/ql-vscode/src/extension.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ import {
7272
showAndLogInformationMessage,
7373
showInformationMessageWithAction,
7474
tmpDir,
75+
showAndLogExceptionWithTelemetry,
7576
} from "./helpers";
7677
import { asError, assertNever, getErrorMessage } from "./pure/helpers-pure";
7778
import { spawnIdeServer } from "./ide-server";
@@ -714,7 +715,10 @@ async function activateWithInstalledDistribution(
714715
try {
715716
await compareView.showResults(from, to);
716717
} catch (e) {
717-
void showAndLogErrorMessage(getErrorMessage(e));
718+
void showAndLogExceptionWithTelemetry(
719+
asError(e),
720+
"compare_view_show_results",
721+
);
718722
}
719723
}
720724

@@ -812,9 +816,14 @@ async function activateWithInstalledDistribution(
812816
)
813817
? `Could not generate markdown from ${pathToQhelp}: Bad formatting in .qhelp file.`
814818
: `Could not open a preview of the generated file (${absolutePathToMd}).`;
815-
void showAndLogErrorMessage(errorMessage, {
816-
fullMessage: `${errorMessage}\n${e}`,
817-
});
819+
void showAndLogExceptionWithTelemetry(
820+
asError(e),
821+
"preview_query_help",
822+
{
823+
notificationMessage: errorMessage,
824+
fullMessage: `${errorMessage}\n${getErrorMessage(e)}`,
825+
},
826+
);
818827
}
819828
}
820829
}

extensions/ql-vscode/src/helpers.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { CodeQLCliServer, QlpacksInfo } from "./cli";
2121
import { UserCancellationException } from "./commandRunner";
2222
import { extLogger, OutputChannelLogger } from "./common";
2323
import { QueryMetadata } from "./pure/interface-types";
24+
import { ErrorType, telemetryListener } from "./telemetry";
2425

2526
// Shared temporary folder for the extension.
2627
export const tmpDir = dirSync({
@@ -43,6 +44,13 @@ export const tmpDirDisposal = {
4344
},
4445
};
4546

47+
interface ShowAndLogExceptionOptions extends ShowAndLogOptions {
48+
/** Custom properties to include in the telemetry report. */
49+
extraTelemetryProperties?: { [key: string]: string };
50+
/** An alternate message to use for the notification instead of the message from the `Error`. */
51+
notificationMessage?: string;
52+
}
53+
4654
interface ShowAndLogOptions {
4755
/** The output logger that will receive the message. */
4856
outputLogger?: OutputChannelLogger;
@@ -55,6 +63,33 @@ interface ShowAndLogOptions {
5563
fullMessage?: string;
5664
}
5765

66+
/**
67+
* Show an error message and log it to the console
68+
*
69+
* @param error The error message to show, either as a Error or string. Will not be included in the
70+
* telemetry event, and therefore may safely include sensitive information.
71+
* @param telemetryErrorType A safe string that identifies the error, to be included in the
72+
* telemetry event. If not provided, then no telemetry event will be sent.
73+
* @param options See indivual fields on `ShowAndLogExceptionOptions` type.
74+
*
75+
* @return A promise that resolves to the selected item or undefined when being dismissed.
76+
*/
77+
export async function showAndLogExceptionWithTelemetry(
78+
error: Error,
79+
telemetryErrorType: ErrorType,
80+
options: ShowAndLogExceptionOptions = {},
81+
): Promise<string | undefined> {
82+
telemetryListener?.sendError(
83+
telemetryErrorType,
84+
error.stack,
85+
options.extraTelemetryProperties,
86+
);
87+
return showAndLogErrorMessage(
88+
options.notificationMessage ?? error.message,
89+
options,
90+
);
91+
}
92+
5893
/**
5994
* Show an error message and log it to the console
6095
*

0 commit comments

Comments
 (0)