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
This commit is contained in:
Srinath Padmanabhan
2026-03-11 12:21:45 -07:00
parent d4356e6732
commit 954dadc02a
6 changed files with 125 additions and 22 deletions
+12 -9
View File
@@ -26,10 +26,7 @@ import {
clearOauthClientCache, clearOauthClientCache,
authEvents, authEvents,
} from './oauth2.js'; } from './oauth2.js';
import { import { logGoogleAuthStart, logGoogleAuthEnd } from '../telemetry/loggers.js';
recordGoogleAuthStart,
recordGoogleAuthEnd,
} from '../telemetry/metrics.js';
import { UserAccountManager } from '../utils/userAccountManager.js'; import { UserAccountManager } from '../utils/userAccountManager.js';
import * as fs from 'node:fs'; import * as fs from 'node:fs';
import * as path from 'node:path'; import * as path from 'node:path';
@@ -109,9 +106,9 @@ vi.mock('../mcp/token-storage/hybrid-token-storage.js', () => ({
})), })),
})); }));
vi.mock('../telemetry/metrics.js', () => ({ vi.mock('../telemetry/loggers.js', () => ({
recordGoogleAuthStart: vi.fn(), logGoogleAuthStart: vi.fn(),
recordGoogleAuthEnd: vi.fn(), logGoogleAuthEnd: vi.fn(),
})); }));
const mockConfig = { const mockConfig = {
@@ -1415,8 +1412,14 @@ describe('oauth2', () => {
await getOauthClient(AuthType.LOGIN_WITH_GOOGLE, mockConfig); await getOauthClient(AuthType.LOGIN_WITH_GOOGLE, mockConfig);
expect(recordGoogleAuthStart).toHaveBeenCalledWith(mockConfig); expect(logGoogleAuthStart).toHaveBeenCalledWith(
expect(recordGoogleAuthEnd).toHaveBeenCalledWith(mockConfig); mockConfig,
expect.any(Object),
);
expect(logGoogleAuthEnd).toHaveBeenCalledWith(
mockConfig,
expect.any(Object),
);
}); });
it('should NOT record google auth events for non-LOGIN_WITH_GOOGLE auth types', async () => { it('should NOT record google auth events for non-LOGIN_WITH_GOOGLE auth types', async () => {
+14 -13
View File
@@ -21,10 +21,11 @@ import { EventEmitter } from 'node:events';
import open from 'open'; import open from 'open';
import path from 'node:path'; import path from 'node:path';
import { promises as fs } from 'node:fs'; import { promises as fs } from 'node:fs';
import { logGoogleAuthStart, logGoogleAuthEnd } from '../telemetry/loggers.js';
import { import {
recordGoogleAuthStart, GoogleAuthStartEvent,
recordGoogleAuthEnd, GoogleAuthEndEvent,
} from '../telemetry/metrics.js'; } from '../telemetry/types.js';
import type { Config } from '../config/config.js'; import type { Config } from '../config/config.js';
import { import {
getErrorMessage, getErrorMessage,
@@ -117,6 +118,12 @@ async function initOauthClient(
authType: AuthType, authType: AuthType,
config: Config, config: Config,
): Promise<AuthClient> { ): Promise<AuthClient> {
const logGoogleAuthEndIfApplicable = () => {
if (authType === AuthType.LOGIN_WITH_GOOGLE) {
logGoogleAuthEnd(config, new GoogleAuthEndEvent());
}
};
const credentials = await fetchCachedCredentials(); const credentials = await fetchCachedCredentials();
if ( if (
@@ -148,7 +155,7 @@ async function initOauthClient(
}); });
if (authType === AuthType.LOGIN_WITH_GOOGLE) { if (authType === AuthType.LOGIN_WITH_GOOGLE) {
recordGoogleAuthStart(config); logGoogleAuthStart(config, new GoogleAuthStartEvent());
} }
const useEncryptedStorage = getUseEncryptedStorageFlag(); const useEncryptedStorage = getUseEncryptedStorageFlag();
@@ -200,9 +207,7 @@ async function initOauthClient(
debugLogger.log('Loaded cached credentials.'); debugLogger.log('Loaded cached credentials.');
await triggerPostAuthCallbacks(credentials as Credentials); await triggerPostAuthCallbacks(credentials as Credentials);
if (authType === AuthType.LOGIN_WITH_GOOGLE) { logGoogleAuthEndIfApplicable();
recordGoogleAuthEnd(config);
}
return client; return client;
} }
} catch (error) { } catch (error) {
@@ -294,9 +299,7 @@ async function initOauthClient(
} }
await triggerPostAuthCallbacks(client.credentials); await triggerPostAuthCallbacks(client.credentials);
if (authType === AuthType.LOGIN_WITH_GOOGLE) { logGoogleAuthEndIfApplicable();
recordGoogleAuthEnd(config);
}
} else { } else {
// In ACP mode, we skip the interactive consent and directly open the browser // In ACP mode, we skip the interactive consent and directly open the browser
if (!config.getAcpMode()) { if (!config.getAcpMode()) {
@@ -403,9 +406,7 @@ async function initOauthClient(
}); });
await triggerPostAuthCallbacks(client.credentials); await triggerPostAuthCallbacks(client.credentials);
if (authType === AuthType.LOGIN_WITH_GOOGLE) { logGoogleAuthEndIfApplicable();
recordGoogleAuthEnd(config);
}
} }
return client; return client;
@@ -50,6 +50,8 @@ import type {
KeychainAvailabilityEvent, KeychainAvailabilityEvent,
TokenStorageInitializationEvent, TokenStorageInitializationEvent,
StartupStatsEvent, StartupStatsEvent,
GoogleAuthStartEvent,
GoogleAuthEndEvent,
} 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';
@@ -116,6 +118,8 @@ export enum EventNames {
TOOL_OUTPUT_MASKING = 'tool_output_masking', TOOL_OUTPUT_MASKING = 'tool_output_masking',
KEYCHAIN_AVAILABILITY = 'keychain_availability', KEYCHAIN_AVAILABILITY = 'keychain_availability',
TOKEN_STORAGE_INITIALIZATION = 'token_storage_initialization', 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_POLICY_GENERATION = 'conseca_policy_generation',
CONSECA_VERDICT = 'conseca_verdict', CONSECA_VERDICT = 'conseca_verdict',
STARTUP_STATS = 'startup_stats', STARTUP_STATS = 'startup_stats',
@@ -1693,6 +1697,16 @@ export class ClearcutLogger {
this.flushIfNeeded(); 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 { logStartupStatsEvent(event: StartupStatsEvent): void {
const data: EventValue[] = [ const data: EventValue[] = [
{ {
+38
View File
@@ -56,6 +56,8 @@ import {
type ToolOutputMaskingEvent, type ToolOutputMaskingEvent,
type KeychainAvailabilityEvent, type KeychainAvailabilityEvent,
type TokenStorageInitializationEvent, type TokenStorageInitializationEvent,
type GoogleAuthStartEvent,
type GoogleAuthEndEvent,
} from './types.js'; } from './types.js';
import { import {
recordApiErrorMetrics, recordApiErrorMetrics,
@@ -77,6 +79,8 @@ import {
recordKeychainAvailability, recordKeychainAvailability,
recordTokenStorageInitialization, recordTokenStorageInitialization,
recordInvalidChunk, recordInvalidChunk,
recordGoogleAuthStart,
recordGoogleAuthEnd,
} from './metrics.js'; } from './metrics.js';
import { bufferTelemetryEvent } from './sdk.js'; import { bufferTelemetryEvent } from './sdk.js';
import { uiTelemetryService, type UiEvent } from './uiTelemetry.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( export function logBillingEvent(
config: Config, config: Config,
event: BillingTelemetryEvent, event: BillingTelemetryEvent,
+1
View File
@@ -1398,6 +1398,7 @@ export function recordTokenStorageInitialization(
...baseMetricDefinition.getCommonAttributes(config), ...baseMetricDefinition.getCommonAttributes(config),
type: event.type, type: event.type,
forced: event.forced, forced: event.forced,
type: event.type,
}); });
} }
+46
View File
@@ -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 = export const EVENT_TOKEN_STORAGE_INITIALIZATION =
'gemini_cli.token_storage.initialization'; 'gemini_cli.token_storage.initialization';
export class TokenStorageInitializationEvent implements BaseTelemetryEvent { export class TokenStorageInitializationEvent implements BaseTelemetryEvent {