From 38bc856212b957e845113fa5fd58a1f81ffbe4ac Mon Sep 17 00:00:00 2001 From: Jerop Kipruto Date: Fri, 10 Oct 2025 12:06:08 -0400 Subject: [PATCH] feat(telemetry): ensure all telemetry includes user email and installation id (#10897) --- docs/cli/telemetry.md | 3 +- packages/core/src/telemetry/loggers.test.ts | 31 ++++ packages/core/src/telemetry/metrics.test.ts | 143 ++++++++++++++++++ packages/core/src/telemetry/metrics.ts | 5 +- .../core/src/telemetry/telemetryAttributes.ts | 10 +- 5 files changed, 185 insertions(+), 7 deletions(-) diff --git a/docs/cli/telemetry.md b/docs/cli/telemetry.md index 92d45f4057..954fe4d90e 100644 --- a/docs/cli/telemetry.md +++ b/docs/cli/telemetry.md @@ -204,7 +204,8 @@ For local development and debugging, you can capture telemetry data locally: The following section describes the structure of logs and metrics generated for Gemini CLI. -- A `sessionId` is included as a common attribute on all logs and metrics. +The `session.id`, `installation.id`, and `user.email` are included as common +attributes on all logs and metrics. ### Logs diff --git a/packages/core/src/telemetry/loggers.test.ts b/packages/core/src/telemetry/loggers.test.ts index 4320f5df28..4145bfcb0b 100644 --- a/packages/core/src/telemetry/loggers.test.ts +++ b/packages/core/src/telemetry/loggers.test.ts @@ -100,6 +100,7 @@ import * as uiTelemetry from './uiTelemetry.js'; import { makeFakeConfig } from '../test-utils/config.js'; import { ClearcutLogger } from './clearcut-logger/clearcut-logger.js'; import { UserAccountManager } from '../utils/userAccountManager.js'; +import { InstallationManager } from '../utils/installationManager.js'; import { AgentTerminateMode } from '../agents/types.js'; describe('loggers', () => { @@ -121,6 +122,10 @@ describe('loggers', () => { UserAccountManager.prototype, 'getCachedGoogleAccount', ).mockReturnValue('test-user@example.com'); + vi.spyOn( + InstallationManager.prototype, + 'getInstallationId', + ).mockReturnValue('test-installation-id'); vi.useFakeTimers(); vi.setSystemTime(new Date('2025-01-01T00:00:00.000Z')); }); @@ -203,6 +208,7 @@ describe('loggers', () => { attributes: { 'session.id': 'test-session-id', 'user.email': 'test-user@example.com', + 'installation.id': 'test-installation-id', 'event.name': EVENT_CLI_CONFIG, 'event.timestamp': '2025-01-01T00:00:00.000Z', model: 'test-model', @@ -248,6 +254,7 @@ describe('loggers', () => { attributes: { 'session.id': 'test-session-id', 'user.email': 'test-user@example.com', + 'installation.id': 'test-installation-id', 'event.name': EVENT_USER_PROMPT, 'event.timestamp': '2025-01-01T00:00:00.000Z', prompt_length: 11, @@ -280,6 +287,7 @@ describe('loggers', () => { attributes: { 'session.id': 'test-session-id', 'user.email': 'test-user@example.com', + 'installation.id': 'test-installation-id', 'event.name': EVENT_USER_PROMPT, 'event.timestamp': '2025-01-01T00:00:00.000Z', prompt_length: 11, @@ -346,6 +354,7 @@ describe('loggers', () => { attributes: { 'session.id': 'test-session-id', 'user.email': 'test-user@example.com', + 'installation.id': 'test-installation-id', 'event.name': EVENT_API_RESPONSE, 'event.timestamp': '2025-01-01T00:00:00.000Z', [SemanticAttributes.HTTP_STATUS_CODE]: 200, @@ -441,6 +450,7 @@ describe('loggers', () => { attributes: { 'session.id': 'test-session-id', 'user.email': 'test-user@example.com', + 'installation.id': 'test-installation-id', 'event.name': EVENT_API_REQUEST, 'event.timestamp': '2025-01-01T00:00:00.000Z', model: 'test-model', @@ -460,6 +470,7 @@ describe('loggers', () => { attributes: { 'session.id': 'test-session-id', 'user.email': 'test-user@example.com', + 'installation.id': 'test-installation-id', 'event.name': EVENT_API_REQUEST, 'event.timestamp': '2025-01-01T00:00:00.000Z', model: 'test-model', @@ -485,6 +496,7 @@ describe('loggers', () => { attributes: { 'session.id': 'test-session-id', 'user.email': 'test-user@example.com', + 'installation.id': 'test-installation-id', 'event.name': EVENT_FLASH_FALLBACK, 'event.timestamp': '2025-01-01T00:00:00.000Z', auth_type: 'vertex-ai', @@ -518,6 +530,7 @@ describe('loggers', () => { expect.objectContaining({ 'session.id': 'test-session-id', 'user.email': 'test-user@example.com', + 'installation.id': 'test-installation-id', 'event.name': EVENT_RIPGREP_FALLBACK, error: undefined, }), @@ -539,6 +552,7 @@ describe('loggers', () => { expect.objectContaining({ 'session.id': 'test-session-id', 'user.email': 'test-user@example.com', + 'installation.id': 'test-installation-id', 'event.name': EVENT_RIPGREP_FALLBACK, error: 'rg not found', }), @@ -651,6 +665,7 @@ describe('loggers', () => { attributes: { 'session.id': 'test-session-id', 'user.email': 'test-user@example.com', + 'installation.id': 'test-installation-id', 'event.name': EVENT_TOOL_CALL, 'event.timestamp': '2025-01-01T00:00:00.000Z', function_name: 'test-function', @@ -738,6 +753,7 @@ describe('loggers', () => { attributes: { 'session.id': 'test-session-id', 'user.email': 'test-user@example.com', + 'installation.id': 'test-installation-id', 'event.name': EVENT_TOOL_CALL, 'event.timestamp': '2025-01-01T00:00:00.000Z', function_name: 'test-function', @@ -814,6 +830,7 @@ describe('loggers', () => { attributes: { 'session.id': 'test-session-id', 'user.email': 'test-user@example.com', + 'installation.id': 'test-installation-id', 'event.name': EVENT_TOOL_CALL, 'event.timestamp': '2025-01-01T00:00:00.000Z', function_name: 'test-function', @@ -889,6 +906,7 @@ describe('loggers', () => { attributes: { 'session.id': 'test-session-id', 'user.email': 'test-user@example.com', + 'installation.id': 'test-installation-id', 'event.name': EVENT_TOOL_CALL, 'event.timestamp': '2025-01-01T00:00:00.000Z', function_name: 'test-function', @@ -963,6 +981,7 @@ describe('loggers', () => { attributes: { 'session.id': 'test-session-id', 'user.email': 'test-user@example.com', + 'installation.id': 'test-installation-id', 'event.name': EVENT_TOOL_CALL, 'event.timestamp': '2025-01-01T00:00:00.000Z', function_name: 'test-function', @@ -1051,6 +1070,7 @@ describe('loggers', () => { attributes: { 'session.id': 'test-session-id', 'user.email': 'test-user@example.com', + 'installation.id': 'test-installation-id', 'event.name': EVENT_TOOL_CALL, 'event.timestamp': '2025-01-01T00:00:00.000Z', function_name: 'mock_mcp_tool', @@ -1096,6 +1116,7 @@ describe('loggers', () => { attributes: { 'session.id': 'test-session-id', 'user.email': 'test-user@example.com', + 'installation.id': 'test-installation-id', 'event.name': EVENT_MALFORMED_JSON_RESPONSE, 'event.timestamp': '2025-01-01T00:00:00.000Z', model: 'test-model', @@ -1140,6 +1161,7 @@ describe('loggers', () => { attributes: { 'session.id': 'test-session-id', 'user.email': 'test-user@example.com', + 'installation.id': 'test-installation-id', 'event.name': EVENT_FILE_OPERATION, 'event.timestamp': '2025-01-01T00:00:00.000Z', tool_name: 'test-tool', @@ -1186,6 +1208,7 @@ describe('loggers', () => { attributes: { 'session.id': 'test-session-id', 'user.email': 'test-user@example.com', + 'installation.id': 'test-installation-id', 'event.name': EVENT_TOOL_OUTPUT_TRUNCATED, 'event.timestamp': '2025-01-01T00:00:00.000Z', eventName: 'tool_output_truncated', @@ -1232,6 +1255,7 @@ describe('loggers', () => { attributes: { 'session.id': 'test-session-id', 'user.email': 'test-user@example.com', + 'installation.id': 'test-installation-id', ...event, 'event.name': EVENT_MODEL_ROUTING, }, @@ -1297,6 +1321,7 @@ describe('loggers', () => { 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', @@ -1336,6 +1361,7 @@ describe('loggers', () => { 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', @@ -1373,6 +1399,7 @@ describe('loggers', () => { 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', @@ -1410,6 +1437,7 @@ describe('loggers', () => { 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', @@ -1443,6 +1471,7 @@ describe('loggers', () => { attributes: { 'session.id': 'test-session-id', 'user.email': 'test-user@example.com', + 'installation.id': 'test-installation-id', 'event.name': EVENT_AGENT_START, 'event.timestamp': '2025-01-01T00:00:00.000Z', agent_id: 'agent-123', @@ -1483,6 +1512,7 @@ describe('loggers', () => { attributes: { 'session.id': 'test-session-id', 'user.email': 'test-user@example.com', + 'installation.id': 'test-installation-id', 'event.name': EVENT_AGENT_FINISH, 'event.timestamp': '2025-01-01T00:00:00.000Z', agent_id: 'agent-123', @@ -1524,6 +1554,7 @@ describe('loggers', () => { attributes: { 'session.id': 'test-session-id', 'user.email': 'test-user@example.com', + 'installation.id': 'test-installation-id', 'event.name': EVENT_WEB_FETCH_FALLBACK_ATTEMPT, 'event.timestamp': '2025-01-01T00:00:00.000Z', reason: 'private_ip', diff --git a/packages/core/src/telemetry/metrics.test.ts b/packages/core/src/telemetry/metrics.test.ts index 3444a591e9..e3816fe051 100644 --- a/packages/core/src/telemetry/metrics.test.ts +++ b/packages/core/src/telemetry/metrics.test.ts @@ -71,6 +71,7 @@ function originalOtelMockFactory() { } vi.mock('@opentelemetry/api'); +vi.mock('./telemetryAttributes.js'); describe('Telemetry Metrics', () => { let initializeMetricsModule: typeof import('./metrics.js').initializeMetrics; @@ -100,6 +101,13 @@ describe('Telemetry Metrics', () => { return actualApi; }); + const { getCommonAttributes } = await import('./telemetryAttributes.js'); + (getCommonAttributes as Mock).mockReturnValue({ + 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', + }); + const metricsJsModule = await import('./metrics.js'); initializeMetricsModule = metricsJsModule.initializeMetrics; recordTokenUsageMetricsModule = metricsJsModule.recordTokenUsageMetrics; @@ -138,6 +146,23 @@ describe('Telemetry Metrics', () => { mockCreateHistogramFn.mockReturnValue(mockHistogramInstance); }); + describe('initializeMetrics', () => { + const mockConfig = { + getSessionId: () => 'test-session-id', + getTelemetryEnabled: () => true, + } as unknown as Config; + + it('should apply common attributes including email', () => { + initializeMetricsModule(mockConfig); + + expect(mockCounterAddFn).toHaveBeenCalledWith(1, { + 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', + }); + }); + }); + describe('recordChatCompressionMetrics', () => { it('does not record metrics if not initialized', () => { const lol = makeFakeConfig({}); @@ -161,6 +186,8 @@ describe('Telemetry Metrics', () => { expect(mockCounterAddFn).toHaveBeenCalledWith(1, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', tokens_after: 100, tokens_before: 200, }); @@ -190,9 +217,13 @@ describe('Telemetry Metrics', () => { expect(mockCounterAddFn).toHaveBeenCalledTimes(2); expect(mockCounterAddFn).toHaveBeenNthCalledWith(1, 1, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', }); expect(mockCounterAddFn).toHaveBeenNthCalledWith(2, 100, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', model: 'gemini-pro', type: 'input', }); @@ -208,6 +239,8 @@ describe('Telemetry Metrics', () => { }); expect(mockCounterAddFn).toHaveBeenCalledWith(50, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', model: 'gemini-pro', type: 'output', }); @@ -218,6 +251,8 @@ describe('Telemetry Metrics', () => { }); expect(mockCounterAddFn).toHaveBeenCalledWith(25, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', model: 'gemini-pro', type: 'thought', }); @@ -228,6 +263,8 @@ describe('Telemetry Metrics', () => { }); expect(mockCounterAddFn).toHaveBeenCalledWith(75, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', model: 'gemini-pro', type: 'cache', }); @@ -238,6 +275,8 @@ describe('Telemetry Metrics', () => { }); expect(mockCounterAddFn).toHaveBeenCalledWith(125, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', model: 'gemini-pro', type: 'tool', }); @@ -253,6 +292,8 @@ describe('Telemetry Metrics', () => { }); expect(mockCounterAddFn).toHaveBeenCalledWith(200, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', model: 'gemini-ultra', type: 'input', }); @@ -287,9 +328,13 @@ describe('Telemetry Metrics', () => { expect(mockCounterAddFn).toHaveBeenCalledTimes(2); expect(mockCounterAddFn).toHaveBeenNthCalledWith(1, 1, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', }); expect(mockCounterAddFn).toHaveBeenNthCalledWith(2, 1, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', operation: FileOperation.CREATE, lines: 10, mimetype: 'text/plain', @@ -306,6 +351,8 @@ describe('Telemetry Metrics', () => { }); expect(mockCounterAddFn).toHaveBeenCalledWith(1, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', operation: FileOperation.READ, }); }); @@ -320,6 +367,8 @@ describe('Telemetry Metrics', () => { }); expect(mockCounterAddFn).toHaveBeenCalledWith(1, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', operation: FileOperation.UPDATE, mimetype: 'application/javascript', }); @@ -335,6 +384,8 @@ describe('Telemetry Metrics', () => { expect(mockCounterAddFn).toHaveBeenCalledWith(1, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', operation: FileOperation.UPDATE, }); }); @@ -352,6 +403,8 @@ describe('Telemetry Metrics', () => { expect(mockCounterAddFn).toHaveBeenCalledWith(1, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', operation: FileOperation.UPDATE, lines: 10, mimetype: 'text/plain', @@ -369,6 +422,8 @@ describe('Telemetry Metrics', () => { expect(mockCounterAddFn).toHaveBeenCalledWith(1, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', operation: FileOperation.UPDATE, }); }); @@ -408,6 +463,8 @@ describe('Telemetry Metrics', () => { expect(mockHistogramRecordFn).toHaveBeenCalledWith(150, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', 'routing.decision_model': 'gemini-pro', 'routing.decision_source': 'default', }); @@ -429,6 +486,8 @@ describe('Telemetry Metrics', () => { expect(mockHistogramRecordFn).toHaveBeenCalledWith(200, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', 'routing.decision_model': 'gemini-pro', 'routing.decision_source': 'classifier', }); @@ -436,6 +495,8 @@ describe('Telemetry Metrics', () => { expect(mockCounterAddFn).toHaveBeenCalledTimes(2); expect(mockCounterAddFn).toHaveBeenNthCalledWith(2, 1, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', 'routing.decision_source': 'classifier', 'routing.error_message': 'test-error', }); @@ -478,6 +539,8 @@ describe('Telemetry Metrics', () => { // Verify agent run counter expect(mockCounterAddFn).toHaveBeenCalledWith(1, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', agent_name: 'TestAgent', terminate_reason: 'GOAL', }); @@ -485,12 +548,16 @@ describe('Telemetry Metrics', () => { // Verify agent duration histogram expect(mockHistogramRecordFn).toHaveBeenCalledWith(1000, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', agent_name: 'TestAgent', }); // Verify agent turns histogram expect(mockHistogramRecordFn).toHaveBeenCalledWith(5, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', agent_name: 'TestAgent', }); }); @@ -527,6 +594,8 @@ describe('Telemetry Metrics', () => { expect(mockHistogramRecordFn).toHaveBeenCalledWith(150, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', 'gen_ai.operation.name': 'generate_content', 'gen_ai.provider.name': 'gcp.gen_ai', 'gen_ai.token.type': 'input', @@ -548,6 +617,8 @@ describe('Telemetry Metrics', () => { expect(mockHistogramRecordFn).toHaveBeenCalledWith(75, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', 'gen_ai.operation.name': 'generate_content', 'gen_ai.provider.name': 'gcp.vertex_ai', 'gen_ai.token.type': 'output', @@ -570,6 +641,8 @@ describe('Telemetry Metrics', () => { expect(mockHistogramRecordFn).toHaveBeenCalledWith(200, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', 'gen_ai.operation.name': 'generate_content', 'gen_ai.provider.name': 'gcp.vertex_ai', 'gen_ai.token.type': 'input', @@ -603,6 +676,8 @@ describe('Telemetry Metrics', () => { expect(mockHistogramRecordFn).toHaveBeenCalledWith(1.25, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', 'gen_ai.operation.name': 'generate_content', 'gen_ai.provider.name': 'gcp.gen_ai', 'gen_ai.request.model': 'gemini-2.0-flash', @@ -623,6 +698,8 @@ describe('Telemetry Metrics', () => { expect(mockHistogramRecordFn).toHaveBeenCalledWith(3.75, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', 'gen_ai.operation.name': 'generate_content', 'gen_ai.provider.name': 'gcp.vertex_ai', 'gen_ai.request.model': 'gemini-pro', @@ -645,6 +722,8 @@ describe('Telemetry Metrics', () => { expect(mockHistogramRecordFn).toHaveBeenCalledWith(0.95, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', 'gen_ai.operation.name': 'generate_content', 'gen_ai.provider.name': 'gcp.vertex_ai', 'gen_ai.request.model': 'gemini-1.5-pro', @@ -665,6 +744,8 @@ describe('Telemetry Metrics', () => { expect(mockHistogramRecordFn).toHaveBeenCalledWith(2.1, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', 'gen_ai.operation.name': 'generate_content', 'gen_ai.provider.name': 'gcp.gen_ai', }); @@ -714,6 +795,8 @@ describe('Telemetry Metrics', () => { expect(mockHistogramRecordFn).toHaveBeenCalledWith(150, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', phase: 'settings_loading', auth_type: 'gemini', telemetry_enabled: true, @@ -729,6 +812,8 @@ describe('Telemetry Metrics', () => { expect(mockHistogramRecordFn).toHaveBeenCalledWith(50, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', phase: 'cleanup', }); }); @@ -751,6 +836,8 @@ describe('Telemetry Metrics', () => { floatingPointDuration, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', phase: 'total_startup', is_tty: true, has_question: false, @@ -771,6 +858,8 @@ describe('Telemetry Metrics', () => { expect(mockHistogramRecordFn).toHaveBeenCalledWith(15728640, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', memory_type: 'heap_used', component: 'startup', }); @@ -796,16 +885,22 @@ describe('Telemetry Metrics', () => { expect(mockHistogramRecordFn).toHaveBeenCalledTimes(3); // One for each call expect(mockHistogramRecordFn).toHaveBeenNthCalledWith(1, 31457280, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', memory_type: 'heap_total', component: 'api_call', }); expect(mockHistogramRecordFn).toHaveBeenNthCalledWith(2, 2097152, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', memory_type: 'external', component: 'tool_execution', }); expect(mockHistogramRecordFn).toHaveBeenNthCalledWith(3, 41943040, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', memory_type: 'rss', component: 'memory_monitor', }); @@ -821,6 +916,8 @@ describe('Telemetry Metrics', () => { expect(mockHistogramRecordFn).toHaveBeenCalledWith(15728640, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', memory_type: 'heap_used', }); }); @@ -837,6 +934,8 @@ describe('Telemetry Metrics', () => { expect(mockHistogramRecordFn).toHaveBeenCalledWith(85.5, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', component: 'tool_execution', }); }); @@ -849,6 +948,8 @@ describe('Telemetry Metrics', () => { expect(mockHistogramRecordFn).toHaveBeenCalledWith(42.3, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', }); }); }); @@ -862,6 +963,8 @@ describe('Telemetry Metrics', () => { expect(mockHistogramRecordFn).toHaveBeenCalledWith(3, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', }); }); @@ -873,6 +976,8 @@ describe('Telemetry Metrics', () => { expect(mockHistogramRecordFn).toHaveBeenCalledWith(0, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', }); }); }); @@ -889,6 +994,8 @@ describe('Telemetry Metrics', () => { expect(mockHistogramRecordFn).toHaveBeenCalledWith(25, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', function_name: 'Read', phase: 'validation', }); @@ -914,16 +1021,22 @@ describe('Telemetry Metrics', () => { expect(mockHistogramRecordFn).toHaveBeenCalledTimes(3); // One for each call expect(mockHistogramRecordFn).toHaveBeenNthCalledWith(1, 50, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', function_name: 'Bash', phase: 'preparation', }); expect(mockHistogramRecordFn).toHaveBeenNthCalledWith(2, 1500, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', function_name: 'Bash', phase: 'execution', }); expect(mockHistogramRecordFn).toHaveBeenNthCalledWith(3, 75, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', function_name: 'Bash', phase: 'result_processing', }); @@ -943,6 +1056,8 @@ describe('Telemetry Metrics', () => { expect(mockHistogramRecordFn).toHaveBeenCalledWith(0.85, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', model: 'gemini-pro', metric: 'cache_hit_rate', context: 'api_request', @@ -960,6 +1075,8 @@ describe('Telemetry Metrics', () => { expect(mockHistogramRecordFn).toHaveBeenCalledWith(125.5, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', model: 'gemini-pro', metric: 'tokens_per_operation', }); @@ -978,6 +1095,8 @@ describe('Telemetry Metrics', () => { expect(mockHistogramRecordFn).toHaveBeenCalledWith(15, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', model: 'gemini-pro', phase: 'request_preparation', }); @@ -1003,16 +1122,22 @@ describe('Telemetry Metrics', () => { expect(mockHistogramRecordFn).toHaveBeenCalledTimes(3); // One for each call expect(mockHistogramRecordFn).toHaveBeenNthCalledWith(1, 250, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', model: 'gemini-pro', phase: 'network_latency', }); expect(mockHistogramRecordFn).toHaveBeenNthCalledWith(2, 100, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', model: 'gemini-pro', phase: 'response_processing', }); expect(mockHistogramRecordFn).toHaveBeenNthCalledWith(3, 50, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', model: 'gemini-pro', phase: 'token_processing', }); @@ -1031,6 +1156,8 @@ describe('Telemetry Metrics', () => { expect(mockHistogramRecordFn).toHaveBeenCalledWith(85.5, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', category: 'memory_efficiency', baseline: 80.0, }); @@ -1046,6 +1173,8 @@ describe('Telemetry Metrics', () => { expect(mockHistogramRecordFn).toHaveBeenCalledWith(92.3, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', category: 'overall_performance', }); }); @@ -1067,6 +1196,8 @@ describe('Telemetry Metrics', () => { // Verify regression counter expect(mockCounterAddFn).toHaveBeenCalledWith(1, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', metric: 'startup_time', severity: 'medium', current_value: 1200, @@ -1076,6 +1207,8 @@ describe('Telemetry Metrics', () => { // Verify baseline comparison histogram (20% increase) expect(mockHistogramRecordFn).toHaveBeenCalledWith(20, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', metric: 'startup_time', severity: 'medium', current_value: 1200, @@ -1098,6 +1231,8 @@ describe('Telemetry Metrics', () => { // Verify regression counter still recorded expect(mockCounterAddFn).toHaveBeenCalledWith(1, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', metric: 'memory_usage', severity: 'high', current_value: 100, @@ -1127,6 +1262,8 @@ describe('Telemetry Metrics', () => { expect(mockCounterAddFn).toHaveBeenNthCalledWith(1, 1, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', metric: 'api_latency', severity: 'low', current_value: 500, @@ -1134,6 +1271,8 @@ describe('Telemetry Metrics', () => { }); expect(mockCounterAddFn).toHaveBeenNthCalledWith(2, 1, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', metric: 'cpu_usage', severity: 'high', current_value: 90, @@ -1157,6 +1296,8 @@ describe('Telemetry Metrics', () => { // 20% increase: (120 - 100) / 100 * 100 = 20% expect(mockHistogramRecordFn).toHaveBeenCalledWith(20, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', metric: 'memory_usage', category: 'performance_tracking', current_value: 120, @@ -1178,6 +1319,8 @@ describe('Telemetry Metrics', () => { // 20% decrease: (800 - 1000) / 1000 * 100 = -20% expect(mockHistogramRecordFn).toHaveBeenCalledWith(-20, { 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', metric: 'startup_time', category: 'optimization', current_value: 800, diff --git a/packages/core/src/telemetry/metrics.ts b/packages/core/src/telemetry/metrics.ts index f6de7e47bc..95b0c592d9 100644 --- a/packages/core/src/telemetry/metrics.ts +++ b/packages/core/src/telemetry/metrics.ts @@ -15,6 +15,7 @@ import type { AgentFinishEvent, } from './types.js'; import { AuthType } from '../core/contentGenerator.js'; +import { getCommonAttributes } from './telemetryAttributes.js'; const TOOL_CALL_COUNT = 'gemini_cli.tool.call.count'; const TOOL_CALL_LATENCY = 'gemini_cli.tool.call.latency'; @@ -56,9 +57,7 @@ const REGRESSION_PERCENTAGE_CHANGE = const BASELINE_COMPARISON = 'gemini_cli.performance.baseline.comparison'; const baseMetricDefinition = { - getCommonAttributes: (config: Config): Attributes => ({ - 'session.id': config.getSessionId(), - }), + getCommonAttributes, }; const COUNTER_DEFINITIONS = { diff --git a/packages/core/src/telemetry/telemetryAttributes.ts b/packages/core/src/telemetry/telemetryAttributes.ts index fc609bdb45..6c205161f0 100644 --- a/packages/core/src/telemetry/telemetryAttributes.ts +++ b/packages/core/src/telemetry/telemetryAttributes.ts @@ -4,15 +4,19 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type { LogAttributes } from '@opentelemetry/api-logs'; +import type { Attributes } from '@opentelemetry/api'; import type { Config } from '../config/config.js'; +import { InstallationManager } from '../utils/installationManager.js'; import { UserAccountManager } from '../utils/userAccountManager.js'; -export function getCommonAttributes(config: Config): LogAttributes { - const userAccountManager = new UserAccountManager(); +const userAccountManager = new UserAccountManager(); +const installationManager = new InstallationManager(); + +export function getCommonAttributes(config: Config): Attributes { const email = userAccountManager.getCachedGoogleAccount(); return { 'session.id': config.getSessionId(), + 'installation.id': installationManager.getInstallationId(), ...(email && { 'user.email': email }), }; }