From df1a6996e091b03798859cd9b9edbd44fdad2647 Mon Sep 17 00:00:00 2001 From: Srinath Padmanabhan Date: Wed, 11 Mar 2026 12:21:45 -0700 Subject: [PATCH] feat(telemetry): migrate Google Auth events to event-based logging and fix token storage metric - Add GoogleAuthStartEvent and GoogleAuthEndEvent types - Implement logGoogleAuthStart and logGoogleAuthEnd in telemetry loggers - Update ClearcutLogger to support Google Auth events - Update OAuth2 logic to use the new logging functions - Fix missing 'type' attribute in recordTokenStorageInitialization metric --- packages/core/src/code_assist/oauth2.test.ts | 21 +++++---- packages/core/src/code_assist/oauth2.ts | 19 ++++---- .../clearcut-logger/clearcut-logger.ts | 14 ++++++ packages/core/src/telemetry/loggers.ts | 38 +++++++++++++++ packages/core/src/telemetry/metrics.ts | 1 + packages/core/src/telemetry/types.ts | 46 +++++++++++++++++++ 6 files changed, 121 insertions(+), 18 deletions(-) diff --git a/packages/core/src/code_assist/oauth2.test.ts b/packages/core/src/code_assist/oauth2.test.ts index 134244c294..ac528e6797 100644 --- a/packages/core/src/code_assist/oauth2.test.ts +++ b/packages/core/src/code_assist/oauth2.test.ts @@ -26,10 +26,7 @@ import { clearOauthClientCache, authEvents, } from './oauth2.js'; -import { - recordGoogleAuthStart, - recordGoogleAuthEnd, -} from '../telemetry/metrics.js'; +import { logGoogleAuthStart, logGoogleAuthEnd } from '../telemetry/loggers.js'; import { UserAccountManager } from '../utils/userAccountManager.js'; import * as fs from 'node:fs'; import * as path from 'node:path'; @@ -109,9 +106,9 @@ vi.mock('../mcp/token-storage/hybrid-token-storage.js', () => ({ })), })); -vi.mock('../telemetry/metrics.js', () => ({ - recordGoogleAuthStart: vi.fn(), - recordGoogleAuthEnd: vi.fn(), +vi.mock('../telemetry/loggers.js', () => ({ + logGoogleAuthStart: vi.fn(), + logGoogleAuthEnd: vi.fn(), })); const mockConfig = { @@ -1415,8 +1412,14 @@ describe('oauth2', () => { await getOauthClient(AuthType.LOGIN_WITH_GOOGLE, mockConfig); - expect(recordGoogleAuthStart).toHaveBeenCalledWith(mockConfig); - expect(recordGoogleAuthEnd).toHaveBeenCalledWith(mockConfig); + expect(logGoogleAuthStart).toHaveBeenCalledWith( + mockConfig, + expect.any(Object), + ); + expect(logGoogleAuthEnd).toHaveBeenCalledWith( + mockConfig, + expect.any(Object), + ); }); it('should NOT record onboarding events for other auth types', async () => { diff --git a/packages/core/src/code_assist/oauth2.ts b/packages/core/src/code_assist/oauth2.ts index 0540ff4565..d5abee0673 100644 --- a/packages/core/src/code_assist/oauth2.ts +++ b/packages/core/src/code_assist/oauth2.ts @@ -21,10 +21,11 @@ import { EventEmitter } from 'node:events'; import open from 'open'; import path from 'node:path'; import { promises as fs } from 'node:fs'; +import { logGoogleAuthStart, logGoogleAuthEnd } from '../telemetry/loggers.js'; import { - recordGoogleAuthStart, - recordGoogleAuthEnd, -} from '../telemetry/metrics.js'; + GoogleAuthStartEvent, + GoogleAuthEndEvent, +} from '../telemetry/types.js'; import type { Config } from '../config/config.js'; import { getErrorMessage, @@ -117,9 +118,9 @@ async function initOauthClient( authType: AuthType, config: Config, ): Promise { - const recordGoogleAuthEndIfApplicable = () => { + const logGoogleAuthEndIfApplicable = () => { if (authType === AuthType.LOGIN_WITH_GOOGLE) { - recordGoogleAuthEnd(config); + logGoogleAuthEnd(config, new GoogleAuthEndEvent()); } }; @@ -154,7 +155,7 @@ async function initOauthClient( }); if (authType === AuthType.LOGIN_WITH_GOOGLE) { - recordGoogleAuthStart(config); + logGoogleAuthStart(config, new GoogleAuthStartEvent()); } const useEncryptedStorage = getUseEncryptedStorageFlag(); @@ -203,7 +204,7 @@ async function initOauthClient( debugLogger.log('Loaded cached credentials.'); await triggerPostAuthCallbacks(credentials as Credentials); - recordGoogleAuthEndIfApplicable(); + logGoogleAuthEndIfApplicable(); return client; } } catch (error) { @@ -295,7 +296,7 @@ async function initOauthClient( } await triggerPostAuthCallbacks(client.credentials); - recordGoogleAuthEndIfApplicable(); + logGoogleAuthEndIfApplicable(); } else { // In ACP mode, we skip the interactive consent and directly open the browser if (!config.getAcpMode()) { @@ -402,7 +403,7 @@ async function initOauthClient( }); await triggerPostAuthCallbacks(client.credentials); - recordGoogleAuthEndIfApplicable(); + logGoogleAuthEndIfApplicable(); } return client; diff --git a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts index 51c5ab382f..90376be96d 100644 --- a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts +++ b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts @@ -50,6 +50,8 @@ import type { KeychainAvailabilityEvent, TokenStorageInitializationEvent, StartupStatsEvent, + GoogleAuthStartEvent, + GoogleAuthEndEvent, } from '../types.js'; import { EventMetadataKey } from './event-metadata-key.js'; import type { Config } from '../../config/config.js'; @@ -116,6 +118,8 @@ export enum EventNames { TOOL_OUTPUT_MASKING = 'tool_output_masking', KEYCHAIN_AVAILABILITY = 'keychain_availability', TOKEN_STORAGE_INITIALIZATION = 'token_storage_initialization', + GOOGLE_AUTH_START = 'google_auth_start', + GOOGLE_AUTH_END = 'google_auth_end', CONSECA_POLICY_GENERATION = 'conseca_policy_generation', CONSECA_VERDICT = 'conseca_verdict', STARTUP_STATS = 'startup_stats', @@ -1693,6 +1697,16 @@ export class ClearcutLogger { this.flushIfNeeded(); } + logGoogleAuthStartEvent(_event: GoogleAuthStartEvent): void { + this.enqueueLogEvent(this.createLogEvent(EventNames.GOOGLE_AUTH_START, [])); + this.flushIfNeeded(); + } + + logGoogleAuthEndEvent(_event: GoogleAuthEndEvent): void { + this.enqueueLogEvent(this.createLogEvent(EventNames.GOOGLE_AUTH_END, [])); + this.flushIfNeeded(); + } + logStartupStatsEvent(event: StartupStatsEvent): void { const data: EventValue[] = [ { diff --git a/packages/core/src/telemetry/loggers.ts b/packages/core/src/telemetry/loggers.ts index 393519d3ec..83a5800e78 100644 --- a/packages/core/src/telemetry/loggers.ts +++ b/packages/core/src/telemetry/loggers.ts @@ -56,6 +56,8 @@ import { type ToolOutputMaskingEvent, type KeychainAvailabilityEvent, type TokenStorageInitializationEvent, + type GoogleAuthStartEvent, + type GoogleAuthEndEvent, } from './types.js'; import { recordApiErrorMetrics, @@ -77,6 +79,8 @@ import { recordKeychainAvailability, recordTokenStorageInitialization, recordInvalidChunk, + recordGoogleAuthStart, + recordGoogleAuthEnd, } from './metrics.js'; import { bufferTelemetryEvent } from './sdk.js'; import { uiTelemetryService, type UiEvent } from './uiTelemetry.js'; @@ -844,6 +848,40 @@ export function logTokenStorageInitialization( }); } +export function logGoogleAuthStart( + config: Config, + event: GoogleAuthStartEvent, +): void { + ClearcutLogger.getInstance(config)?.logGoogleAuthStartEvent(event); + bufferTelemetryEvent(() => { + const logger = logs.getLogger(SERVICE_NAME); + const logRecord: LogRecord = { + body: event.toLogBody(), + attributes: event.toOpenTelemetryAttributes(config), + }; + logger.emit(logRecord); + + recordGoogleAuthStart(config); + }); +} + +export function logGoogleAuthEnd( + config: Config, + event: GoogleAuthEndEvent, +): void { + ClearcutLogger.getInstance(config)?.logGoogleAuthEndEvent(event); + bufferTelemetryEvent(() => { + const logger = logs.getLogger(SERVICE_NAME); + const logRecord: LogRecord = { + body: event.toLogBody(), + attributes: event.toOpenTelemetryAttributes(config), + }; + logger.emit(logRecord); + + recordGoogleAuthEnd(config); + }); +} + export function logBillingEvent( config: Config, event: BillingTelemetryEvent, diff --git a/packages/core/src/telemetry/metrics.ts b/packages/core/src/telemetry/metrics.ts index ba5f8c0236..935bb0ea46 100644 --- a/packages/core/src/telemetry/metrics.ts +++ b/packages/core/src/telemetry/metrics.ts @@ -1397,6 +1397,7 @@ export function recordTokenStorageInitialization( tokenStorageTypeCounter.add(1, { ...baseMetricDefinition.getCommonAttributes(config), forced: event.forced, + type: event.type, }); } diff --git a/packages/core/src/telemetry/types.ts b/packages/core/src/telemetry/types.ts index 43317f8baa..09f624d91f 100644 --- a/packages/core/src/telemetry/types.ts +++ b/packages/core/src/telemetry/types.ts @@ -2307,6 +2307,52 @@ export class KeychainAvailabilityEvent implements BaseTelemetryEvent { } } +export const EVENT_GOOGLE_AUTH_START = 'gemini_cli.google_auth.start'; +export class GoogleAuthStartEvent implements BaseTelemetryEvent { + 'event.name': 'google_auth_start'; + 'event.timestamp': string; + + constructor() { + this['event.name'] = 'google_auth_start'; + this['event.timestamp'] = new Date().toISOString(); + } + + toOpenTelemetryAttributes(config: Config): LogAttributes { + return { + ...getCommonAttributes(config), + 'event.name': EVENT_GOOGLE_AUTH_START, + 'event.timestamp': this['event.timestamp'], + }; + } + + toLogBody(): string { + return 'Google auth started.'; + } +} + +export const EVENT_GOOGLE_AUTH_END = 'gemini_cli.google_auth.end'; +export class GoogleAuthEndEvent implements BaseTelemetryEvent { + 'event.name': 'google_auth_end'; + 'event.timestamp': string; + + constructor() { + this['event.name'] = 'google_auth_end'; + this['event.timestamp'] = new Date().toISOString(); + } + + toOpenTelemetryAttributes(config: Config): LogAttributes { + return { + ...getCommonAttributes(config), + 'event.name': EVENT_GOOGLE_AUTH_END, + 'event.timestamp': this['event.timestamp'], + }; + } + + toLogBody(): string { + return 'Google auth succeeded.'; + } +} + export const EVENT_TOKEN_STORAGE_INITIALIZATION = 'gemini_cli.token_storage.initialization'; export class TokenStorageInitializationEvent implements BaseTelemetryEvent {