Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Report unhandled errors, but only those that are from our extension #2125

Merged
merged 1 commit into from
Mar 3, 2023
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
2 changes: 2 additions & 0 deletions extensions/ql-vscode/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## [UNRELEASED]

- Send telemetry about unhandled errors happening within the extension. [#2125](https://github.com/github/vscode-codeql/pull/2125)

## 1.7.11 - 1 March 2023

- Enable collection of telemetry concerning interactions with UI elements, including buttons, links, and other inputs. [#2114](https://github.com/github/vscode-codeql/pull/2114)
Expand Down
51 changes: 50 additions & 1 deletion extensions/ql-vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,12 @@ import {
tmpDir,
tmpDirDisposal,
} from "./helpers";
import { asError, assertNever, getErrorMessage } from "./pure/helpers-pure";
import {
asError,
assertNever,
getErrorMessage,
getErrorStack,
} from "./pure/helpers-pure";
import { spawnIdeServer } from "./ide-server";
import { ResultsView } from "./interface";
import { WebviewReveal } from "./interface-utils";
Expand Down Expand Up @@ -235,6 +240,7 @@ export async function activate(
const distributionConfigListener = new DistributionConfigListener();
await initializeLogging(ctx);
await initializeTelemetry(extension, ctx);
addUnhandledRejectionListener();
install();

const codelensProvider = new QuickEvalCodeLensProvider();
Expand Down Expand Up @@ -1569,6 +1575,49 @@ async function activateWithInstalledDistribution(
};
}

function addUnhandledRejectionListener() {
const handler = (error: unknown) => {
// This listener will be triggered for errors from other extensions as
// well as errors from this extension. We don't want to flood the user
// with popups about errors from other extensions, and we don't want to
// report them in our telemetry.
//
// The stack trace gets redacted before being sent as telemetry, but at
// this point in the code we have the full unredacted information.
const isFromThisExtension =
extension && getErrorStack(error).includes(extension.extensionPath);

Comment on lines +1587 to +1589
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice...so this will handle the local development case as well as bundled extensions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does still work for local development, because the extension path still matches the location of the installed extension. In my case it's /Users/robertbrignull/github/vscode-codeql/extensions/ql-vscode.

if (isFromThisExtension) {
const message = redactableError(
asError(error),
)`Unhandled error: ${getErrorMessage(error)}`;
// Add a catch so that showAndLogExceptionWithTelemetry fails, we avoid
// triggering "unhandledRejection" and avoid an infinite loop
showAndLogExceptionWithTelemetry(message).catch(
(telemetryError: unknown) => {
void extLogger.log(
`Failed to send error telemetry: ${getErrorMessage(
telemetryError,
)}`,
);
void extLogger.log(message.fullMessage);
},
);
}
};

// "uncaughtException" will trigger whenever an exception reaches the top level.
// This covers extension initialization and any code within a `setTimeout`.
// Notably this does not include exceptions thrown when executing commands,
// because `commandRunner` wraps the command body and handles errors.
process.addListener("uncaughtException", handler);

// "unhandledRejection" will trigger whenever any promise is rejected and it is
// not handled by a "catch" somewhere in the promise chain. This includes when
// a promise is used with the "void" operator.
process.addListener("unhandledRejection", handler);
}

async function createQueryServer(
qlConfigurationListener: QueryServerConfigListener,
cliServer: CodeQLCliServer,
Expand Down