Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
),
);
Expand Down
57 changes: 4 additions & 53 deletions extensions/ql-vscode/src/common/vscode/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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),
);
}

/**
Expand Down
81 changes: 81 additions & 0 deletions extensions/ql-vscode/src/common/vscode/error-handling.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
}