Add extensions logging (#11261)

This commit is contained in:
christine betts
2025-10-21 16:55:16 -04:00
committed by GitHub
parent e9e80b054d
commit c6a59896f3
16 changed files with 230 additions and 65 deletions

View File

@@ -140,7 +140,7 @@ export interface GeminiCLIExtension {
mcpServers?: Record<string, MCPServerConfig>;
contextFiles: string[];
excludeTools?: string[];
id?: string;
id: string;
}
export interface ExtensionInstallMetadata {

View File

@@ -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,

View File

@@ -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,

View File

@@ -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',
},
});

View File

@@ -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;
}

View File

@@ -610,6 +610,7 @@ export async function discoverTools(
mcpServerConfig.trust,
undefined,
cliConfig,
mcpServerConfig.extension?.id,
),
);
} catch (error) {

View File

@@ -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,
);
}

View File

@@ -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 {