Add the CLI version to telemetry events

This adds the CLI version to telemetry command-usage events.

Note that the CLI server is created after the telemetry listener is
created. The first few telemetry events may have a "not-set" value for
the CLI version.
This commit is contained in:
Andrew Eisenberg
2023-04-12 16:34:14 -07:00
parent eabcd00e75
commit d6b3e51f15
4 changed files with 53 additions and 9 deletions

View File

@@ -75,7 +75,7 @@ export function registerCommandWithErrorHandling(
return undefined;
} finally {
const executionTime = Date.now() - startTime;
telemetryListener?.sendCommandUsage(commandId, executionTime, error);
void telemetryListener?.sendCommandUsage(commandId, executionTime, error);
}
});
}

View File

@@ -306,7 +306,7 @@ export async function activate(
const distributionConfigListener = new DistributionConfigListener();
await initializeLogging(ctx);
await initializeTelemetry(extension, ctx);
const telemetryListener = await initializeTelemetry(extension, ctx);
addUnhandledRejectionListener();
install();
@@ -395,6 +395,7 @@ export async function activate(
variantAnalysisViewSerializer.onExtensionLoaded(
codeQlExtension.variantAnalysisManager,
);
telemetryListener.cli = codeQlExtension.cliServer;
}
return codeQlExtension;

View File

@@ -19,6 +19,7 @@ import { extLogger } from "./common";
import { UserCancellationException } from "./progress";
import { showBinaryChoiceWithUrlDialog } from "./helpers";
import { RedactableError } from "./pure/errors";
import { CodeQLCliServer } from "./cli";
// Key is injected at build time through the APP_INSIGHTS_KEY environment variable.
const key = "REPLACE-APP-INSIGHTS-KEY";
@@ -56,6 +57,8 @@ export class TelemetryListener extends ConfigListener {
private reporter?: TelemetryReporter;
private _cli?: CodeQLCliServer;
constructor(
private readonly id: string,
private readonly version: string,
@@ -147,7 +150,7 @@ export class TelemetryListener extends ConfigListener {
void this.reporter?.dispose();
}
sendCommandUsage(name: string, executionTime: number, error?: Error) {
async sendCommandUsage(name: string, executionTime: number, error?: Error) {
if (!this.reporter) {
return;
}
@@ -157,12 +160,18 @@ export class TelemetryListener extends ConfigListener {
? CommandCompletion.Cancelled
: CommandCompletion.Failed;
const cliVersion = this._cli
? (await this._cli.getVersion()).toString()
: // telemetry events that are sent before the cli is initialized will not have a version number
"not-set";
this.reporter.sendTelemetryEvent(
"command-usage",
{
name,
status,
isCanary: isCanary().toString(),
cliVersion,
},
{ executionTime },
);
@@ -241,6 +250,10 @@ export class TelemetryListener extends ConfigListener {
return this.reporter;
}
set cli(cli: CodeQLCliServer) {
this._cli = cli;
}
private disposeReporter() {
if (this.reporter) {
void this.reporter.dispose();
@@ -265,7 +278,7 @@ export let telemetryListener: TelemetryListener | undefined;
export async function initializeTelemetry(
extension: Extension<any>,
ctx: ExtensionContext,
): Promise<void> {
): Promise<TelemetryListener> {
if (telemetryListener !== undefined) {
throw new Error("Telemetry is already initialized");
}
@@ -279,4 +292,5 @@ export async function initializeTelemetry(
// this is a particular problem during integration tests, which will hang if a modal popup is displayed.
void telemetryListener.initialize();
ctx.subscriptions.push(telemetryListener);
return telemetryListener;
}

View File

@@ -185,7 +185,7 @@ describe("telemetry reporting", () => {
it("should send an event", async () => {
await telemetryListener.initialize();
telemetryListener.sendCommandUsage("command-id", 1234, undefined);
await telemetryListener.sendCommandUsage("command-id", 1234, undefined);
expect(sendTelemetryEventSpy).toHaveBeenCalledWith(
"command-usage",
@@ -193,6 +193,7 @@ describe("telemetry reporting", () => {
name: "command-id",
status: "Success",
isCanary,
cliVersion: "not-set",
},
{ executionTime: 1234 },
);
@@ -203,7 +204,7 @@ describe("telemetry reporting", () => {
it("should send a command usage event with an error", async () => {
await telemetryListener.initialize();
telemetryListener.sendCommandUsage(
await telemetryListener.sendCommandUsage(
"command-id",
1234,
new UserCancellationException(),
@@ -215,6 +216,33 @@ describe("telemetry reporting", () => {
name: "command-id",
status: "Cancelled",
isCanary,
cliVersion: "not-set",
},
{ executionTime: 1234 },
);
expect(sendTelemetryExceptionSpy).not.toBeCalled();
});
it("should send a command usage event with a cli version", async () => {
await telemetryListener.initialize();
telemetryListener.cli = {
getVersion: () => Promise.resolve("1.2.3"),
} as any;
await telemetryListener.sendCommandUsage(
"command-id",
1234,
new UserCancellationException(),
);
expect(sendTelemetryEventSpy).toHaveBeenCalledWith(
"command-usage",
{
name: "command-id",
status: "Cancelled",
isCanary,
cliVersion: "1.2.3",
},
{ executionTime: 1234 },
);
@@ -226,8 +254,8 @@ describe("telemetry reporting", () => {
await telemetryListener.initialize();
await enableTelemetry("codeQL.telemetry", false);
telemetryListener.sendCommandUsage("command-id", 1234, undefined);
telemetryListener.sendCommandUsage("command-id", 1234, new Error());
await telemetryListener.sendCommandUsage("command-id", 1234, undefined);
await telemetryListener.sendCommandUsage("command-id", 1234, new Error());
expect(sendTelemetryEventSpy).not.toBeCalled();
expect(sendTelemetryExceptionSpy).not.toBeCalled();
@@ -238,7 +266,7 @@ describe("telemetry reporting", () => {
await enableTelemetry("codeQL.telemetry", false);
await enableTelemetry("codeQL.telemetry", true);
telemetryListener.sendCommandUsage("command-id", 1234, undefined);
await telemetryListener.sendCommandUsage("command-id", 1234, undefined);
expect(sendTelemetryEventSpy).toHaveBeenCalledWith(
"command-usage",
@@ -246,6 +274,7 @@ describe("telemetry reporting", () => {
name: "command-id",
status: "Success",
isCanary,
cliVersion: "not-set",
},
{ executionTime: 1234 },
);