mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 21:03:05 -07:00
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:
@@ -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 onboarding events for other auth types', async () => {
|
it('should NOT record onboarding events for other auth types', async () => {
|
||||||
|
|||||||
@@ -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,9 +118,9 @@ async function initOauthClient(
|
|||||||
authType: AuthType,
|
authType: AuthType,
|
||||||
config: Config,
|
config: Config,
|
||||||
): Promise<AuthClient> {
|
): Promise<AuthClient> {
|
||||||
const recordGoogleAuthEndIfApplicable = () => {
|
const logGoogleAuthEndIfApplicable = () => {
|
||||||
if (authType === AuthType.LOGIN_WITH_GOOGLE) {
|
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) {
|
if (authType === AuthType.LOGIN_WITH_GOOGLE) {
|
||||||
recordGoogleAuthStart(config);
|
logGoogleAuthStart(config, new GoogleAuthStartEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
const useEncryptedStorage = getUseEncryptedStorageFlag();
|
const useEncryptedStorage = getUseEncryptedStorageFlag();
|
||||||
@@ -203,7 +204,7 @@ async function initOauthClient(
|
|||||||
debugLogger.log('Loaded cached credentials.');
|
debugLogger.log('Loaded cached credentials.');
|
||||||
await triggerPostAuthCallbacks(credentials as Credentials);
|
await triggerPostAuthCallbacks(credentials as Credentials);
|
||||||
|
|
||||||
recordGoogleAuthEndIfApplicable();
|
logGoogleAuthEndIfApplicable();
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -295,7 +296,7 @@ async function initOauthClient(
|
|||||||
}
|
}
|
||||||
|
|
||||||
await triggerPostAuthCallbacks(client.credentials);
|
await triggerPostAuthCallbacks(client.credentials);
|
||||||
recordGoogleAuthEndIfApplicable();
|
logGoogleAuthEndIfApplicable();
|
||||||
} 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()) {
|
||||||
@@ -402,7 +403,7 @@ async function initOauthClient(
|
|||||||
});
|
});
|
||||||
|
|
||||||
await triggerPostAuthCallbacks(client.credentials);
|
await triggerPostAuthCallbacks(client.credentials);
|
||||||
recordGoogleAuthEndIfApplicable();
|
logGoogleAuthEndIfApplicable();
|
||||||
}
|
}
|
||||||
|
|
||||||
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[] = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -1397,6 +1397,7 @@ export function recordTokenStorageInitialization(
|
|||||||
tokenStorageTypeCounter.add(1, {
|
tokenStorageTypeCounter.add(1, {
|
||||||
...baseMetricDefinition.getCommonAttributes(config),
|
...baseMetricDefinition.getCommonAttributes(config),
|
||||||
forced: event.forced,
|
forced: event.forced,
|
||||||
|
type: event.type,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user