Metric(extension) - Add logging for uninstalling extension (#8293)

Co-authored-by: Shi Shu <shii@google.com>
This commit is contained in:
shishu314
2025-09-12 13:38:54 -04:00
committed by GitHub
parent c99539b991
commit 8a5e692373
6 changed files with 78 additions and 9 deletions

View File

@@ -31,6 +31,7 @@ import {
type MCPServerConfig,
ClearcutLogger,
type Config,
ExtensionUninstallEvent,
} from '@google/gemini-cli-core';
import { execSync } from 'node:child_process';
import { SettingScope, loadSettings } from './settings.js';
@@ -76,15 +77,18 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const actual =
await importOriginal<typeof import('@google/gemini-cli-core')>();
const mockLogExtensionInstallEvent = vi.fn();
const mockLogExtensionUninstallEvent = vi.fn();
return {
...actual,
ClearcutLogger: {
getInstance: vi.fn(() => ({
logExtensionInstallEvent: mockLogExtensionInstallEvent,
logExtensionUninstallEvent: mockLogExtensionUninstallEvent,
})),
},
Config: vi.fn(),
ExtensionInstallEvent: vi.fn(),
ExtensionUninstallEvent: vi.fn(),
};
});
@@ -689,6 +693,21 @@ describe('uninstallExtension', () => {
'Extension "nonexistent-extension" not found.',
);
});
it('should log uninstall event', async () => {
createExtension({
extensionsDir: userExtensionsDir,
name: 'my-local-extension',
version: '1.0.0',
});
await uninstallExtension('my-local-extension');
const logger = ClearcutLogger.getInstance({} as Config);
expect(logger?.logExtensionUninstallEvent).toHaveBeenCalledWith(
new ExtensionUninstallEvent('my-local-extension', 'success'),
);
});
});
describe('performWorkspaceExtensionMigration', () => {

View File

@@ -14,6 +14,7 @@ import {
ClearcutLogger,
Config,
ExtensionInstallEvent,
ExtensionUninstallEvent,
} from '@google/gemini-cli-core';
import * as fs from 'node:fs';
import * as path from 'node:path';
@@ -123,6 +124,18 @@ export async function performWorkspaceExtensionMigration(
return failedInstallNames;
}
function getClearcutLogger(cwd: string) {
const config = new Config({
sessionId: randomUUID(),
targetDir: cwd,
cwd,
model: '',
debugMode: false,
});
const logger = ClearcutLogger.getInstance(config);
return logger;
}
export function loadExtensions(
workspaceDir: string = process.cwd(),
): Extension[] {
@@ -403,14 +416,7 @@ export async function installExtension(
installMetadata: ExtensionInstallMetadata,
cwd: string = process.cwd(),
): Promise<string> {
const config = new Config({
sessionId: randomUUID(),
targetDir: process.cwd(),
cwd: process.cwd(),
model: '',
debugMode: false,
});
const logger = ClearcutLogger.getInstance(config);
const logger = getClearcutLogger(cwd);
let newExtensionConfig: ExtensionConfig | null = null;
let localSourcePath: string | undefined;
@@ -563,6 +569,7 @@ export async function uninstallExtension(
extensionName: string,
cwd: string = process.cwd(),
): Promise<void> {
const logger = getClearcutLogger(cwd);
const installedExtensions = loadUserExtensions();
if (
!installedExtensions.some(
@@ -577,10 +584,14 @@ export async function uninstallExtension(
cwd,
);
const storage = new ExtensionStorage(extensionName);
return await fs.promises.rm(storage.getExtensionDir(), {
await fs.promises.rm(storage.getExtensionDir(), {
recursive: true,
force: true,
});
logger?.logExtensionUninstallEvent(
new ExtensionUninstallEvent(extensionName, 'success'),
);
}
export function toOutputString(extension: Extension): string {

View File

@@ -29,6 +29,7 @@ export {
IdeConnectionEvent,
IdeConnectionType,
ExtensionInstallEvent,
ExtensionUninstallEvent,
} from './src/telemetry/types.js';
export { makeFakeConfig } from './src/test-utils/config.js';
export * from './src/utils/pathReader.js';

View File

@@ -26,6 +26,7 @@ import type {
ContentRetryFailureEvent,
ExtensionInstallEvent,
ToolOutputTruncatedEvent,
ExtensionUninstallEvent,
} from '../types.js';
import { EventMetadataKey } from './event-metadata-key.js';
import type { Config } from '../../config/config.js';
@@ -59,6 +60,7 @@ export enum EventNames {
CONTENT_RETRY = 'content_retry',
CONTENT_RETRY_FAILURE = 'content_retry_failure',
EXTENSION_INSTALL = 'extension_install',
EXTENSION_UNINSTALL = 'extension_uninstall',
TOOL_OUTPUT_TRUNCATED = 'tool_output_truncated',
}
@@ -863,6 +865,24 @@ export class ClearcutLogger {
this.flushIfNeeded();
}
logExtensionUninstallEvent(event: ExtensionUninstallEvent): void {
const data: EventValue[] = [
{
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME,
value: event.extension_name,
},
{
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_UNINSTALL_STATUS,
value: event.status,
},
];
this.enqueueLogEvent(
this.createLogEvent(EventNames.EXTENSION_UNINSTALL, data),
);
this.flushIfNeeded();
}
logToolOutputTruncatedEvent(event: ToolOutputTruncatedEvent): void {
const data: EventValue[] = [
{

View File

@@ -350,6 +350,9 @@ export enum EventMetadataKey {
// Logs the status of the extension install.
GEMINI_CLI_EXTENSION_INSTALL_STATUS = 88,
// Logs the status of the extension uninstall
GEMINI_CLI_EXTENSION_UNINSTALL_STATUS = 96,
// ==========================================================================
// Tool Output Truncated Event Keys
// ===========================================================================

View File

@@ -535,6 +535,7 @@ export type TelemetryEvent =
| ContentRetryEvent
| ContentRetryFailureEvent
| ExtensionInstallEvent
| ExtensionUninstallEvent
| ToolOutputTruncatedEvent;
export class ExtensionInstallEvent implements BaseTelemetryEvent {
@@ -590,3 +591,17 @@ export class ToolOutputTruncatedEvent implements BaseTelemetryEvent {
this.lines = details.lines;
}
}
export class ExtensionUninstallEvent implements BaseTelemetryEvent {
'event.name': 'extension_uninstall';
'event.timestamp': string;
extension_name: string;
status: 'success' | 'error';
constructor(extension_name: string, status: 'success' | 'error') {
this['event.name'] = 'extension_uninstall';
this['event.timestamp'] = new Date().toISOString();
this.extension_name = extension_name;
this.status = status;
}
}