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

[DO NOT MERGE] Initial check-in for supporting telemetry in vscode #6123

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
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
Prev Previous commit
Next Next commit
update telemetry
  • Loading branch information
RodgeFu committed Feb 24, 2025
commit f495b74899e2be8a01aef5940a413c68be4b92d3
11 changes: 8 additions & 3 deletions packages/typespec-vscode/src/extension.ts
Original file line number Diff line number Diff line change
@@ -210,9 +210,14 @@ async function recreateLSPClient(
client = await TspLanguageClient.create(activityId, context, outputChannel);
await oldClient?.stop();
await client.start(activityId);
return client.state === State.Running
? { code: ResultCode.Success, value: client }
: { code: ResultCode.Fail, details: "TspLanguageClient is not running." };
if (client.state === State.Running) {
telemetryClient.logOperationDetailTelemetry(activityId, {
compilerVersion: client.initializeResult?.serverInfo?.version ?? "< 0.64.0",
});
return { code: ResultCode.Success, value: client };
} else {
return { code: ResultCode.Fail, details: "TspLanguageClient is not running." };
}
}

function showStartUpMessages(stateManager: ExtensionStateManager) {
49 changes: 29 additions & 20 deletions packages/typespec-vscode/src/telemetry/telemetry-client.ts
Original file line number Diff line number Diff line change
@@ -4,8 +4,8 @@ import logger from "../log/logger.js";
import { ResultCode } from "../types.js";
import { isWhitespaceStringOrUndefined } from "../utils.js";
import {
createOperationTelemetryEvent,
emptyActivityId,
generateActivityId,
OperationDetailProperties,
OperationDetailTelemetryEvent,
OperationTelemetryEvent,
@@ -59,16 +59,18 @@ class TelemetryClient {

public async doOperationWithTelemetry<T>(
eventName: TelemetryEventName,
/**
* The result will be set automatically if the return type is ResultCode or Result<T>
* Otherwise, you can set the result manually by setting the opTelemetryEvent.result
*/
operation: (
opTelemetryEvent: OperationTelemetryEvent,
/**
* Call this function to send the telemetry event if you don't want to wait until the end of the operation for some reason
*/
/** Call this function to send the telemetry event if you don't want to wait until the end of the operation for some reason*/
sendTelemetryEvent: (result: ResultCode) => void,
) => Promise<T>,
activityId?: string,
): Promise<T> {
const opTelemetryEvent = createOperationTelemetryEvent(eventName, activityId);
const opTelemetryEvent = this.createOperationTelemetryEvent(eventName, activityId);
let eventSent = false;
const sendTelemetryEvent = (result?: ResultCode) => {
if (!eventSent) {
@@ -82,13 +84,11 @@ class TelemetryClient {
};
try {
const result = await operation(opTelemetryEvent, (result) => sendTelemetryEvent(result));
const isResultCode = (v: any) => Object.values(ResultCode).includes(v as ResultCode);
if (result) {
const isResultCode = (v: any) => Object.values(ResultCode).includes(v as ResultCode);
if (isResultCode(result)) {
// TODO: test
opTelemetryEvent.result ??= result as ResultCode;
} else if (typeof result === "object" && "code" in result && isResultCode(result.code)) {
// TODO: test
opTelemetryEvent.result ??= result.code as ResultCode;
}
}
@@ -137,19 +137,28 @@ class TelemetryClient {
}

/**
* Use this method to send error to telemetry.
* IMPORTANT: make sure to:
* - Collect as *little* telemetry as possible.
* - Do not include any personal or sensitive information.
* Detail guidance can be found at: https://code.visualstudio.com/api/extension-guides/telemetry
* Create a operation telemetry event with following default values.
* Please make sure the default values are updated properly as needed
* activityId: a new random guid will be generated if not provided
* eventName: the event name provided
* startTime: set to the current time
* endTime: undefined
* result: undefined
* lastStep: undefined
*/
// public logError(error: string, activityId?: string) {
// this.sendErrorEvent(TelemetryEventName.Error, {
// activityId: isWhitespaceStringOrUndefined(activityId) ? emptyActivityId : activityId!,
// timestamp: new Date().toISOString(),
// error: error,
// });
// }
private createOperationTelemetryEvent(
eventName: TelemetryEventName,
activityId?: string,
): OperationTelemetryEvent {
return {
activityId: isWhitespaceStringOrUndefined(activityId) ? generateActivityId() : activityId!,
eventName: eventName,
startTime: new Date(),
endTime: undefined,
result: undefined,
lastStep: undefined,
};
}

private logErrorWhenLoggingTelemetry(error: any) {
if (this._logTelemetryErrorCount++ < this.MAX_LOG_TELEMETRY_ERROR) {
28 changes: 3 additions & 25 deletions packages/typespec-vscode/src/telemetry/telemetry-event.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ResultCode } from "../types.js";
import { isWhitespaceStringOrUndefined } from "../utils.js";

export enum TelemetryEventName {
StartExtension = "start-extension",
@@ -15,6 +14,7 @@ export class OperationDetailProperties {
error = "error";
emitterPackage = "emitterPackage";
compilerLocation = "compilerLocation";
compilerVersion = "compilerVersion";
}

export interface TelemetryEventBase {
@@ -31,30 +31,8 @@ export interface OperationTelemetryEvent extends TelemetryEventBase {

export interface OperationDetailTelemetryEvent
extends TelemetryEventBase,
Partial<Record<keyof OperationDetailProperties, string>> {}

/**
* Create a operation telemetry event with following default values.
* Please make sure the default values are updated properly as needed
* activityId: a new random guid will be generated if not provided
* eventName: the event name provided
* startTime: set to the current time
* endTime: undefined
* result: undefined
* lastStep: undefined
*/
export function createOperationTelemetryEvent(
eventName: TelemetryEventName,
activityId?: string,
): OperationTelemetryEvent {
return {
activityId: isWhitespaceStringOrUndefined(activityId) ? generateActivityId() : activityId!,
eventName: eventName,
startTime: new Date(),
endTime: undefined,
result: undefined,
lastStep: undefined,
};
Partial<Record<keyof OperationDetailProperties, string>> {
eventName: TelemetryEventName.OperationDetail;
}

export function generateActivityId() {
2 changes: 1 addition & 1 deletion packages/typespec-vscode/src/tsp-executable-resolver.ts
Original file line number Diff line number Diff line change
@@ -80,7 +80,7 @@ export async function resolveTypeSpecServer(
if (serverPath) {
logger.info(`Server path loaded from TypeSpec extension configuration: ${serverPath}`);
telemetryClient.logOperationDetailTelemetry(activityId, {
compilerLocation: "customized-compiler",
compilerLocation: "configured-compiler",
});
} else {
logger.info(
20 changes: 11 additions & 9 deletions packages/typespec-vscode/src/vscode-cmd/import-from-openapi3.ts
Original file line number Diff line number Diff line change
@@ -92,7 +92,6 @@ export async function importFromOpenApi3(uri: vscode.Uri | undefined) {
},
);
}
tel.lastStep = "Importing from OpenAPI";
return result.code;
},
);
@@ -110,6 +109,7 @@ export async function importFromOpenApi3(uri: vscode.Uri | undefined) {
} else {
const result = await tryInstallOpenApi3Locally(packageJson, packageJsonFolder);
if (result.code === ResultCode.Success) {
tel.lastStep = "Install openapi3 locally and import";
return tryImport(sourceFile, targetFolder, false /*globalScope*/);
} else {
tel.lastStep = "Install OpenAPI3 locally";
@@ -139,7 +139,6 @@ export async function importFromOpenApi3(uri: vscode.Uri | undefined) {
logger.info(
`Found ${TSP_OPENAPI3_PACKAGE} in package.json but not installed, try to confirm to install it by 'npm install'`,
);
tel.lastStep = "Install local OpenAPI3 by 'npm install'";
return await tryExecuteWithUi(
{
name: `Confirm and try to install OpenAPI3 package by 'npm install'`,
@@ -188,7 +187,6 @@ export async function importFromOpenApi3(uri: vscode.Uri | undefined) {
logger.info(
`Cannot find ${TSP_OPENAPI3_PACKAGE} in package.json, try to confirm to install it by 'npm install ${TSP_OPENAPI3_PACKAGE}'`,
);
tel.lastStep = "Install local OpenAPI3 by 'npm install ${TSP_OPENAPI3_PACKAGE}'";
return tryExecuteWithUi(
{
name: `Confirm and try to install OpenAPI3 package by 'npm install ${TSP_OPENAPI3_PACKAGE}'`,
@@ -271,7 +269,7 @@ export async function importFromOpenApi3(uri: vscode.Uri | undefined) {
targetFolder: string,
isGlobal: boolean,
): Promise<Result<ExecOutput>> {
return await tryExecuteWithUi(
const result = await tryExecuteWithUi(
{
name: "Importing OpenAPI to TypeSpec",
progress: {
@@ -282,14 +280,12 @@ export async function importFromOpenApi3(uri: vscode.Uri | undefined) {
},
async () => {
if (isGlobal) {
tel.lastStep = "Import using global openapi3";
return await spawnExecutionAndLogToOutput(
TSP_OPENAPI3_COMMAND,
[sourceFile, "--output-dir", `${targetFolder}`],
targetFolder,
);
} else {
tel.lastStep = "Import using local openapi3";
return await spawnExecutionAndLogToOutput(
"npx",
[TSP_OPENAPI3_COMMAND, sourceFile, "--output-dir", `${targetFolder}`],
@@ -298,19 +294,24 @@ export async function importFromOpenApi3(uri: vscode.Uri | undefined) {
}
},
);
if (result.code === ResultCode.Fail) {
telemetryClient.logOperationDetailTelemetry(tel.activityId, {
error: `Error when importing OpenAPI to TypeSpec: ${result.details}`,
});
}
return result;
}

async function importUsingGlobalOpenApi3(
sourceFile: string,
targetFolder: string,
): Promise<Result<ExecOutput>> {
tel.lastStep = "Try to import using global openapi3";
tel.lastStep = "Import using global openapi3";
const firstTry = await tryImport(sourceFile, targetFolder, true /* isGlobal */);
if (firstTry.code === ResultCode.Fail && isExecOutputCmdNotFound(firstTry.details)) {
logger.info(
`${TSP_OPENAPI3_COMMAND} failed because the command is not found. Try to prompt user to install it and import again.`,
);
tel.lastStep = "install OpenAPI3 globally";
const installResult = await tryExecuteWithUi(
{
name: `Install ${TSP_OPENAPI3_PACKAGE} globally`,
@@ -338,9 +339,10 @@ export async function importFromOpenApi3(uri: vscode.Uri | undefined) {
},
);
if (installResult.code === ResultCode.Success) {
tel.lastStep = "Import using global openapi3";
tel.lastStep = "Install openapi3 globally and import again";
return await tryImport(sourceFile, targetFolder, true /* isGlobal */);
} else {
tel.lastStep = "Install openapi3 globally";
return installResult;
}
} else {
Loading
Oops, something went wrong.