Added error handling on webview message processing (#3470)

This commit is contained in:
Charis Kyriakou
2024-03-15 12:27:26 +00:00
committed by GitHub
parent ec31b97de8
commit 752ec04400
3 changed files with 93 additions and 54 deletions

View File

@@ -13,6 +13,8 @@ import { tmpDir } from "../../tmp-dir";
import type { WebviewMessage, WebviewKind } from "./webview-html";
import { getHtmlForWebview } from "./webview-html";
import type { DeepReadonly } from "../readonly";
import { runWithErrorHandling } from "./error-handling";
import { telemetryListener } from "./telemetry";
export type WebviewPanelConfig = {
viewId: string;
@@ -117,7 +119,12 @@ export abstract class AbstractWebview<
);
this.push(
panel.webview.onDidReceiveMessage(
async (e) => this.onMessage(e),
async (e) =>
runWithErrorHandling(
() => this.onMessage(e),
this.app.logger,
telemetryListener,
),
undefined,
),
);

View File

@@ -3,18 +3,10 @@ import { commands } from "vscode";
import type { CommandFunction } from "../../packages/commands";
import { CommandManager } from "../../packages/commands";
import type { NotificationLogger } from "../logging";
import {
showAndLogWarningMessage,
showAndLogExceptionWithTelemetry,
} from "../logging";
import { extLogger } from "../logging/vscode";
import { asError, getErrorMessage } from "../../common/helpers-pure";
import { redactableError } from "../../common/errors";
import { UserCancellationException } from "./progress";
import { telemetryListener } from "./telemetry";
import type { AppTelemetry } from "../telemetry";
import { CliError } from "../../codeql-cli/cli-errors";
import { EOL } from "os";
import { runWithErrorHandling } from "./error-handling";
/**
* Create a command manager for VSCode, wrapping registerCommandWithErrorHandling
@@ -48,50 +40,9 @@ export function registerCommandWithErrorHandling<
logger: NotificationLogger = extLogger,
telemetry: AppTelemetry | undefined = telemetryListener,
): Disposable {
return commands.registerCommand(commandId, async (...args: Parameters<T>) => {
const startTime = Date.now();
let error: Error | undefined;
try {
return await task(...args);
} catch (e) {
error = asError(e);
const errorMessage = redactableError(error)`${
getErrorMessage(e) || e
} (${commandId})`;
if (e instanceof UserCancellationException) {
// User has cancelled this action manually
if (e.silent) {
void logger.log(errorMessage.fullMessage);
} else {
void showAndLogWarningMessage(logger, errorMessage.fullMessage);
}
} else if (e instanceof CliError) {
const fullMessage = `${e.commandDescription} failed with args:${EOL} ${e.commandArgs.join(" ")}${EOL}${
e.stderr ?? e.cause
}`;
void showAndLogExceptionWithTelemetry(logger, telemetry, errorMessage, {
fullMessage,
extraTelemetryProperties: {
command: commandId,
},
});
} else {
// Include the full stack in the error log only.
const fullMessage = errorMessage.fullMessageWithStack;
void showAndLogExceptionWithTelemetry(logger, telemetry, errorMessage, {
fullMessage,
extraTelemetryProperties: {
command: commandId,
},
});
}
return undefined;
} finally {
const executionTime = Date.now() - startTime;
telemetryListener?.sendCommandUsage(commandId, executionTime, error);
}
});
return commands.registerCommand(commandId, async (...args: Parameters<T>) =>
runWithErrorHandling(task, logger, telemetry, commandId, ...args),
);
}
/**

View File

@@ -0,0 +1,81 @@
import {
showAndLogWarningMessage,
showAndLogExceptionWithTelemetry,
} from "../logging";
import type { NotificationLogger } from "../logging";
import { extLogger } from "../logging/vscode";
import type { AppTelemetry } from "../telemetry";
import { telemetryListener } from "./telemetry";
import { asError, getErrorMessage } from "../helpers-pure";
import { redactableError } from "../errors";
import { UserCancellationException } from "./progress";
import { CliError } from "../../codeql-cli/cli-errors";
import { EOL } from "os";
/**
* Executes a task with error handling. It provides a uniform way to handle errors.
*
* @template T - A function type that takes an unknown number of arguments and returns a Promise.
* @param {T} task - The task to be executed.
* @param {NotificationLogger} [logger=extLogger] - The logger to use for error reporting.
* @param {AppTelemetry | undefined} [telemetry=telemetryListener] - The telemetry listener to use for error reporting.
* @param {string} [commandId] - The optional command id associated with the task.
* @param {...unknown} args - The arguments to be passed to the task.
* @returns {Promise<unknown>} The result of the task, or undefined if an error occurred.
* @throws {Error} If an error occurs during the execution of the task.
*/
export async function runWithErrorHandling<
T extends (...args: unknown[]) => Promise<unknown>,
>(
task: T,
logger: NotificationLogger = extLogger,
telemetry: AppTelemetry | undefined = telemetryListener,
commandId?: string,
...args: unknown[]
): Promise<unknown> {
const startTime = Date.now();
let error: Error | undefined;
try {
return await task(...args);
} catch (e) {
error = asError(e);
const errorMessage = redactableError(error)`${
getErrorMessage(e) || e
}${commandId ? ` (${commandId})` : ""}`;
const extraTelemetryProperties = commandId
? { command: commandId }
: undefined;
if (e instanceof UserCancellationException) {
// User has cancelled this action manually
if (e.silent) {
void logger.log(errorMessage.fullMessage);
} else {
void showAndLogWarningMessage(logger, errorMessage.fullMessage);
}
} else if (e instanceof CliError) {
const fullMessage = `${e.commandDescription} failed with args:${EOL} ${e.commandArgs.join(" ")}${EOL}${
e.stderr ?? e.cause
}`;
void showAndLogExceptionWithTelemetry(logger, telemetry, errorMessage, {
fullMessage,
extraTelemetryProperties,
});
} else {
// Include the full stack in the error log only.
const fullMessage = errorMessage.fullMessageWithStack;
void showAndLogExceptionWithTelemetry(logger, telemetry, errorMessage, {
fullMessage,
extraTelemetryProperties,
});
}
return undefined;
} finally {
if (commandId) {
const executionTime = Date.now() - startTime;
telemetryListener?.sendCommandUsage(commandId, executionTime, error);
}
}
}