Use redactable error message for telemetry events
This commit is contained in:
@@ -26,7 +26,8 @@ import {
|
||||
import { commandRunner } from "./commandRunner";
|
||||
import { DisposableObject } from "./pure/disposable-object";
|
||||
import { showAndLogExceptionWithTelemetry } from "./helpers";
|
||||
import { asError } from "./pure/helpers-pure";
|
||||
import { asError, getErrorMessage } from "./pure/helpers-pure";
|
||||
import { redactableErrorMessage } from "./pure/errors";
|
||||
|
||||
export interface AstItem {
|
||||
id: BqrsId;
|
||||
@@ -148,7 +149,12 @@ export class AstViewer extends DisposableObject {
|
||||
/**/
|
||||
},
|
||||
(error: unknown) =>
|
||||
showAndLogExceptionWithTelemetry(asError(error), "AST_viewer_reveal"),
|
||||
showAndLogExceptionWithTelemetry(
|
||||
asError(error),
|
||||
redactableErrorMessage`Failed to reveal AST: ${getErrorMessage(
|
||||
error,
|
||||
)}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -209,7 +215,9 @@ export class AstViewer extends DisposableObject {
|
||||
(error: unknown) =>
|
||||
showAndLogExceptionWithTelemetry(
|
||||
asError(error),
|
||||
"AST_viewer_reveal",
|
||||
redactableErrorMessage`Failed to reveal AST: ${getErrorMessage(
|
||||
error,
|
||||
)}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import { extLogger } from "./common";
|
||||
import { asError, getErrorMessage, getErrorStack } from "./pure/helpers-pure";
|
||||
import { telemetryListener } from "./telemetry";
|
||||
import { redactableErrorMessage } from "./pure/errors";
|
||||
|
||||
export class UserCancellationException extends Error {
|
||||
/**
|
||||
@@ -128,23 +129,24 @@ export function commandRunner(
|
||||
try {
|
||||
return await task(...args);
|
||||
} catch (e) {
|
||||
const notificationMessage = `${getErrorMessage(e) || e} (${commandId})`;
|
||||
const errorMessage = redactableErrorMessage`${
|
||||
getErrorMessage(e) || e
|
||||
} (${commandId})`;
|
||||
error = asError(e);
|
||||
const errorStack = getErrorStack(e);
|
||||
if (e instanceof UserCancellationException) {
|
||||
// User has cancelled this action manually
|
||||
if (e.silent) {
|
||||
void extLogger.log(notificationMessage);
|
||||
void extLogger.log(errorMessage.fullMessage);
|
||||
} else {
|
||||
void showAndLogWarningMessage(notificationMessage);
|
||||
void showAndLogWarningMessage(errorMessage.fullMessage);
|
||||
}
|
||||
} else {
|
||||
// Include the full stack in the error log only.
|
||||
const fullMessage = errorStack
|
||||
? `${notificationMessage}\n${errorStack}`
|
||||
: notificationMessage;
|
||||
void showAndLogExceptionWithTelemetry(error, "command_failed", {
|
||||
notificationMessage,
|
||||
? `${errorMessage.fullMessage}\n${errorStack}`
|
||||
: errorMessage.fullMessage;
|
||||
void showAndLogExceptionWithTelemetry(error, errorMessage, {
|
||||
fullMessage,
|
||||
extraTelemetryProperties: {
|
||||
command: commandId,
|
||||
@@ -185,24 +187,27 @@ export function commandRunnerWithProgress<R>(
|
||||
try {
|
||||
return await withProgress(progressOptionsWithDefaults, task, ...args);
|
||||
} catch (e) {
|
||||
const notificationMessage = `${getErrorMessage(e) || e} (${commandId})`;
|
||||
const errorMessage = redactableErrorMessage`${
|
||||
getErrorMessage(e) || e
|
||||
} (${commandId})`;
|
||||
error = asError(e);
|
||||
const errorStack = getErrorStack(e);
|
||||
if (e instanceof UserCancellationException) {
|
||||
// User has cancelled this action manually
|
||||
if (e.silent) {
|
||||
void outputLogger.log(notificationMessage);
|
||||
void outputLogger.log(errorMessage.fullMessage);
|
||||
} else {
|
||||
void showAndLogWarningMessage(notificationMessage, { outputLogger });
|
||||
void showAndLogWarningMessage(errorMessage.fullMessage, {
|
||||
outputLogger,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Include the full stack in the error log only.
|
||||
const fullMessage = errorStack
|
||||
? `${notificationMessage}\n${errorStack}`
|
||||
: notificationMessage;
|
||||
void showAndLogExceptionWithTelemetry(error, "command_failed", {
|
||||
? `${errorMessage.fullMessage}\n${errorStack}`
|
||||
: errorMessage.fullMessage;
|
||||
void showAndLogExceptionWithTelemetry(error, errorMessage, {
|
||||
outputLogger,
|
||||
notificationMessage,
|
||||
fullMessage,
|
||||
extraTelemetryProperties: {
|
||||
command: commandId,
|
||||
|
||||
@@ -18,6 +18,7 @@ import { createInitialQueryInfo } from "../run-queries-shared";
|
||||
import { CancellationToken, Uri } from "vscode";
|
||||
import { ProgressCallback } from "../commandRunner";
|
||||
import { QueryRunner } from "../queryRunner";
|
||||
import { redactableErrorMessage } from "../pure/errors";
|
||||
|
||||
export async function qlpackOfDatabase(
|
||||
cli: CodeQLCliServer,
|
||||
@@ -91,13 +92,13 @@ export async function resolveQueries(
|
||||
const keyTypeName = nameOfKeyType(keyType);
|
||||
const keyTypeTag = tagOfKeyType(keyType);
|
||||
const joinedPacksToSearch = packsToSearch.join(", ");
|
||||
const errorMessage = `No ${keyTypeName} queries (tagged "${keyTypeTag}") could be found in the \
|
||||
const errorMessage = redactableErrorMessage`No ${keyTypeName} queries (tagged "${keyTypeTag}") could be found in the \
|
||||
current library path (tried searching the following packs: ${joinedPacksToSearch}). \
|
||||
Try upgrading the CodeQL libraries. If that doesn't work, then ${keyTypeName} queries are not yet available \
|
||||
for this language.`;
|
||||
|
||||
const e = new Error(errorMessage);
|
||||
void showAndLogExceptionWithTelemetry(e, "resolve_queries");
|
||||
const e = new Error(errorMessage.fullMessage);
|
||||
void showAndLogExceptionWithTelemetry(e, errorMessage);
|
||||
throw e;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ import { QueryRunner } from "./queryRunner";
|
||||
import { isCanary } from "./config";
|
||||
import { App } from "./common/app";
|
||||
import { Credentials } from "./common/authentication";
|
||||
import { redactableErrorMessage } from "./pure/errors";
|
||||
|
||||
enum SortOrder {
|
||||
NameAsc = "NameAsc",
|
||||
@@ -347,7 +348,9 @@ export class DatabaseUI extends DisposableObject {
|
||||
} catch (e) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError(e),
|
||||
"databases_ui_choose_and_set_database",
|
||||
redactableErrorMessage`Failed to choose and set database: ${getErrorMessage(
|
||||
e,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -399,7 +402,9 @@ export class DatabaseUI extends DisposableObject {
|
||||
} catch (e) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError(e),
|
||||
"databases_ui_remove_orphaned_database",
|
||||
redactableErrorMessage`Failed to delete orphaned database: ${getErrorMessage(
|
||||
e,
|
||||
)}`,
|
||||
);
|
||||
failures.push(`${basename(dbDir)}`);
|
||||
}
|
||||
@@ -425,7 +430,9 @@ export class DatabaseUI extends DisposableObject {
|
||||
} catch (e: unknown) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError(e),
|
||||
"databases_ui_choose_and_set_database",
|
||||
redactableErrorMessage`Failed to choose and set database: ${getErrorMessage(
|
||||
e,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -22,6 +22,7 @@ import { Logger, extLogger } from "./common";
|
||||
import { asError, getErrorMessage } from "./pure/helpers-pure";
|
||||
import { QueryRunner } from "./queryRunner";
|
||||
import { pathsEqual } from "./pure/files";
|
||||
import { redactableErrorMessage } from "./pure/errors";
|
||||
|
||||
/**
|
||||
* databases.ts
|
||||
@@ -796,12 +797,9 @@ export class DatabaseManager extends DisposableObject {
|
||||
// database list had an unexpected type - nothing to be done?
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError(e),
|
||||
"databases_load_persisted_state",
|
||||
{
|
||||
notificationMessage: `Database list loading failed: ${getErrorMessage(
|
||||
e,
|
||||
)}`,
|
||||
},
|
||||
redactableErrorMessage`Database list loading failed: ${getErrorMessage(
|
||||
e,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,8 @@ import {
|
||||
import { commandRunner } from "./commandRunner";
|
||||
import { DisposableObject } from "./pure/disposable-object";
|
||||
import { showAndLogExceptionWithTelemetry } from "./helpers";
|
||||
import { asError } from "./pure/helpers-pure";
|
||||
import { asError, getErrorMessage } from "./pure/helpers-pure";
|
||||
import { redactableErrorMessage } from "./pure/errors";
|
||||
|
||||
export interface EvalLogTreeItem {
|
||||
label?: string;
|
||||
@@ -108,7 +109,9 @@ export class EvalLogViewer extends DisposableObject {
|
||||
(err: unknown) =>
|
||||
showAndLogExceptionWithTelemetry(
|
||||
asError(err),
|
||||
"eval_log_viewer_reveal",
|
||||
redactableErrorMessage`Failed to reveal tree view: ${getErrorMessage(
|
||||
err,
|
||||
)}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -138,6 +138,7 @@ import { VariantAnalysisResultsManager } from "./remote-queries/variant-analysis
|
||||
import { ExtensionApp } from "./common/vscode/vscode-app";
|
||||
import { RepositoriesFilterSortStateWithIds } from "./pure/variant-analysis-filter-sort";
|
||||
import { DbModule } from "./databases/db-module";
|
||||
import { redactableErrorMessage } from "./pure/errors";
|
||||
|
||||
/**
|
||||
* extension.ts
|
||||
@@ -717,7 +718,7 @@ async function activateWithInstalledDistribution(
|
||||
} catch (e) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError(e),
|
||||
"compare_view_show_results",
|
||||
redactableErrorMessage`Failed to show results: ${getErrorMessage(e)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -814,16 +815,11 @@ async function activateWithInstalledDistribution(
|
||||
const errorMessage = getErrorMessage(e).includes(
|
||||
"Generating qhelp in markdown",
|
||||
)
|
||||
? `Could not generate markdown from ${pathToQhelp}: Bad formatting in .qhelp file.`
|
||||
: `Could not open a preview of the generated file (${absolutePathToMd}).`;
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError(e),
|
||||
"preview_query_help",
|
||||
{
|
||||
notificationMessage: errorMessage,
|
||||
fullMessage: `${errorMessage}\n${getErrorMessage(e)}`,
|
||||
},
|
||||
);
|
||||
? redactableErrorMessage`Could not generate markdown from ${pathToQhelp}: Bad formatting in .qhelp file.`
|
||||
: redactableErrorMessage`Could not open a preview of the generated file (${absolutePathToMd}).`;
|
||||
void showAndLogExceptionWithTelemetry(asError(e), errorMessage, {
|
||||
fullMessage: `${errorMessage}\n${getErrorMessage(e)}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ import { CodeQLCliServer, QlpacksInfo } from "./cli";
|
||||
import { UserCancellationException } from "./commandRunner";
|
||||
import { extLogger, OutputChannelLogger } from "./common";
|
||||
import { QueryMetadata } from "./pure/interface-types";
|
||||
import { ErrorType, telemetryListener } from "./telemetry";
|
||||
import { telemetryListener } from "./telemetry";
|
||||
import { RedactableErrorMessage } from "./pure/errors";
|
||||
|
||||
// Shared temporary folder for the extension.
|
||||
export const tmpDir = dirSync({
|
||||
@@ -47,8 +48,6 @@ export const tmpDirDisposal = {
|
||||
interface ShowAndLogExceptionOptions extends ShowAndLogOptions {
|
||||
/** Custom properties to include in the telemetry report. */
|
||||
extraTelemetryProperties?: { [key: string]: string };
|
||||
/** An alternate message to use for the notification instead of the message from the `Error`. */
|
||||
notificationMessage?: string;
|
||||
}
|
||||
|
||||
interface ShowAndLogOptions {
|
||||
@@ -68,26 +67,23 @@ interface ShowAndLogOptions {
|
||||
*
|
||||
* @param error The error message to show, either as a Error or string. Will not be included in the
|
||||
* telemetry event, and therefore may safely include sensitive information.
|
||||
* @param telemetryErrorType A safe string that identifies the error, to be included in the
|
||||
* telemetry event. If not provided, then no telemetry event will be sent.
|
||||
* @param message A message to show to the user. Will also be included in the telemetry event,
|
||||
* but only the redacated message will be sent.
|
||||
* @param options See individual fields on `ShowAndLogExceptionOptions` type.
|
||||
*
|
||||
* @return A promise that resolves to the selected item or undefined when being dismissed.
|
||||
*/
|
||||
export async function showAndLogExceptionWithTelemetry(
|
||||
error: Error,
|
||||
telemetryErrorType: ErrorType,
|
||||
message: RedactableErrorMessage,
|
||||
options: ShowAndLogExceptionOptions = {},
|
||||
): Promise<string | undefined> {
|
||||
telemetryListener?.sendError(
|
||||
telemetryErrorType,
|
||||
message,
|
||||
error.stack,
|
||||
options.extraTelemetryProperties,
|
||||
);
|
||||
return showAndLogErrorMessage(
|
||||
options.notificationMessage ?? error.message,
|
||||
options,
|
||||
);
|
||||
return showAndLogErrorMessage(message.fullMessage, options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -67,6 +67,7 @@ import { AbstractWebview, WebviewPanelConfig } from "./abstract-webview";
|
||||
import { PAGE_SIZE } from "./config";
|
||||
import { HistoryItemLabelProvider } from "./query-history/history-item-label-provider";
|
||||
import { telemetryListener } from "./telemetry";
|
||||
import { redactableErrorMessage } from "./pure/errors";
|
||||
|
||||
/**
|
||||
* interface.ts
|
||||
@@ -294,7 +295,9 @@ export class ResultsView extends AbstractWebview<
|
||||
} catch (e) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError(e),
|
||||
"results_view_on_message",
|
||||
redactableErrorMessage`Error handling message from results view: ${getErrorMessage(
|
||||
e,
|
||||
)}`,
|
||||
{
|
||||
fullMessage: getErrorStack(e),
|
||||
},
|
||||
@@ -342,7 +345,7 @@ export class ResultsView extends AbstractWebview<
|
||||
if (this._displayedQuery === undefined) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError("Failed to sort results since evaluation info was unknown."),
|
||||
"results_view_displayed_query_undefined",
|
||||
redactableErrorMessage`Failed to sort results since evaluation info was unknown.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -361,7 +364,7 @@ export class ResultsView extends AbstractWebview<
|
||||
if (this._displayedQuery === undefined) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError("Failed to sort results since evaluation info was unknown."),
|
||||
"results_view_displayed_query_undefined",
|
||||
redactableErrorMessage`Failed to sort results since evaluation info was unknown.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -771,12 +774,9 @@ export class ResultsView extends AbstractWebview<
|
||||
// trying to render uninterpreted results anyway.
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError(e),
|
||||
"results_view_interpret_results_info",
|
||||
{
|
||||
notificationMessage: `Showing raw results instead of interpreted ones due to an error. ${getErrorMessage(
|
||||
e,
|
||||
)}`,
|
||||
},
|
||||
redactableErrorMessage`Showing raw results instead of interpreted ones due to an error. ${getErrorMessage(
|
||||
e,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import * as qsClient from "./queryserver-client";
|
||||
import { asError, getErrorMessage } from "../pure/helpers-pure";
|
||||
import { compileDatabaseUpgradeSequence } from "./upgrades";
|
||||
import { QueryEvaluationInfo, QueryWithResults } from "../run-queries-shared";
|
||||
import { redactableErrorMessage } from "../pure/errors";
|
||||
|
||||
/**
|
||||
* A collection of evaluation-time information about a query,
|
||||
@@ -368,16 +369,8 @@ export async function compileAndRunQueryAgainstDatabase(
|
||||
void extLogger.log("Did not find any available ML models.");
|
||||
}
|
||||
} catch (e) {
|
||||
const notificationMessage =
|
||||
`Couldn't resolve available ML models for ${qlProgram.queryPath}. Running the ` +
|
||||
`query without any ML models: ${e}.`;
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError(e),
|
||||
"legacy_query_server_ml_models_not_found",
|
||||
{
|
||||
notificationMessage,
|
||||
},
|
||||
);
|
||||
const notificationMessage = redactableErrorMessage`Couldn't resolve available ML models for ${qlProgram.queryPath}. Running the query without any ML models: ${e}.`;
|
||||
void showAndLogExceptionWithTelemetry(asError(e), notificationMessage);
|
||||
}
|
||||
|
||||
const hasMetadataFile = await dbItem.hasMetadataFile();
|
||||
@@ -429,11 +422,13 @@ export async function compileAndRunQueryAgainstDatabase(
|
||||
queryInfo,
|
||||
);
|
||||
if (result.resultType !== messages.QueryResultType.SUCCESS) {
|
||||
const message = result.message || "Failed to run query";
|
||||
void extLogger.log(message);
|
||||
const message = result.message
|
||||
? redactableErrorMessage`${result.message}`
|
||||
: redactableErrorMessage`Failed to run query`;
|
||||
void extLogger.log(message.fullMessage);
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError(message),
|
||||
"legacy_query_server_run_queries",
|
||||
redactableErrorMessage`Failed to run query: ${message}`,
|
||||
);
|
||||
}
|
||||
const message = formatLegacyMessage(result);
|
||||
|
||||
@@ -11,7 +11,8 @@ import * as qsClient from "./queryserver-client";
|
||||
import * as tmp from "tmp-promise";
|
||||
import { dirname } from "path";
|
||||
import { DatabaseItem } from "../databases";
|
||||
import { asError } from "../pure/helpers-pure";
|
||||
import { asError, getErrorMessage } from "../pure/helpers-pure";
|
||||
import { redactableErrorMessage } from "../pure/errors";
|
||||
|
||||
/**
|
||||
* Maximum number of lines to include from database upgrade message,
|
||||
@@ -212,10 +213,9 @@ export async function upgradeDatabaseExplicit(
|
||||
} catch (e) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError(e),
|
||||
"database_upgrade_compilation",
|
||||
{
|
||||
notificationMessage: `Compilation of database upgrades failed: ${e}`,
|
||||
},
|
||||
redactableErrorMessage`Compilation of database upgrades failed: ${getErrorMessage(
|
||||
e,
|
||||
)}`,
|
||||
);
|
||||
return;
|
||||
} finally {
|
||||
@@ -224,10 +224,13 @@ export async function upgradeDatabaseExplicit(
|
||||
|
||||
if (!compileUpgradeResult.compiledUpgrades) {
|
||||
const error =
|
||||
compileUpgradeResult.error || "[no error message available]";
|
||||
redactableErrorMessage`${compileUpgradeResult.error}` ||
|
||||
redactableErrorMessage`[no error message available]`;
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError(`Compilation of database upgrades failed: ${error}`),
|
||||
"database_upgrade_compilation",
|
||||
asError(
|
||||
`Compilation of database upgrades failed: ${error.fullMessage}`,
|
||||
),
|
||||
redactableErrorMessage`Compilation of database upgrades failed: ${error}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -259,9 +262,10 @@ export async function upgradeDatabaseExplicit(
|
||||
await qs.restartQueryServer(progress, token);
|
||||
return result;
|
||||
} catch (e) {
|
||||
void showAndLogExceptionWithTelemetry(asError(e), "database_upgrade", {
|
||||
notificationMessage: `Database upgrade failed: ${e}`,
|
||||
});
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError(e),
|
||||
redactableErrorMessage`Database upgrade failed: ${getErrorMessage(e)}`,
|
||||
);
|
||||
return;
|
||||
} finally {
|
||||
void qs.logger.log("Done running database upgrade.");
|
||||
|
||||
@@ -8,6 +8,7 @@ import { QuickPickItem, window } from "vscode";
|
||||
import { ProgressCallback, UserCancellationException } from "./commandRunner";
|
||||
import { extLogger } from "./common";
|
||||
import { asError, getErrorStack } from "./pure/helpers-pure";
|
||||
import { redactableErrorMessage } from "./pure/errors";
|
||||
|
||||
const QUERY_PACKS = [
|
||||
"codeql/cpp-queries",
|
||||
@@ -69,10 +70,8 @@ export async function handleDownloadPacks(
|
||||
} catch (error) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError(error),
|
||||
"packaging_download_packs",
|
||||
redactableErrorMessage`Unable to download all packs. See log for more details.`,
|
||||
{
|
||||
notificationMessage:
|
||||
"Unable to download all packs. See log for more details.",
|
||||
fullMessage: getErrorStack(error),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -16,12 +16,24 @@ export class RedactableErrorMessage {
|
||||
|
||||
public get redactedMessage(): string {
|
||||
return this.strings
|
||||
.map((s, i) => s + (this.hasValue(i) ? "[REDACTED]" : ""))
|
||||
.map((s, i) => s + (this.hasValue(i) ? this.getRedactedValue(i) : ""))
|
||||
.join("");
|
||||
}
|
||||
|
||||
private getValue(index: number): unknown {
|
||||
return this.values[index];
|
||||
const value = this.values[index];
|
||||
if (value instanceof RedactableErrorMessage) {
|
||||
return value.fullMessage;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private getRedactedValue(index: number): unknown {
|
||||
const value = this.values[index];
|
||||
if (value instanceof RedactableErrorMessage) {
|
||||
return value.redactedMessage;
|
||||
}
|
||||
return "[REDACTED]";
|
||||
}
|
||||
|
||||
private hasValue(index: number): boolean {
|
||||
@@ -29,7 +41,7 @@ export class RedactableErrorMessage {
|
||||
}
|
||||
}
|
||||
|
||||
export function errorMessage(
|
||||
export function redactableErrorMessage(
|
||||
strings: TemplateStringsArray,
|
||||
...values: unknown[]
|
||||
): RedactableErrorMessage {
|
||||
@@ -5,6 +5,8 @@
|
||||
* Helper functions that don't depend on vscode or the CLI and therefore can be used by the front-end and pure unit tests.
|
||||
*/
|
||||
|
||||
import { RedactableErrorMessage } from "./errors";
|
||||
|
||||
/**
|
||||
* This error is used to indicate a runtime failure of an exhaustivity check enforced at compile time.
|
||||
*/
|
||||
@@ -47,6 +49,10 @@ export const REPO_REGEX = /^[a-zA-Z0-9-_\.]+\/[a-zA-Z0-9-_\.]+$/;
|
||||
export const OWNER_REGEX = /^[a-zA-Z0-9-_\.]+$/;
|
||||
|
||||
export function getErrorMessage(e: unknown): string {
|
||||
if (e instanceof RedactableErrorMessage) {
|
||||
return e.fullMessage;
|
||||
}
|
||||
|
||||
return e instanceof Error ? e.message : String(e);
|
||||
}
|
||||
|
||||
@@ -55,5 +61,9 @@ export function getErrorStack(e: unknown): string {
|
||||
}
|
||||
|
||||
export function asError(e: unknown): Error {
|
||||
if (e instanceof RedactableErrorMessage) {
|
||||
return new Error(e.fullMessage);
|
||||
}
|
||||
|
||||
return e instanceof Error ? e : new Error(String(e));
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ import { VariantAnalysisHistoryItem } from "./variant-analysis-history-item";
|
||||
import { getTotalResultCount } from "../remote-queries/shared/variant-analysis";
|
||||
import { App } from "../common/app";
|
||||
import { HistoryTreeDataProvider } from "./history-tree-data-provider";
|
||||
import { redactableErrorMessage } from "../pure/errors";
|
||||
|
||||
/**
|
||||
* query-history-manager.ts
|
||||
@@ -813,7 +814,9 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
} catch (e) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError(e),
|
||||
"query_history_manager_compare_with",
|
||||
redactableErrorMessage`Failed to compare queries: ${getErrorMessage(
|
||||
e,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1381,16 +1384,17 @@ the file in the file explorer and dragging it into the workspace.`,
|
||||
} catch (e) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError(e),
|
||||
"query_history_manager_reveal_file_in_os",
|
||||
redactableErrorMessage`Failed to reveal file in OS: ${getErrorMessage(
|
||||
e,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError(e),
|
||||
"query_history_manager_show_text_document",
|
||||
redactableErrorMessage`Could not open file ${fileLocation}`,
|
||||
{
|
||||
notificationMessage: `Could not open file ${fileLocation}`,
|
||||
fullMessage: `${getErrorMessage(e)}\n${getErrorStack(e)}`,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -13,6 +13,7 @@ import { QueryHistoryInfo } from "./query-history/query-history-info";
|
||||
import { QueryStatus } from "./query-status";
|
||||
import { QueryEvaluationInfo } from "./run-queries-shared";
|
||||
import { QueryResultType } from "./pure/legacy-messages";
|
||||
import { redactableErrorMessage } from "./pure/errors";
|
||||
|
||||
export async function deserializeQueryHistory(
|
||||
fsPath: string,
|
||||
@@ -29,7 +30,7 @@ export async function deserializeQueryHistory(
|
||||
asError(
|
||||
`Can't parse query history. Unsupported query history format: v${obj.version}.`,
|
||||
),
|
||||
"query_serialization_unsupported_format",
|
||||
redactableErrorMessage`Can't parse query history. Unsupported query history format: v${obj.version}.`,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
@@ -98,9 +99,8 @@ export async function deserializeQueryHistory(
|
||||
} catch (e) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError(e),
|
||||
"query_history_deserialization",
|
||||
redactableErrorMessage`Error loading query history.`,
|
||||
{
|
||||
notificationMessage: "Error loading query history.",
|
||||
fullMessage: `Error loading query history.\n${getErrorStack(e)}`,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -16,6 +16,7 @@ import { InitialQueryInfo, LocalQueryInfo } from "../query-results";
|
||||
import { QueryEvaluationInfo, QueryWithResults } from "../run-queries-shared";
|
||||
import * as qsClient from "./queryserver-client";
|
||||
import { asError } from "../pure/helpers-pure";
|
||||
import { redactableErrorMessage } from "../pure/errors";
|
||||
|
||||
/**
|
||||
* run-queries.ts
|
||||
@@ -110,11 +111,13 @@ export async function compileAndRunQueryAgainstDatabase(
|
||||
}
|
||||
|
||||
if (result.resultType !== messages.QueryResultType.SUCCESS) {
|
||||
const message = result.message || "Failed to run query";
|
||||
void extLogger.log(message);
|
||||
const message = result?.message
|
||||
? redactableErrorMessage`${result.message}`
|
||||
: redactableErrorMessage`Failed to run query`;
|
||||
void extLogger.log(message.fullMessage);
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError(message),
|
||||
"query_server_run_queries",
|
||||
redactableErrorMessage`Failed to run query: ${message}`,
|
||||
);
|
||||
}
|
||||
let message;
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
import { asError, getErrorMessage } from "../../pure/helpers-pure";
|
||||
import { unzipFile } from "../../pure/zip";
|
||||
import { VariantAnalysis } from "../shared/variant-analysis";
|
||||
import { redactableErrorMessage } from "../../pure/errors";
|
||||
|
||||
export const RESULT_INDEX_ARTIFACT_NAME = "result-index";
|
||||
|
||||
@@ -494,12 +495,9 @@ export async function getRepositoriesMetadata(
|
||||
} catch (e) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError(e),
|
||||
"gh_actions_api_client_get_repositories_metadata",
|
||||
{
|
||||
notificationMessage: `Error retrieving repository metadata for variant analysis: ${getErrorMessage(
|
||||
e,
|
||||
)}`,
|
||||
},
|
||||
redactableErrorMessage`Error retrieving repository metadata for variant analysis: ${getErrorMessage(
|
||||
e,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,9 @@ import {
|
||||
showAndLogExceptionWithTelemetry,
|
||||
showAndLogInformationMessage,
|
||||
} from "../helpers";
|
||||
import { asError } from "../pure/helpers-pure";
|
||||
import { asError, getErrorMessage } from "../pure/helpers-pure";
|
||||
import { pluralize } from "../pure/word";
|
||||
import { redactableErrorMessage } from "../pure/errors";
|
||||
|
||||
export async function runRemoteQueriesApiRequest(
|
||||
credentials: Credentials,
|
||||
@@ -46,7 +47,9 @@ export async function runRemoteQueriesApiRequest(
|
||||
} else {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError(error),
|
||||
"remote_queries_submit",
|
||||
redactableErrorMessage`Error submitting remote queries request: ${getErrorMessage(
|
||||
error,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,12 +37,13 @@ import {
|
||||
} from "./remote-query-result";
|
||||
import { DownloadLink } from "./download-link";
|
||||
import { AnalysesResultsManager } from "./analyses-results-manager";
|
||||
import { asError, assertNever } from "../pure/helpers-pure";
|
||||
import { asError, assertNever, getErrorMessage } from "../pure/helpers-pure";
|
||||
import { QueryStatus } from "../query-status";
|
||||
import { DisposableObject } from "../pure/disposable-object";
|
||||
import { AnalysisResults } from "./shared/analysis-result";
|
||||
import { runRemoteQueriesApiRequest } from "./remote-queries-api";
|
||||
import { App } from "../common/app";
|
||||
import { redactableErrorMessage } from "../pure/errors";
|
||||
|
||||
const autoDownloadMaxSize = 300 * 1024;
|
||||
const autoDownloadMaxCount = 100;
|
||||
@@ -153,19 +154,17 @@ export class RemoteQueriesManager extends DisposableObject {
|
||||
(e: unknown) =>
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError(e),
|
||||
"remote_queries_manager_open_results",
|
||||
{
|
||||
notificationMessage: `Could not open query results. ${e}`,
|
||||
},
|
||||
redactableErrorMessage`Could not open query results. ${getErrorMessage(
|
||||
e,
|
||||
)}`,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError(e),
|
||||
"remote_queries_manager_open_results",
|
||||
{
|
||||
notificationMessage: `Could not open query results. ${e}`,
|
||||
},
|
||||
redactableErrorMessage`Could not open query results. ${getErrorMessage(
|
||||
e,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -285,7 +284,7 @@ export class RemoteQueriesManager extends DisposableObject {
|
||||
// Should not get here. Only including this to ensure `assertNever` uses proper type checking.
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError(`Unexpected status: ${queryWorkflowResult.status}`),
|
||||
"remote_queries_manager_monitor_unexpectd_status",
|
||||
redactableErrorMessage`Unexpected status: ${queryWorkflowResult.status}`,
|
||||
);
|
||||
} else {
|
||||
// Ensure all cases are covered
|
||||
@@ -493,10 +492,9 @@ export class RemoteQueriesManager extends DisposableObject {
|
||||
(e: unknown) =>
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError(e),
|
||||
"remote_queries_manager_open_results",
|
||||
{
|
||||
notificationMessage: `Could not open query results. ${e}`,
|
||||
},
|
||||
redactableErrorMessage`Could not open query results. ${getErrorMessage(
|
||||
e,
|
||||
)}`,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
@@ -506,7 +504,7 @@ export class RemoteQueriesManager extends DisposableObject {
|
||||
asError(
|
||||
`There was an issue retrieving the result for the query [${remoteQuery.queryName}](${workflowRunUrl}).`,
|
||||
),
|
||||
"remote_queries_manager_download_missing_index",
|
||||
redactableErrorMessage`There was an issue retrieving the result for the query [${remoteQuery.queryName}](${workflowRunUrl}).`,
|
||||
);
|
||||
this.remoteQueryStatusUpdateEventEmitter.fire({
|
||||
queryId,
|
||||
|
||||
@@ -62,6 +62,7 @@ import { URLSearchParams } from "url";
|
||||
import { DbManager } from "../databases/db-manager";
|
||||
import { isVariantAnalysisReposPanelEnabled } from "../config";
|
||||
import { App } from "../common/app";
|
||||
import { redactableErrorMessage } from "../pure/errors";
|
||||
|
||||
export class VariantAnalysisManager
|
||||
extends DisposableObject
|
||||
@@ -263,7 +264,7 @@ export class VariantAnalysisManager
|
||||
if (!this.variantAnalyses.get(variantAnalysisId)) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError(`No variant analysis found with id: ${variantAnalysisId}.`),
|
||||
"variant_analysis_manager_id_not_found",
|
||||
redactableErrorMessage`No variant analysis found with id: ${variantAnalysisId}.`,
|
||||
);
|
||||
}
|
||||
if (!this.views.has(variantAnalysisId)) {
|
||||
|
||||
@@ -18,6 +18,7 @@ import * as appInsights from "applicationinsights";
|
||||
import { extLogger } from "./common";
|
||||
import { UserCancellationException } from "./commandRunner";
|
||||
import { showBinaryChoiceWithUrlDialog } from "./helpers";
|
||||
import { RedactableErrorMessage } from "./pure/errors";
|
||||
|
||||
// Key is injected at build time through the APP_INSIGHTS_KEY environment variable.
|
||||
const key = "REPLACE-APP-INSIGHTS-KEY";
|
||||
@@ -28,38 +29,6 @@ export enum CommandCompletion {
|
||||
Cancelled = "Cancelled",
|
||||
}
|
||||
|
||||
export type ErrorType =
|
||||
| "AST_viewer_reveal"
|
||||
| "command_failed"
|
||||
| "compare_view_show_results"
|
||||
| "databases_load_persisted_state"
|
||||
| "databases_ui_choose_and_set_database"
|
||||
| "databases_ui_remove_orphaned_database"
|
||||
| "database_upgrade"
|
||||
| "database_upgrade_compilation"
|
||||
| "eval_log_viewer_reveal"
|
||||
| "gh_actions_api_client_get_repositories_metadata"
|
||||
| "legacy_query_server_ml_models_not_found"
|
||||
| "legacy_query_server_run_queries"
|
||||
| "packaging_download_packs"
|
||||
| "preview_query_help"
|
||||
| "query_history_deserialization"
|
||||
| "query_history_manager_compare_with"
|
||||
| "query_history_manager_reveal_file_in_os"
|
||||
| "query_history_manager_show_text_document"
|
||||
| "query_serialization_unsupported_format"
|
||||
| "query_server_run_queries"
|
||||
| "remote_queries_submit"
|
||||
| "remote_queries_manager_open_results"
|
||||
| "remote_queries_manager_monitor_unexpectd_status"
|
||||
| "remote_queries_manager_download_missing_index"
|
||||
| "resolve_queries"
|
||||
| "results_view_on_message"
|
||||
| "results_view_interpret_results_info"
|
||||
| "results_view_displayed_query_undefined"
|
||||
| "test_adapter_remove_databases_before_tests"
|
||||
| "variant_analysis_manager_id_not_found";
|
||||
|
||||
// Avoid sending the following data to App insights since we don't need it.
|
||||
const tagsToRemove = [
|
||||
"ai.application.ver",
|
||||
@@ -215,7 +184,7 @@ export class TelemetryListener extends ConfigListener {
|
||||
}
|
||||
|
||||
sendError(
|
||||
errorType: ErrorType,
|
||||
errorType: RedactableErrorMessage,
|
||||
stack?: string,
|
||||
extraProperties?: { [key: string]: string },
|
||||
) {
|
||||
@@ -224,7 +193,7 @@ export class TelemetryListener extends ConfigListener {
|
||||
}
|
||||
|
||||
const properties: { [key: string]: string } = {
|
||||
type: errorType,
|
||||
message: errorType.redactedMessage,
|
||||
...extraProperties,
|
||||
};
|
||||
if (stack && stack !== "") {
|
||||
|
||||
@@ -35,7 +35,8 @@ import {
|
||||
} from "./helpers";
|
||||
import { testLogger } from "./common";
|
||||
import { DatabaseItem, DatabaseManager } from "./databases";
|
||||
import { asError } from "./pure/helpers-pure";
|
||||
import { asError, getErrorMessage } from "./pure/helpers-pure";
|
||||
import { redactableErrorMessage } from "./pure/errors";
|
||||
|
||||
/**
|
||||
* Get the full path of the `.expected` file for the specified QL test.
|
||||
@@ -281,10 +282,9 @@ export class QLTestAdapter extends DisposableObject implements TestAdapter {
|
||||
// So we need to display the error message ourselves and then rethrow.
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
asError(e),
|
||||
"test_adapter_remove_databases_before_tests",
|
||||
{
|
||||
notificationMessage: `Cannot remove database ${database.name}: ${e}`,
|
||||
},
|
||||
redactableErrorMessage`Cannot remove database ${
|
||||
database.name
|
||||
}: ${getErrorMessage(e)}`,
|
||||
);
|
||||
throw e;
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import {
|
||||
errorMessage,
|
||||
RedactableErrorMessage,
|
||||
} from "../../../src/common/errors";
|
||||
|
||||
describe("errorMessage", () => {
|
||||
it("creates a RedactableErrorMessage", () => {
|
||||
expect(errorMessage`Failed to create database ${"foo"}`).toBeInstanceOf(
|
||||
RedactableErrorMessage,
|
||||
);
|
||||
});
|
||||
|
||||
it("toString() matches the given message", () => {
|
||||
expect(errorMessage`Failed to create database ${"foo"}`.toString()).toEqual(
|
||||
"Failed to create database foo",
|
||||
);
|
||||
});
|
||||
|
||||
it("fullMessage matches the given message", () => {
|
||||
expect(
|
||||
errorMessage`Failed to create database ${"foo"}`.fullMessage,
|
||||
).toEqual("Failed to create database foo");
|
||||
});
|
||||
|
||||
it("redactedMessage redacts the given message", () => {
|
||||
expect(
|
||||
errorMessage`Failed to create database ${"foo"}`.redactedMessage,
|
||||
).toEqual("Failed to create database [REDACTED]");
|
||||
});
|
||||
});
|
||||
45
extensions/ql-vscode/test/unit-tests/pure/errors.test.ts
Normal file
45
extensions/ql-vscode/test/unit-tests/pure/errors.test.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import {
|
||||
redactableErrorMessage,
|
||||
RedactableErrorMessage,
|
||||
} from "../../../src/pure/errors";
|
||||
|
||||
describe("errorMessage", () => {
|
||||
it("creates a RedactableErrorMessage", () => {
|
||||
expect(
|
||||
redactableErrorMessage`Failed to create database ${"foo"}`,
|
||||
).toBeInstanceOf(RedactableErrorMessage);
|
||||
});
|
||||
|
||||
it("toString() matches the given message", () => {
|
||||
expect(
|
||||
redactableErrorMessage`Failed to create database ${"foo"}`.toString(),
|
||||
).toEqual("Failed to create database foo");
|
||||
});
|
||||
|
||||
it("fullMessage matches the given message", () => {
|
||||
expect(
|
||||
redactableErrorMessage`Failed to create database ${"foo"}`.fullMessage,
|
||||
).toEqual("Failed to create database foo");
|
||||
});
|
||||
|
||||
it("redactedMessage redacts the given message", () => {
|
||||
expect(
|
||||
redactableErrorMessage`Failed to create database ${"foo"}`
|
||||
.redactedMessage,
|
||||
).toEqual("Failed to create database [REDACTED]");
|
||||
});
|
||||
|
||||
it("fullMessage returns the correct message for nested redactableErrorMessage", () => {
|
||||
expect(
|
||||
redactableErrorMessage`Failed to create database ${redactableErrorMessage`foo ${"bar"}`}`
|
||||
.fullMessage,
|
||||
).toEqual("Failed to create database foo bar");
|
||||
});
|
||||
|
||||
it("redactedMessage returns the correct message for nested redactableErrorMessage", () => {
|
||||
expect(
|
||||
redactableErrorMessage`Failed to create database ${redactableErrorMessage`foo ${"bar"}`}`
|
||||
.redactedMessage,
|
||||
).toEqual("Failed to create database foo [REDACTED]");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user