mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-20 18:14:29 -07:00
metrics(extension) - Add logging for disable extension (#9238)
Co-authored-by: Shi Shu <shii@google.com>
This commit is contained in:
@@ -25,6 +25,7 @@ import {
|
|||||||
GEMINI_DIR,
|
GEMINI_DIR,
|
||||||
type GeminiCLIExtension,
|
type GeminiCLIExtension,
|
||||||
ExtensionUninstallEvent,
|
ExtensionUninstallEvent,
|
||||||
|
ExtensionDisableEvent,
|
||||||
ExtensionEnableEvent,
|
ExtensionEnableEvent,
|
||||||
} from '@google/gemini-cli-core';
|
} from '@google/gemini-cli-core';
|
||||||
import { execSync } from 'node:child_process';
|
import { execSync } from 'node:child_process';
|
||||||
@@ -71,6 +72,7 @@ vi.mock('./trustedFolders.js', async (importOriginal) => {
|
|||||||
const mockLogExtensionEnable = vi.hoisted(() => vi.fn());
|
const mockLogExtensionEnable = vi.hoisted(() => vi.fn());
|
||||||
const mockLogExtensionInstallEvent = vi.hoisted(() => vi.fn());
|
const mockLogExtensionInstallEvent = vi.hoisted(() => vi.fn());
|
||||||
const mockLogExtensionUninstall = vi.hoisted(() => vi.fn());
|
const mockLogExtensionUninstall = vi.hoisted(() => vi.fn());
|
||||||
|
const mockLogExtensionDisable = vi.hoisted(() => vi.fn());
|
||||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||||
const actual =
|
const actual =
|
||||||
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||||
@@ -79,9 +81,11 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
|||||||
logExtensionEnable: mockLogExtensionEnable,
|
logExtensionEnable: mockLogExtensionEnable,
|
||||||
logExtensionInstallEvent: mockLogExtensionInstallEvent,
|
logExtensionInstallEvent: mockLogExtensionInstallEvent,
|
||||||
logExtensionUninstall: mockLogExtensionUninstall,
|
logExtensionUninstall: mockLogExtensionUninstall,
|
||||||
|
logExtensionDisable: mockLogExtensionDisable,
|
||||||
ExtensionEnableEvent: vi.fn(),
|
ExtensionEnableEvent: vi.fn(),
|
||||||
ExtensionInstallEvent: vi.fn(),
|
ExtensionInstallEvent: vi.fn(),
|
||||||
ExtensionUninstallEvent: vi.fn(),
|
ExtensionUninstallEvent: vi.fn(),
|
||||||
|
ExtensionDisableEvent: vi.fn(),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1178,6 +1182,16 @@ This extension will run the following MCP servers:
|
|||||||
disableExtension('my-extension', SettingScope.System),
|
disableExtension('my-extension', SettingScope.System),
|
||||||
).toThrow('System and SystemDefaults scopes are not supported.');
|
).toThrow('System and SystemDefaults scopes are not supported.');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should log a disable event', () => {
|
||||||
|
disableExtension('ext1', SettingScope.Workspace);
|
||||||
|
|
||||||
|
expect(mockLogExtensionDisable).toHaveBeenCalled();
|
||||||
|
expect(ExtensionDisableEvent).toHaveBeenCalledWith(
|
||||||
|
'ext1',
|
||||||
|
SettingScope.Workspace,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('enableExtension', () => {
|
describe('enableExtension', () => {
|
||||||
|
|||||||
@@ -15,10 +15,12 @@ import {
|
|||||||
Config,
|
Config,
|
||||||
ExtensionInstallEvent,
|
ExtensionInstallEvent,
|
||||||
ExtensionUninstallEvent,
|
ExtensionUninstallEvent,
|
||||||
|
ExtensionDisableEvent,
|
||||||
ExtensionEnableEvent,
|
ExtensionEnableEvent,
|
||||||
logExtensionEnable,
|
logExtensionEnable,
|
||||||
logExtensionInstallEvent,
|
logExtensionInstallEvent,
|
||||||
logExtensionUninstall,
|
logExtensionUninstall,
|
||||||
|
logExtensionDisable,
|
||||||
} from '@google/gemini-cli-core';
|
} from '@google/gemini-cli-core';
|
||||||
import * as fs from 'node:fs';
|
import * as fs from 'node:fs';
|
||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
@@ -648,6 +650,7 @@ export function disableExtension(
|
|||||||
scope: SettingScope,
|
scope: SettingScope,
|
||||||
cwd: string = process.cwd(),
|
cwd: string = process.cwd(),
|
||||||
) {
|
) {
|
||||||
|
const config = getTelemetryConfig(cwd);
|
||||||
if (scope === SettingScope.System || scope === SettingScope.SystemDefaults) {
|
if (scope === SettingScope.System || scope === SettingScope.SystemDefaults) {
|
||||||
throw new Error('System and SystemDefaults scopes are not supported.');
|
throw new Error('System and SystemDefaults scopes are not supported.');
|
||||||
}
|
}
|
||||||
@@ -657,6 +660,7 @@ export function disableExtension(
|
|||||||
);
|
);
|
||||||
const scopePath = scope === SettingScope.Workspace ? cwd : os.homedir();
|
const scopePath = scope === SettingScope.Workspace ? cwd : os.homedir();
|
||||||
manager.disable(name, true, scopePath);
|
manager.disable(name, true, scopePath);
|
||||||
|
logExtensionDisable(config, new ExtensionDisableEvent(name, scope));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function enableExtension(
|
export function enableExtension(
|
||||||
|
|||||||
@@ -27,12 +27,14 @@ export { detectIdeFromEnv } from './src/ide/detect-ide.js';
|
|||||||
export {
|
export {
|
||||||
logExtensionEnable,
|
logExtensionEnable,
|
||||||
logIdeConnection,
|
logIdeConnection,
|
||||||
|
logExtensionDisable,
|
||||||
} from './src/telemetry/loggers.js';
|
} from './src/telemetry/loggers.js';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
IdeConnectionEvent,
|
IdeConnectionEvent,
|
||||||
IdeConnectionType,
|
IdeConnectionType,
|
||||||
ExtensionInstallEvent,
|
ExtensionInstallEvent,
|
||||||
|
ExtensionDisableEvent,
|
||||||
ExtensionEnableEvent,
|
ExtensionEnableEvent,
|
||||||
ExtensionUninstallEvent,
|
ExtensionUninstallEvent,
|
||||||
} from './src/telemetry/types.js';
|
} from './src/telemetry/types.js';
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import type {
|
|||||||
ExtensionUninstallEvent,
|
ExtensionUninstallEvent,
|
||||||
ModelRoutingEvent,
|
ModelRoutingEvent,
|
||||||
ExtensionEnableEvent,
|
ExtensionEnableEvent,
|
||||||
|
ExtensionDisableEvent,
|
||||||
} from '../types.js';
|
} from '../types.js';
|
||||||
import { EventMetadataKey } from './event-metadata-key.js';
|
import { EventMetadataKey } from './event-metadata-key.js';
|
||||||
import type { Config } from '../../config/config.js';
|
import type { Config } from '../../config/config.js';
|
||||||
@@ -63,6 +64,7 @@ export enum EventNames {
|
|||||||
CONTENT_RETRY = 'content_retry',
|
CONTENT_RETRY = 'content_retry',
|
||||||
CONTENT_RETRY_FAILURE = 'content_retry_failure',
|
CONTENT_RETRY_FAILURE = 'content_retry_failure',
|
||||||
EXTENSION_ENABLE = 'extension_enable',
|
EXTENSION_ENABLE = 'extension_enable',
|
||||||
|
EXTENSION_DISABLE = 'extension_disable',
|
||||||
EXTENSION_INSTALL = 'extension_install',
|
EXTENSION_INSTALL = 'extension_install',
|
||||||
EXTENSION_UNINSTALL = 'extension_uninstall',
|
EXTENSION_UNINSTALL = 'extension_uninstall',
|
||||||
TOOL_OUTPUT_TRUNCATED = 'tool_output_truncated',
|
TOOL_OUTPUT_TRUNCATED = 'tool_output_truncated',
|
||||||
@@ -984,6 +986,25 @@ export class ClearcutLogger {
|
|||||||
this.flushIfNeeded();
|
this.flushIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logExtensionDisableEvent(event: ExtensionDisableEvent): void {
|
||||||
|
const data: EventValue[] = [
|
||||||
|
{
|
||||||
|
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME,
|
||||||
|
value: event.extension_name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gemini_cli_key:
|
||||||
|
EventMetadataKey.GEMINI_CLI_EXTENSION_DISABLE_SETTING_SCOPE,
|
||||||
|
value: event.setting_scope,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
this.enqueueLogEvent(
|
||||||
|
this.createLogEvent(EventNames.EXTENSION_DISABLE, data),
|
||||||
|
);
|
||||||
|
this.flushIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds default fields to data, and returns a new data array. This fields
|
* Adds default fields to data, and returns a new data array. This fields
|
||||||
* should exist on all log events.
|
* should exist on all log events.
|
||||||
|
|||||||
@@ -362,6 +362,9 @@ export enum EventMetadataKey {
|
|||||||
// Logs the setting scope for an extension enablement.
|
// Logs the setting scope for an extension enablement.
|
||||||
GEMINI_CLI_EXTENSION_ENABLE_SETTING_SCOPE = 102,
|
GEMINI_CLI_EXTENSION_ENABLE_SETTING_SCOPE = 102,
|
||||||
|
|
||||||
|
// Logs the setting scope for an extension disablement.
|
||||||
|
GEMINI_CLI_EXTENSION_DISABLE_SETTING_SCOPE = 107,
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Tool Output Truncated Event Keys
|
// Tool Output Truncated Event Keys
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export const EVENT_API_REQUEST = 'gemini_cli.api_request';
|
|||||||
export const EVENT_API_ERROR = 'gemini_cli.api_error';
|
export const EVENT_API_ERROR = 'gemini_cli.api_error';
|
||||||
export const EVENT_API_RESPONSE = 'gemini_cli.api_response';
|
export const EVENT_API_RESPONSE = 'gemini_cli.api_response';
|
||||||
export const EVENT_CLI_CONFIG = 'gemini_cli.config';
|
export const EVENT_CLI_CONFIG = 'gemini_cli.config';
|
||||||
|
export const EVENT_EXTENSION_DISABLE = 'gemini_cli.extension_disable';
|
||||||
export const EVENT_EXTENSION_ENABLE = 'gemini_cli.extension_enable';
|
export const EVENT_EXTENSION_ENABLE = 'gemini_cli.extension_enable';
|
||||||
export const EVENT_EXTENSION_INSTALL = 'gemini_cli.extension_install';
|
export const EVENT_EXTENSION_INSTALL = 'gemini_cli.extension_install';
|
||||||
export const EVENT_EXTENSION_UNINSTALL = 'gemini_cli.extension_uninstall';
|
export const EVENT_EXTENSION_UNINSTALL = 'gemini_cli.extension_uninstall';
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import {
|
|||||||
EVENT_RIPGREP_FALLBACK,
|
EVENT_RIPGREP_FALLBACK,
|
||||||
EVENT_MODEL_ROUTING,
|
EVENT_MODEL_ROUTING,
|
||||||
EVENT_EXTENSION_ENABLE,
|
EVENT_EXTENSION_ENABLE,
|
||||||
|
EVENT_EXTENSION_DISABLE,
|
||||||
EVENT_EXTENSION_INSTALL,
|
EVENT_EXTENSION_INSTALL,
|
||||||
EVENT_EXTENSION_UNINSTALL,
|
EVENT_EXTENSION_UNINSTALL,
|
||||||
} from './constants.js';
|
} from './constants.js';
|
||||||
@@ -51,6 +52,7 @@ import {
|
|||||||
logToolOutputTruncated,
|
logToolOutputTruncated,
|
||||||
logModelRouting,
|
logModelRouting,
|
||||||
logExtensionEnable,
|
logExtensionEnable,
|
||||||
|
logExtensionDisable,
|
||||||
logExtensionInstallEvent,
|
logExtensionInstallEvent,
|
||||||
logExtensionUninstall,
|
logExtensionUninstall,
|
||||||
} from './loggers.js';
|
} from './loggers.js';
|
||||||
@@ -69,6 +71,7 @@ import {
|
|||||||
ToolOutputTruncatedEvent,
|
ToolOutputTruncatedEvent,
|
||||||
ModelRoutingEvent,
|
ModelRoutingEvent,
|
||||||
ExtensionEnableEvent,
|
ExtensionEnableEvent,
|
||||||
|
ExtensionDisableEvent,
|
||||||
ExtensionInstallEvent,
|
ExtensionInstallEvent,
|
||||||
ExtensionUninstallEvent,
|
ExtensionUninstallEvent,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
@@ -1308,4 +1311,41 @@ describe('loggers', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('logExtensionDisable', () => {
|
||||||
|
const mockConfig = {
|
||||||
|
getSessionId: () => 'test-session-id',
|
||||||
|
getUsageStatisticsEnabled: () => true,
|
||||||
|
} as unknown as Config;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.spyOn(ClearcutLogger.prototype, 'logExtensionDisableEvent');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should log extension disable event', () => {
|
||||||
|
const event = new ExtensionDisableEvent('vscode', 'user');
|
||||||
|
|
||||||
|
logExtensionDisable(mockConfig, event);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
ClearcutLogger.prototype.logExtensionDisableEvent,
|
||||||
|
).toHaveBeenCalledWith(event);
|
||||||
|
|
||||||
|
expect(mockLogger.emit).toHaveBeenCalledWith({
|
||||||
|
body: 'Disabled extension vscode',
|
||||||
|
attributes: {
|
||||||
|
'session.id': 'test-session-id',
|
||||||
|
'user.email': 'test-user@example.com',
|
||||||
|
'event.name': EVENT_EXTENSION_DISABLE,
|
||||||
|
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
||||||
|
extension_name: 'vscode',
|
||||||
|
setting_scope: 'user',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import {
|
|||||||
EVENT_RIPGREP_FALLBACK,
|
EVENT_RIPGREP_FALLBACK,
|
||||||
EVENT_MODEL_ROUTING,
|
EVENT_MODEL_ROUTING,
|
||||||
EVENT_EXTENSION_INSTALL,
|
EVENT_EXTENSION_INSTALL,
|
||||||
|
EVENT_EXTENSION_DISABLE,
|
||||||
} from './constants.js';
|
} from './constants.js';
|
||||||
import type {
|
import type {
|
||||||
ApiErrorEvent,
|
ApiErrorEvent,
|
||||||
@@ -57,6 +58,7 @@ import type {
|
|||||||
RipgrepFallbackEvent,
|
RipgrepFallbackEvent,
|
||||||
ToolOutputTruncatedEvent,
|
ToolOutputTruncatedEvent,
|
||||||
ModelRoutingEvent,
|
ModelRoutingEvent,
|
||||||
|
ExtensionDisableEvent,
|
||||||
ExtensionEnableEvent,
|
ExtensionEnableEvent,
|
||||||
ExtensionUninstallEvent,
|
ExtensionUninstallEvent,
|
||||||
ExtensionInstallEvent,
|
ExtensionInstallEvent,
|
||||||
@@ -767,3 +769,25 @@ export function logExtensionEnable(
|
|||||||
};
|
};
|
||||||
logger.emit(logRecord);
|
logger.emit(logRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function logExtensionDisable(
|
||||||
|
config: Config,
|
||||||
|
event: ExtensionDisableEvent,
|
||||||
|
): void {
|
||||||
|
ClearcutLogger.getInstance(config)?.logExtensionDisableEvent(event);
|
||||||
|
if (!isTelemetrySdkInitialized()) return;
|
||||||
|
|
||||||
|
const attributes: LogAttributes = {
|
||||||
|
...getCommonAttributes(config),
|
||||||
|
...event,
|
||||||
|
'event.name': EVENT_EXTENSION_DISABLE,
|
||||||
|
'event.timestamp': new Date().toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const logger = logs.getLogger(SERVICE_NAME);
|
||||||
|
const logRecord: LogRecord = {
|
||||||
|
body: `Disabled extension ${event.extension_name}`,
|
||||||
|
attributes,
|
||||||
|
};
|
||||||
|
logger.emit(logRecord);
|
||||||
|
}
|
||||||
|
|||||||
@@ -669,3 +669,17 @@ export class ExtensionEnableEvent implements BaseTelemetryEvent {
|
|||||||
this.setting_scope = settingScope;
|
this.setting_scope = settingScope;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ExtensionDisableEvent implements BaseTelemetryEvent {
|
||||||
|
'event.name': 'extension_disable';
|
||||||
|
'event.timestamp': string;
|
||||||
|
extension_name: string;
|
||||||
|
setting_scope: string;
|
||||||
|
|
||||||
|
constructor(extension_name: string, settingScope: string) {
|
||||||
|
this['event.name'] = 'extension_disable';
|
||||||
|
this['event.timestamp'] = new Date().toISOString();
|
||||||
|
this.extension_name = extension_name;
|
||||||
|
this.setting_scope = settingScope;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user