mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-06 19:31:15 -07:00
Add extensions logging (#11261)
This commit is contained in:
@@ -140,7 +140,7 @@ export interface GeminiCLIExtension {
|
||||
mcpServers?: Record<string, MCPServerConfig>;
|
||||
contextFiles: string[];
|
||||
excludeTools?: string[];
|
||||
id?: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface ExtensionInstallMetadata {
|
||||
|
||||
@@ -446,6 +446,15 @@ export class ClearcutLogger {
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_START_SESSION_MCP_TOOLS,
|
||||
value: event.mcp_tools ? event.mcp_tools : '',
|
||||
},
|
||||
{
|
||||
gemini_cli_key:
|
||||
EventMetadataKey.GEMINI_CLI_START_SESSION_EXTENSIONS_COUNT,
|
||||
value: event.extensions_count.toString(),
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_START_SESSION_EXTENSION_IDS,
|
||||
value: event.extension_ids.toString(),
|
||||
},
|
||||
];
|
||||
this.sessionData = data;
|
||||
|
||||
@@ -893,6 +902,10 @@ export class ClearcutLogger {
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME,
|
||||
value: event.extension_name,
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_ID,
|
||||
value: event.extension_id,
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_VERSION,
|
||||
value: event.extension_version,
|
||||
@@ -921,6 +934,10 @@ export class ClearcutLogger {
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME,
|
||||
value: event.extension_name,
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_ID,
|
||||
value: event.extension_id,
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_UNINSTALL_STATUS,
|
||||
value: event.status,
|
||||
@@ -941,6 +958,10 @@ export class ClearcutLogger {
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME,
|
||||
value: event.extension_name,
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_ID,
|
||||
value: event.extension_id,
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_VERSION,
|
||||
value: event.extension_version,
|
||||
@@ -1037,6 +1058,10 @@ export class ClearcutLogger {
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME,
|
||||
value: event.extension_name,
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_ID,
|
||||
value: event.extension_id,
|
||||
},
|
||||
{
|
||||
gemini_cli_key:
|
||||
EventMetadataKey.GEMINI_CLI_EXTENSION_ENABLE_SETTING_SCOPE,
|
||||
@@ -1072,6 +1097,10 @@ export class ClearcutLogger {
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME,
|
||||
value: event.extension_name,
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_ID,
|
||||
value: event.extension_id,
|
||||
},
|
||||
{
|
||||
gemini_cli_key:
|
||||
EventMetadataKey.GEMINI_CLI_EXTENSION_DISABLE_SETTING_SCOPE,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
// Defines valid event metadata keys for Clearcut logging.
|
||||
export enum EventMetadataKey {
|
||||
// Deleted enums: 24
|
||||
// Next ID: 117
|
||||
// Next ID: 122
|
||||
|
||||
GEMINI_CLI_KEY_UNKNOWN = 0,
|
||||
|
||||
@@ -373,6 +373,9 @@ export enum EventMetadataKey {
|
||||
// Logs the name of the extension.
|
||||
GEMINI_CLI_EXTENSION_NAME = 85,
|
||||
|
||||
// Logs the name of the extension.
|
||||
GEMINI_CLI_EXTENSION_ID = 121,
|
||||
|
||||
// Logs the version of the extension.
|
||||
GEMINI_CLI_EXTENSION_VERSION = 86,
|
||||
|
||||
@@ -391,6 +394,12 @@ export enum EventMetadataKey {
|
||||
// Logs the status of the extension uninstall
|
||||
GEMINI_CLI_EXTENSION_UPDATE_STATUS = 118,
|
||||
|
||||
// Logs the count of extensions in Start Session Event
|
||||
GEMINI_CLI_START_SESSION_EXTENSIONS_COUNT = 119,
|
||||
|
||||
// Logs the name of extensions as a comma-separated string
|
||||
GEMINI_CLI_START_SESSION_EXTENSION_IDS = 120,
|
||||
|
||||
// Logs the setting scope for an extension enablement.
|
||||
GEMINI_CLI_EXTENSION_ENABLE_SETTING_SCOPE = 102,
|
||||
|
||||
|
||||
@@ -201,6 +201,7 @@ describe('loggers', () => {
|
||||
getTargetDir: () => 'target-dir',
|
||||
getProxy: () => 'http://test.proxy.com:8080',
|
||||
getOutputFormat: () => OutputFormat.JSON,
|
||||
getExtensions: () => [],
|
||||
} as unknown as Config;
|
||||
|
||||
const startSessionEvent = new StartSessionEvent(mockConfig);
|
||||
@@ -229,6 +230,8 @@ describe('loggers', () => {
|
||||
mcp_tools: undefined,
|
||||
mcp_tools_count: undefined,
|
||||
output_format: 'json',
|
||||
extension_ids: '',
|
||||
extensions_count: 0,
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -1042,6 +1045,10 @@ describe('loggers', () => {
|
||||
},
|
||||
required: ['arg1', 'arg2'],
|
||||
},
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
'test-extension',
|
||||
);
|
||||
|
||||
const call: CompletedToolCall = {
|
||||
@@ -1076,6 +1083,7 @@ describe('loggers', () => {
|
||||
'installation.id': 'test-installation-id',
|
||||
'event.name': EVENT_TOOL_CALL,
|
||||
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
||||
extension_id: 'test-extension',
|
||||
function_name: 'mock_mcp_tool',
|
||||
function_args: JSON.stringify(
|
||||
{
|
||||
@@ -1094,6 +1102,7 @@ describe('loggers', () => {
|
||||
error: undefined,
|
||||
error_type: undefined,
|
||||
metadata: undefined,
|
||||
content_length: undefined,
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -1310,7 +1319,8 @@ describe('loggers', () => {
|
||||
|
||||
it('should log extension install event', () => {
|
||||
const event = new ExtensionInstallEvent(
|
||||
'vscode',
|
||||
'testing',
|
||||
'testing-id',
|
||||
'0.1.0',
|
||||
'git',
|
||||
'success',
|
||||
@@ -1323,14 +1333,14 @@ describe('loggers', () => {
|
||||
).toHaveBeenCalledWith(event);
|
||||
|
||||
expect(mockLogger.emit).toHaveBeenCalledWith({
|
||||
body: 'Installed extension vscode',
|
||||
body: 'Installed extension testing',
|
||||
attributes: {
|
||||
'session.id': 'test-session-id',
|
||||
'user.email': 'test-user@example.com',
|
||||
'installation.id': 'test-installation-id',
|
||||
'event.name': EVENT_EXTENSION_INSTALL,
|
||||
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
||||
extension_name: 'vscode',
|
||||
extension_name: 'testing',
|
||||
extension_version: '0.1.0',
|
||||
extension_source: 'git',
|
||||
status: 'success',
|
||||
@@ -1358,7 +1368,8 @@ describe('loggers', () => {
|
||||
|
||||
it('should log extension update event', () => {
|
||||
const event = new ExtensionUpdateEvent(
|
||||
'vscode',
|
||||
'testing',
|
||||
'testing-id',
|
||||
'0.1.0',
|
||||
'0.1.1',
|
||||
'git',
|
||||
@@ -1372,14 +1383,14 @@ describe('loggers', () => {
|
||||
).toHaveBeenCalledWith(event);
|
||||
|
||||
expect(mockLogger.emit).toHaveBeenCalledWith({
|
||||
body: 'Updated extension vscode',
|
||||
body: 'Updated extension testing',
|
||||
attributes: {
|
||||
'session.id': 'test-session-id',
|
||||
'user.email': 'test-user@example.com',
|
||||
'installation.id': 'test-installation-id',
|
||||
'event.name': EVENT_EXTENSION_UPDATE,
|
||||
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
||||
extension_name: 'vscode',
|
||||
extension_name: 'testing',
|
||||
extension_version: '0.1.0',
|
||||
extension_previous_version: '0.1.1',
|
||||
extension_source: 'git',
|
||||
@@ -1407,7 +1418,11 @@ describe('loggers', () => {
|
||||
});
|
||||
|
||||
it('should log extension uninstall event', () => {
|
||||
const event = new ExtensionUninstallEvent('vscode', 'success');
|
||||
const event = new ExtensionUninstallEvent(
|
||||
'testing',
|
||||
'testing-id',
|
||||
'success',
|
||||
);
|
||||
|
||||
logExtensionUninstall(mockConfig, event);
|
||||
|
||||
@@ -1416,14 +1431,14 @@ describe('loggers', () => {
|
||||
).toHaveBeenCalledWith(event);
|
||||
|
||||
expect(mockLogger.emit).toHaveBeenCalledWith({
|
||||
body: 'Uninstalled extension vscode',
|
||||
body: 'Uninstalled extension testing',
|
||||
attributes: {
|
||||
'session.id': 'test-session-id',
|
||||
'user.email': 'test-user@example.com',
|
||||
'installation.id': 'test-installation-id',
|
||||
'event.name': EVENT_EXTENSION_UNINSTALL,
|
||||
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
||||
extension_name: 'vscode',
|
||||
extension_name: 'testing',
|
||||
status: 'success',
|
||||
},
|
||||
});
|
||||
@@ -1445,7 +1460,7 @@ describe('loggers', () => {
|
||||
});
|
||||
|
||||
it('should log extension enable event', () => {
|
||||
const event = new ExtensionEnableEvent('vscode', 'user');
|
||||
const event = new ExtensionEnableEvent('testing', 'testing-id', 'user');
|
||||
|
||||
logExtensionEnable(mockConfig, event);
|
||||
|
||||
@@ -1454,14 +1469,14 @@ describe('loggers', () => {
|
||||
).toHaveBeenCalledWith(event);
|
||||
|
||||
expect(mockLogger.emit).toHaveBeenCalledWith({
|
||||
body: 'Enabled extension vscode',
|
||||
body: 'Enabled extension testing',
|
||||
attributes: {
|
||||
'session.id': 'test-session-id',
|
||||
'user.email': 'test-user@example.com',
|
||||
'installation.id': 'test-installation-id',
|
||||
'event.name': EVENT_EXTENSION_ENABLE,
|
||||
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
||||
extension_name: 'vscode',
|
||||
extension_name: 'testing',
|
||||
setting_scope: 'user',
|
||||
},
|
||||
});
|
||||
@@ -1483,7 +1498,7 @@ describe('loggers', () => {
|
||||
});
|
||||
|
||||
it('should log extension disable event', () => {
|
||||
const event = new ExtensionDisableEvent('vscode', 'user');
|
||||
const event = new ExtensionDisableEvent('testing', 'testing-id', 'user');
|
||||
|
||||
logExtensionDisable(mockConfig, event);
|
||||
|
||||
@@ -1492,14 +1507,14 @@ describe('loggers', () => {
|
||||
).toHaveBeenCalledWith(event);
|
||||
|
||||
expect(mockLogger.emit).toHaveBeenCalledWith({
|
||||
body: 'Disabled extension vscode',
|
||||
body: 'Disabled extension testing',
|
||||
attributes: {
|
||||
'session.id': 'test-session-id',
|
||||
'user.email': 'test-user@example.com',
|
||||
'installation.id': 'test-installation-id',
|
||||
'event.name': EVENT_EXTENSION_DISABLE,
|
||||
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
||||
extension_name: 'vscode',
|
||||
extension_name: 'testing',
|
||||
setting_scope: 'user',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -54,6 +54,8 @@ export class StartSessionEvent implements BaseTelemetryEvent {
|
||||
mcp_tools_count?: number;
|
||||
mcp_tools?: string;
|
||||
output_format: OutputFormat;
|
||||
extensions_count: number;
|
||||
extension_ids: string;
|
||||
|
||||
constructor(config: Config, toolRegistry?: ToolRegistry) {
|
||||
const generatorConfig = config.getContentGeneratorConfig();
|
||||
@@ -85,6 +87,9 @@ export class StartSessionEvent implements BaseTelemetryEvent {
|
||||
config.getFileFilteringRespectGitIgnore();
|
||||
this.mcp_servers_count = mcpServers ? Object.keys(mcpServers).length : 0;
|
||||
this.output_format = config.getOutputFormat();
|
||||
const extensions = config.getExtensions();
|
||||
this.extensions_count = extensions.length;
|
||||
this.extension_ids = extensions.map((e) => e.id).join(',');
|
||||
if (toolRegistry) {
|
||||
const mcpTools = toolRegistry
|
||||
.getAllTools()
|
||||
@@ -116,6 +121,8 @@ export class StartSessionEvent implements BaseTelemetryEvent {
|
||||
mcp_tools: this.mcp_tools,
|
||||
mcp_tools_count: this.mcp_tools_count,
|
||||
output_format: this.output_format,
|
||||
extensions_count: this.extensions_count,
|
||||
extension_ids: this.extension_ids,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -198,6 +205,7 @@ export class ToolCallEvent implements BaseTelemetryEvent {
|
||||
tool_type: 'native' | 'mcp';
|
||||
content_length?: number;
|
||||
mcp_server_name?: string;
|
||||
extension_id?: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
metadata?: { [key: string]: any };
|
||||
|
||||
@@ -243,6 +251,7 @@ export class ToolCallEvent implements BaseTelemetryEvent {
|
||||
) {
|
||||
this.tool_type = 'mcp';
|
||||
this.mcp_server_name = call.tool.serverName;
|
||||
this.extension_id = call.tool.extensionId;
|
||||
} else {
|
||||
this.tool_type = 'native';
|
||||
}
|
||||
@@ -292,6 +301,7 @@ export class ToolCallEvent implements BaseTelemetryEvent {
|
||||
tool_type: this.tool_type,
|
||||
content_length: this.content_length,
|
||||
mcp_server_name: this.mcp_server_name,
|
||||
extension_id: this.extension_id,
|
||||
metadata: this.metadata,
|
||||
};
|
||||
|
||||
@@ -627,6 +637,7 @@ export interface SlashCommandEvent extends BaseTelemetryEvent {
|
||||
command: string;
|
||||
subcommand?: string;
|
||||
status?: SlashCommandStatus;
|
||||
extension_id?: string;
|
||||
toOpenTelemetryAttributes(config: Config): LogAttributes;
|
||||
toLogBody(): string;
|
||||
}
|
||||
@@ -635,6 +646,7 @@ export function makeSlashCommandEvent({
|
||||
command,
|
||||
subcommand,
|
||||
status,
|
||||
extension_id,
|
||||
}: Omit<
|
||||
SlashCommandEvent,
|
||||
CommonFields | 'toOpenTelemetryAttributes' | 'toLogBody'
|
||||
@@ -645,6 +657,7 @@ export function makeSlashCommandEvent({
|
||||
command,
|
||||
subcommand,
|
||||
status,
|
||||
extension_id,
|
||||
toOpenTelemetryAttributes(config: Config): LogAttributes {
|
||||
return {
|
||||
...getCommonAttributes(config),
|
||||
@@ -653,6 +666,7 @@ export function makeSlashCommandEvent({
|
||||
command: this.command,
|
||||
subcommand: this.subcommand,
|
||||
status: this.status,
|
||||
extension_id: this.extension_id,
|
||||
};
|
||||
},
|
||||
toLogBody(): string {
|
||||
@@ -1041,12 +1055,14 @@ export class ExtensionInstallEvent implements BaseTelemetryEvent {
|
||||
'event.name': 'extension_install';
|
||||
'event.timestamp': string;
|
||||
extension_name: string;
|
||||
extension_id: string;
|
||||
extension_version: string;
|
||||
extension_source: string;
|
||||
status: 'success' | 'error';
|
||||
|
||||
constructor(
|
||||
extension_name: string,
|
||||
extension_id: string,
|
||||
extension_version: string,
|
||||
extension_source: string,
|
||||
status: 'success' | 'error',
|
||||
@@ -1054,6 +1070,7 @@ export class ExtensionInstallEvent implements BaseTelemetryEvent {
|
||||
this['event.name'] = 'extension_install';
|
||||
this['event.timestamp'] = new Date().toISOString();
|
||||
this.extension_name = extension_name;
|
||||
this.extension_id = extension_id;
|
||||
this.extension_version = extension_version;
|
||||
this.extension_source = extension_source;
|
||||
this.status = status;
|
||||
@@ -1132,12 +1149,18 @@ export class ExtensionUninstallEvent implements BaseTelemetryEvent {
|
||||
'event.name': 'extension_uninstall';
|
||||
'event.timestamp': string;
|
||||
extension_name: string;
|
||||
extension_id: string;
|
||||
status: 'success' | 'error';
|
||||
|
||||
constructor(extension_name: string, status: 'success' | 'error') {
|
||||
constructor(
|
||||
extension_name: string,
|
||||
extension_id: string,
|
||||
status: 'success' | 'error',
|
||||
) {
|
||||
this['event.name'] = 'extension_uninstall';
|
||||
this['event.timestamp'] = new Date().toISOString();
|
||||
this.extension_name = extension_name;
|
||||
this.extension_id = extension_id;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
@@ -1161,6 +1184,7 @@ export class ExtensionUpdateEvent implements BaseTelemetryEvent {
|
||||
'event.name': 'extension_update';
|
||||
'event.timestamp': string;
|
||||
extension_name: string;
|
||||
extension_id: string;
|
||||
extension_previous_version: string;
|
||||
extension_version: string;
|
||||
extension_source: string;
|
||||
@@ -1168,6 +1192,7 @@ export class ExtensionUpdateEvent implements BaseTelemetryEvent {
|
||||
|
||||
constructor(
|
||||
extension_name: string,
|
||||
extension_id: string,
|
||||
extension_version: string,
|
||||
extension_previous_version: string,
|
||||
extension_source: string,
|
||||
@@ -1176,6 +1201,7 @@ export class ExtensionUpdateEvent implements BaseTelemetryEvent {
|
||||
this['event.name'] = 'extension_update';
|
||||
this['event.timestamp'] = new Date().toISOString();
|
||||
this.extension_name = extension_name;
|
||||
this.extension_id = extension_id;
|
||||
this.extension_version = extension_version;
|
||||
this.extension_previous_version = extension_previous_version;
|
||||
this.extension_source = extension_source;
|
||||
@@ -1205,12 +1231,18 @@ export class ExtensionEnableEvent implements BaseTelemetryEvent {
|
||||
'event.name': 'extension_enable';
|
||||
'event.timestamp': string;
|
||||
extension_name: string;
|
||||
extension_id: string;
|
||||
setting_scope: string;
|
||||
|
||||
constructor(extension_name: string, settingScope: string) {
|
||||
constructor(
|
||||
extension_name: string,
|
||||
extension_id: string,
|
||||
settingScope: string,
|
||||
) {
|
||||
this['event.name'] = 'extension_enable';
|
||||
this['event.timestamp'] = new Date().toISOString();
|
||||
this.extension_name = extension_name;
|
||||
this.extension_id = extension_id;
|
||||
this.setting_scope = settingScope;
|
||||
}
|
||||
|
||||
@@ -1291,12 +1323,18 @@ export class ExtensionDisableEvent implements BaseTelemetryEvent {
|
||||
'event.name': 'extension_disable';
|
||||
'event.timestamp': string;
|
||||
extension_name: string;
|
||||
extension_id: string;
|
||||
setting_scope: string;
|
||||
|
||||
constructor(extension_name: string, settingScope: string) {
|
||||
constructor(
|
||||
extension_name: string,
|
||||
extension_id: string,
|
||||
settingScope: string,
|
||||
) {
|
||||
this['event.name'] = 'extension_disable';
|
||||
this['event.timestamp'] = new Date().toISOString();
|
||||
this.extension_name = extension_name;
|
||||
this.extension_id = extension_id;
|
||||
this.setting_scope = settingScope;
|
||||
}
|
||||
|
||||
|
||||
@@ -610,6 +610,7 @@ export async function discoverTools(
|
||||
mcpServerConfig.trust,
|
||||
undefined,
|
||||
cliConfig,
|
||||
mcpServerConfig.extension?.id,
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
|
||||
@@ -213,6 +213,7 @@ export class DiscoveredMCPTool extends BaseDeclarativeTool<
|
||||
readonly trust?: boolean,
|
||||
nameOverride?: string,
|
||||
private readonly cliConfig?: Config,
|
||||
override readonly extensionId?: string,
|
||||
) {
|
||||
super(
|
||||
nameOverride ?? generateValidName(serverToolName),
|
||||
@@ -222,6 +223,8 @@ export class DiscoveredMCPTool extends BaseDeclarativeTool<
|
||||
parameterSchema,
|
||||
true, // isOutputMarkdown
|
||||
false, // canUpdateOutput
|
||||
undefined, // messageBus
|
||||
extensionId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -235,6 +238,7 @@ export class DiscoveredMCPTool extends BaseDeclarativeTool<
|
||||
this.trust,
|
||||
`${this.serverName}__${this.serverToolName}`,
|
||||
this.cliConfig,
|
||||
this.extensionId,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -290,6 +290,7 @@ export abstract class DeclarativeTool<
|
||||
readonly isOutputMarkdown: boolean = true,
|
||||
readonly canUpdateOutput: boolean = false,
|
||||
readonly messageBus?: MessageBus,
|
||||
readonly extensionId?: string,
|
||||
) {}
|
||||
|
||||
get schema(): FunctionDeclaration {
|
||||
|
||||
Reference in New Issue
Block a user