Skip to content

Commit 752ec04

Browse files
authored
Added error handling on webview message processing (#3470)
1 parent ec31b97 commit 752ec04

File tree

3 files changed

+93
-54
lines changed

3 files changed

+93
-54
lines changed

extensions/ql-vscode/src/common/vscode/abstract-webview.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import { tmpDir } from "../../tmp-dir";
1313
import type { WebviewMessage, WebviewKind } from "./webview-html";
1414
import { getHtmlForWebview } from "./webview-html";
1515
import type { DeepReadonly } from "../readonly";
16+
import { runWithErrorHandling } from "./error-handling";
17+
import { telemetryListener } from "./telemetry";
1618

1719
export type WebviewPanelConfig = {
1820
viewId: string;
@@ -117,7 +119,12 @@ export abstract class AbstractWebview<
117119
);
118120
this.push(
119121
panel.webview.onDidReceiveMessage(
120-
async (e) => this.onMessage(e),
122+
async (e) =>
123+
runWithErrorHandling(
124+
() => this.onMessage(e),
125+
this.app.logger,
126+
telemetryListener,
127+
),
121128
undefined,
122129
),
123130
);

extensions/ql-vscode/src/common/vscode/commands.ts

Lines changed: 4 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,10 @@ import { commands } from "vscode";
33
import type { CommandFunction } from "../../packages/commands";
44
import { CommandManager } from "../../packages/commands";
55
import type { NotificationLogger } from "../logging";
6-
import {
7-
showAndLogWarningMessage,
8-
showAndLogExceptionWithTelemetry,
9-
} from "../logging";
106
import { extLogger } from "../logging/vscode";
11-
import { asError, getErrorMessage } from "../../common/helpers-pure";
12-
import { redactableError } from "../../common/errors";
13-
import { UserCancellationException } from "./progress";
147
import { telemetryListener } from "./telemetry";
158
import type { AppTelemetry } from "../telemetry";
16-
import { CliError } from "../../codeql-cli/cli-errors";
17-
import { EOL } from "os";
9+
import { runWithErrorHandling } from "./error-handling";
1810

1911
/**
2012
* Create a command manager for VSCode, wrapping registerCommandWithErrorHandling
@@ -48,50 +40,9 @@ export function registerCommandWithErrorHandling<
4840
logger: NotificationLogger = extLogger,
4941
telemetry: AppTelemetry | undefined = telemetryListener,
5042
): Disposable {
51-
return commands.registerCommand(commandId, async (...args: Parameters<T>) => {
52-
const startTime = Date.now();
53-
let error: Error | undefined;
54-
55-
try {
56-
return await task(...args);
57-
} catch (e) {
58-
error = asError(e);
59-
const errorMessage = redactableError(error)`${
60-
getErrorMessage(e) || e
61-
} (${commandId})`;
62-
if (e instanceof UserCancellationException) {
63-
// User has cancelled this action manually
64-
if (e.silent) {
65-
void logger.log(errorMessage.fullMessage);
66-
} else {
67-
void showAndLogWarningMessage(logger, errorMessage.fullMessage);
68-
}
69-
} else if (e instanceof CliError) {
70-
const fullMessage = `${e.commandDescription} failed with args:${EOL} ${e.commandArgs.join(" ")}${EOL}${
71-
e.stderr ?? e.cause
72-
}`;
73-
void showAndLogExceptionWithTelemetry(logger, telemetry, errorMessage, {
74-
fullMessage,
75-
extraTelemetryProperties: {
76-
command: commandId,
77-
},
78-
});
79-
} else {
80-
// Include the full stack in the error log only.
81-
const fullMessage = errorMessage.fullMessageWithStack;
82-
void showAndLogExceptionWithTelemetry(logger, telemetry, errorMessage, {
83-
fullMessage,
84-
extraTelemetryProperties: {
85-
command: commandId,
86-
},
87-
});
88-
}
89-
return undefined;
90-
} finally {
91-
const executionTime = Date.now() - startTime;
92-
telemetryListener?.sendCommandUsage(commandId, executionTime, error);
93-
}
94-
});
43+
return commands.registerCommand(commandId, async (...args: Parameters<T>) =>
44+
runWithErrorHandling(task, logger, telemetry, commandId, ...args),
45+
);
9546
}
9647

9748
/**
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import {
2+
showAndLogWarningMessage,
3+
showAndLogExceptionWithTelemetry,
4+
} from "../logging";
5+
import type { NotificationLogger } from "../logging";
6+
import { extLogger } from "../logging/vscode";
7+
import type { AppTelemetry } from "../telemetry";
8+
import { telemetryListener } from "./telemetry";
9+
import { asError, getErrorMessage } from "../helpers-pure";
10+
import { redactableError } from "../errors";
11+
import { UserCancellationException } from "./progress";
12+
import { CliError } from "../../codeql-cli/cli-errors";
13+
import { EOL } from "os";
14+
15+
/**
16+
* Executes a task with error handling. It provides a uniform way to handle errors.
17+
*
18+
* @template T - A function type that takes an unknown number of arguments and returns a Promise.
19+
* @param {T} task - The task to be executed.
20+
* @param {NotificationLogger} [logger=extLogger] - The logger to use for error reporting.
21+
* @param {AppTelemetry | undefined} [telemetry=telemetryListener] - The telemetry listener to use for error reporting.
22+
* @param {string} [commandId] - The optional command id associated with the task.
23+
* @param {...unknown} args - The arguments to be passed to the task.
24+
* @returns {Promise<unknown>} The result of the task, or undefined if an error occurred.
25+
* @throws {Error} If an error occurs during the execution of the task.
26+
*/
27+
export async function runWithErrorHandling<
28+
T extends (...args: unknown[]) => Promise<unknown>,
29+
>(
30+
task: T,
31+
logger: NotificationLogger = extLogger,
32+
telemetry: AppTelemetry | undefined = telemetryListener,
33+
commandId?: string,
34+
...args: unknown[]
35+
): Promise<unknown> {
36+
const startTime = Date.now();
37+
let error: Error | undefined;
38+
39+
try {
40+
return await task(...args);
41+
} catch (e) {
42+
error = asError(e);
43+
const errorMessage = redactableError(error)`${
44+
getErrorMessage(e) || e
45+
}${commandId ? ` (${commandId})` : ""}`;
46+
47+
const extraTelemetryProperties = commandId
48+
? { command: commandId }
49+
: undefined;
50+
51+
if (e instanceof UserCancellationException) {
52+
// User has cancelled this action manually
53+
if (e.silent) {
54+
void logger.log(errorMessage.fullMessage);
55+
} else {
56+
void showAndLogWarningMessage(logger, errorMessage.fullMessage);
57+
}
58+
} else if (e instanceof CliError) {
59+
const fullMessage = `${e.commandDescription} failed with args:${EOL} ${e.commandArgs.join(" ")}${EOL}${
60+
e.stderr ?? e.cause
61+
}`;
62+
void showAndLogExceptionWithTelemetry(logger, telemetry, errorMessage, {
63+
fullMessage,
64+
extraTelemetryProperties,
65+
});
66+
} else {
67+
// Include the full stack in the error log only.
68+
const fullMessage = errorMessage.fullMessageWithStack;
69+
void showAndLogExceptionWithTelemetry(logger, telemetry, errorMessage, {
70+
fullMessage,
71+
extraTelemetryProperties,
72+
});
73+
}
74+
return undefined;
75+
} finally {
76+
if (commandId) {
77+
const executionTime = Date.now() - startTime;
78+
telemetryListener?.sendCommandUsage(commandId, executionTime, error);
79+
}
80+
}
81+
}

0 commit comments

Comments
 (0)