mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-24 21:10:43 -07:00
feat(telemetry): add keychain availability and token storage metrics (#18971)
This commit is contained in:
@@ -79,6 +79,14 @@ vi.mock('./oauth-credential-storage.js', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../mcp/token-storage/hybrid-token-storage.js', () => ({
|
||||
HybridTokenStorage: vi.fn(() => ({
|
||||
getCredentials: vi.fn(),
|
||||
setCredentials: vi.fn(),
|
||||
deleteCredentials: vi.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
const mockConfig = {
|
||||
getNoBrowser: () => false,
|
||||
getProxy: () => 'http://test.proxy.com:8080',
|
||||
|
||||
@@ -22,6 +22,20 @@ vi.mock('./keychain-token-storage.js', () => ({
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock('../../code_assist/oauth-credential-storage.js', () => ({
|
||||
OAuthCredentialStorage: {
|
||||
saveCredentials: vi.fn(),
|
||||
loadCredentials: vi.fn(),
|
||||
clearCredentials: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../core/apiKeyCredentialStorage.js', () => ({
|
||||
loadApiKey: vi.fn(),
|
||||
saveApiKey: vi.fn(),
|
||||
clearApiKey: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('./file-token-storage.js', () => ({
|
||||
FileTokenStorage: vi.fn().mockImplementation(() => ({
|
||||
getCredentials: vi.fn(),
|
||||
|
||||
@@ -8,6 +8,8 @@ import { BaseTokenStorage } from './base-token-storage.js';
|
||||
import { FileTokenStorage } from './file-token-storage.js';
|
||||
import type { TokenStorage, OAuthCredentials } from './types.js';
|
||||
import { TokenStorageType } from './types.js';
|
||||
import { coreEvents } from '../../utils/events.js';
|
||||
import { TokenStorageInitializationEvent } from '../../telemetry/types.js';
|
||||
|
||||
const FORCE_FILE_STORAGE_ENV_VAR = 'GEMINI_FORCE_FILE_STORAGE';
|
||||
|
||||
@@ -34,6 +36,11 @@ export class HybridTokenStorage extends BaseTokenStorage {
|
||||
if (isAvailable) {
|
||||
this.storage = keychainStorage;
|
||||
this.storageType = TokenStorageType.KEYCHAIN;
|
||||
|
||||
coreEvents.emitTelemetryTokenStorageType(
|
||||
new TokenStorageInitializationEvent('keychain', forceFileStorage),
|
||||
);
|
||||
|
||||
return this.storage;
|
||||
}
|
||||
} catch (_e) {
|
||||
@@ -43,6 +50,11 @@ export class HybridTokenStorage extends BaseTokenStorage {
|
||||
|
||||
this.storage = new FileTokenStorage(this.serviceName);
|
||||
this.storageType = TokenStorageType.ENCRYPTED_FILE;
|
||||
|
||||
coreEvents.emitTelemetryTokenStorageType(
|
||||
new TokenStorageInitializationEvent('encrypted_file', forceFileStorage),
|
||||
);
|
||||
|
||||
return this.storage;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,15 +25,20 @@ vi.mock('keytar', () => ({
|
||||
default: mockKeytar,
|
||||
}));
|
||||
|
||||
vi.mock('node:crypto', () => ({
|
||||
randomBytes: vi.fn(() => ({
|
||||
toString: vi.fn(() => mockCryptoRandomBytesString),
|
||||
})),
|
||||
}));
|
||||
vi.mock('node:crypto', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('node:crypto')>();
|
||||
return {
|
||||
...actual,
|
||||
randomBytes: vi.fn(() => ({
|
||||
toString: vi.fn(() => mockCryptoRandomBytesString),
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../../utils/events.js', () => ({
|
||||
coreEvents: {
|
||||
emitFeedback: vi.fn(),
|
||||
emitTelemetryKeychainAvailability: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import * as crypto from 'node:crypto';
|
||||
import { BaseTokenStorage } from './base-token-storage.js';
|
||||
import type { OAuthCredentials, SecretStorage } from './types.js';
|
||||
import { coreEvents } from '../../utils/events.js';
|
||||
import { KeychainAvailabilityEvent } from '../../telemetry/types.js';
|
||||
|
||||
interface Keytar {
|
||||
getPassword(service: string, account: string): Promise<string | null>;
|
||||
@@ -263,9 +264,21 @@ export class KeychainTokenStorage
|
||||
|
||||
const success = deleted && retrieved === testPassword;
|
||||
this.keychainAvailable = success;
|
||||
|
||||
coreEvents.emitTelemetryKeychainAvailability(
|
||||
new KeychainAvailabilityEvent(success),
|
||||
);
|
||||
|
||||
return success;
|
||||
} catch (_error) {
|
||||
this.keychainAvailable = false;
|
||||
|
||||
// Do not log the raw error message to avoid potential PII leaks
|
||||
// (e.g. from OS-level error messages containing file paths)
|
||||
coreEvents.emitTelemetryKeychainAvailability(
|
||||
new KeychainAvailabilityEvent(false),
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,8 @@ import type {
|
||||
ApprovalModeDurationEvent,
|
||||
PlanExecutionEvent,
|
||||
ToolOutputMaskingEvent,
|
||||
KeychainAvailabilityEvent,
|
||||
TokenStorageInitializationEvent,
|
||||
} from '../types.js';
|
||||
import { EventMetadataKey } from './event-metadata-key.js';
|
||||
import type { Config } from '../../config/config.js';
|
||||
@@ -111,6 +113,8 @@ export enum EventNames {
|
||||
APPROVAL_MODE_DURATION = 'approval_mode_duration',
|
||||
PLAN_EXECUTION = 'plan_execution',
|
||||
TOOL_OUTPUT_MASKING = 'tool_output_masking',
|
||||
KEYCHAIN_AVAILABILITY = 'keychain_availability',
|
||||
TOKEN_STORAGE_INITIALIZATION = 'token_storage_initialization',
|
||||
}
|
||||
|
||||
export interface LogResponse {
|
||||
@@ -1613,6 +1617,40 @@ export class ClearcutLogger {
|
||||
this.flushIfNeeded();
|
||||
}
|
||||
|
||||
logKeychainAvailabilityEvent(event: KeychainAvailabilityEvent): void {
|
||||
const data: EventValue[] = [
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_KEYCHAIN_AVAILABLE,
|
||||
value: JSON.stringify(event.available),
|
||||
},
|
||||
];
|
||||
|
||||
this.enqueueLogEvent(
|
||||
this.createLogEvent(EventNames.KEYCHAIN_AVAILABILITY, data),
|
||||
);
|
||||
this.flushIfNeeded();
|
||||
}
|
||||
|
||||
logTokenStorageInitializationEvent(
|
||||
event: TokenStorageInitializationEvent,
|
||||
): void {
|
||||
const data: EventValue[] = [
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOKEN_STORAGE_TYPE,
|
||||
value: event.type,
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOKEN_STORAGE_FORCED,
|
||||
value: JSON.stringify(event.forced),
|
||||
},
|
||||
];
|
||||
|
||||
this.enqueueLogEvent(
|
||||
this.createLogEvent(EventNames.TOKEN_STORAGE_INITIALIZATION, data),
|
||||
);
|
||||
this.flushIfNeeded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds default fields to data, and returns a new data array. This fields
|
||||
* should exist on all log events.
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
// Defines valid event metadata keys for Clearcut logging.
|
||||
export enum EventMetadataKey {
|
||||
// Deleted enums: 24
|
||||
// Next ID: 156
|
||||
// Next ID: 159
|
||||
|
||||
GEMINI_CLI_KEY_UNKNOWN = 0,
|
||||
|
||||
@@ -578,7 +578,6 @@ export enum EventMetadataKey {
|
||||
// Logs the total prunable tokens identified at the trigger point.
|
||||
GEMINI_CLI_TOOL_OUTPUT_MASKING_TOTAL_PRUNABLE_TOKENS = 151,
|
||||
|
||||
// ==========================================================================
|
||||
// Ask User Stats Event Keys
|
||||
// ==========================================================================
|
||||
|
||||
@@ -593,4 +592,17 @@ export enum EventMetadataKey {
|
||||
|
||||
// Logs the number of questions answered in the ask_user tool.
|
||||
GEMINI_CLI_ASK_USER_ANSWER_COUNT = 155,
|
||||
|
||||
// ==========================================================================
|
||||
// Keychain & Token Storage Event Keys
|
||||
// ==========================================================================
|
||||
|
||||
// Logs whether the keychain is available.
|
||||
GEMINI_CLI_KEYCHAIN_AVAILABLE = 156,
|
||||
|
||||
// Logs the type of token storage initialized.
|
||||
GEMINI_CLI_TOKEN_STORAGE_TYPE = 157,
|
||||
|
||||
// Logs whether the token storage type was forced by an environment variable.
|
||||
GEMINI_CLI_TOKEN_STORAGE_FORCED = 158,
|
||||
}
|
||||
|
||||
@@ -57,6 +57,8 @@ import type {
|
||||
LlmLoopCheckEvent,
|
||||
PlanExecutionEvent,
|
||||
ToolOutputMaskingEvent,
|
||||
KeychainAvailabilityEvent,
|
||||
TokenStorageInitializationEvent,
|
||||
} from './types.js';
|
||||
import {
|
||||
recordApiErrorMetrics,
|
||||
@@ -76,6 +78,8 @@ import {
|
||||
recordLinesChanged,
|
||||
recordHookCallMetrics,
|
||||
recordPlanExecution,
|
||||
recordKeychainAvailability,
|
||||
recordTokenStorageInitialization,
|
||||
} from './metrics.js';
|
||||
import { bufferTelemetryEvent } from './sdk.js';
|
||||
import type { UiEvent } from './uiTelemetry.js';
|
||||
@@ -805,3 +809,37 @@ export function logStartupStats(
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function logKeychainAvailability(
|
||||
config: Config,
|
||||
event: KeychainAvailabilityEvent,
|
||||
): void {
|
||||
ClearcutLogger.getInstance(config)?.logKeychainAvailabilityEvent(event);
|
||||
bufferTelemetryEvent(() => {
|
||||
const logger = logs.getLogger(SERVICE_NAME);
|
||||
const logRecord: LogRecord = {
|
||||
body: event.toLogBody(),
|
||||
attributes: event.toOpenTelemetryAttributes(config),
|
||||
};
|
||||
logger.emit(logRecord);
|
||||
|
||||
recordKeychainAvailability(config, event);
|
||||
});
|
||||
}
|
||||
|
||||
export function logTokenStorageInitialization(
|
||||
config: Config,
|
||||
event: TokenStorageInitializationEvent,
|
||||
): void {
|
||||
ClearcutLogger.getInstance(config)?.logTokenStorageInitializationEvent(event);
|
||||
bufferTelemetryEvent(() => {
|
||||
const logger = logs.getLogger(SERVICE_NAME);
|
||||
const logRecord: LogRecord = {
|
||||
body: event.toLogBody(),
|
||||
attributes: event.toOpenTelemetryAttributes(config),
|
||||
};
|
||||
logger.emit(logRecord);
|
||||
|
||||
recordTokenStorageInitialization(config, event);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,7 +20,12 @@ import {
|
||||
ApiRequestPhase,
|
||||
} from './metrics.js';
|
||||
import { makeFakeConfig } from '../test-utils/config.js';
|
||||
import { ModelRoutingEvent, AgentFinishEvent } from './types.js';
|
||||
import {
|
||||
ModelRoutingEvent,
|
||||
AgentFinishEvent,
|
||||
KeychainAvailabilityEvent,
|
||||
TokenStorageInitializationEvent,
|
||||
} from './types.js';
|
||||
import { AgentTerminateMode } from '../agents/types.js';
|
||||
|
||||
const mockCounterAddFn: Mock<
|
||||
@@ -97,6 +102,8 @@ describe('Telemetry Metrics', () => {
|
||||
let recordLinesChangedModule: typeof import('./metrics.js').recordLinesChanged;
|
||||
let recordSlowRenderModule: typeof import('./metrics.js').recordSlowRender;
|
||||
let recordPlanExecutionModule: typeof import('./metrics.js').recordPlanExecution;
|
||||
let recordKeychainAvailabilityModule: typeof import('./metrics.js').recordKeychainAvailability;
|
||||
let recordTokenStorageInitializationModule: typeof import('./metrics.js').recordTokenStorageInitialization;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
@@ -142,6 +149,10 @@ describe('Telemetry Metrics', () => {
|
||||
recordLinesChangedModule = metricsJsModule.recordLinesChanged;
|
||||
recordSlowRenderModule = metricsJsModule.recordSlowRender;
|
||||
recordPlanExecutionModule = metricsJsModule.recordPlanExecution;
|
||||
recordKeychainAvailabilityModule =
|
||||
metricsJsModule.recordKeychainAvailability;
|
||||
recordTokenStorageInitializationModule =
|
||||
metricsJsModule.recordTokenStorageInitialization;
|
||||
|
||||
const otelApiModule = await import('@opentelemetry/api');
|
||||
|
||||
@@ -1485,4 +1496,57 @@ describe('Telemetry Metrics', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Keychain and Token Storage Metrics', () => {
|
||||
describe('recordKeychainAvailability', () => {
|
||||
it('should not record metrics if not initialized', () => {
|
||||
const config = makeFakeConfig({});
|
||||
const event = new KeychainAvailabilityEvent(true);
|
||||
recordKeychainAvailabilityModule(config, event);
|
||||
expect(mockCounterAddFn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should record keychain availability when initialized', () => {
|
||||
const config = makeFakeConfig({});
|
||||
initializeMetricsModule(config);
|
||||
mockCounterAddFn.mockClear();
|
||||
|
||||
const event = new KeychainAvailabilityEvent(true);
|
||||
recordKeychainAvailabilityModule(config, event);
|
||||
|
||||
expect(mockCounterAddFn).toHaveBeenCalledWith(1, {
|
||||
'session.id': 'test-session-id',
|
||||
'installation.id': 'test-installation-id',
|
||||
'user.email': 'test@example.com',
|
||||
available: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('recordTokenStorageInitialization', () => {
|
||||
it('should not record metrics if not initialized', () => {
|
||||
const config = makeFakeConfig({});
|
||||
const event = new TokenStorageInitializationEvent('hybrid', false);
|
||||
recordTokenStorageInitializationModule(config, event);
|
||||
expect(mockCounterAddFn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should record token storage initialization when initialized', () => {
|
||||
const config = makeFakeConfig({});
|
||||
initializeMetricsModule(config);
|
||||
mockCounterAddFn.mockClear();
|
||||
|
||||
const event = new TokenStorageInitializationEvent('keychain', true);
|
||||
recordTokenStorageInitializationModule(config, event);
|
||||
|
||||
expect(mockCounterAddFn).toHaveBeenCalledWith(1, {
|
||||
'session.id': 'test-session-id',
|
||||
'installation.id': 'test-installation-id',
|
||||
'user.email': 'test@example.com',
|
||||
type: 'keychain',
|
||||
forced: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,6 +13,8 @@ import type {
|
||||
ModelSlashCommandEvent,
|
||||
AgentFinishEvent,
|
||||
RecoveryAttemptEvent,
|
||||
KeychainAvailabilityEvent,
|
||||
TokenStorageInitializationEvent,
|
||||
} from './types.js';
|
||||
import { AuthType } from '../core/contentGenerator.js';
|
||||
import { getCommonAttributes } from './telemetryAttributes.js';
|
||||
@@ -37,6 +39,8 @@ const MODEL_SLASH_COMMAND_CALL_COUNT =
|
||||
'gemini_cli.slash_command.model.call_count';
|
||||
const EVENT_HOOK_CALL_COUNT = 'gemini_cli.hook_call.count';
|
||||
const EVENT_HOOK_CALL_LATENCY = 'gemini_cli.hook_call.latency';
|
||||
const KEYCHAIN_AVAILABILITY_COUNT = 'gemini_cli.keychain.availability.count';
|
||||
const TOKEN_STORAGE_TYPE_COUNT = 'gemini_cli.token_storage.type.count';
|
||||
|
||||
// Agent Metrics
|
||||
const AGENT_RUN_COUNT = 'gemini_cli.agent.run.count';
|
||||
@@ -236,6 +240,25 @@ const COUNTER_DEFINITIONS = {
|
||||
success: boolean;
|
||||
},
|
||||
},
|
||||
[KEYCHAIN_AVAILABILITY_COUNT]: {
|
||||
description: 'Counts keychain availability checks.',
|
||||
valueType: ValueType.INT,
|
||||
assign: (c: Counter) => (keychainAvailabilityCounter = c),
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
attributes: {} as {
|
||||
available: boolean;
|
||||
},
|
||||
},
|
||||
[TOKEN_STORAGE_TYPE_COUNT]: {
|
||||
description: 'Counts token storage type initializations.',
|
||||
valueType: ValueType.INT,
|
||||
assign: (c: Counter) => (tokenStorageTypeCounter = c),
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
attributes: {} as {
|
||||
type: string;
|
||||
forced: boolean;
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
const HISTOGRAM_DEFINITIONS = {
|
||||
@@ -572,6 +595,8 @@ let planExecutionCounter: Counter | undefined;
|
||||
let slowRenderHistogram: Histogram | undefined;
|
||||
let hookCallCounter: Counter | undefined;
|
||||
let hookCallLatencyHistogram: Histogram | undefined;
|
||||
let keychainAvailabilityCounter: Counter | undefined;
|
||||
let tokenStorageTypeCounter: Counter | undefined;
|
||||
|
||||
// OpenTelemetry GenAI Semantic Convention Metrics
|
||||
let genAiClientTokenUsageHistogram: Histogram | undefined;
|
||||
@@ -1279,3 +1304,32 @@ export function recordHookCallMetrics(
|
||||
hookCallCounter.add(1, metricAttributes);
|
||||
hookCallLatencyHistogram.record(durationMs, metricAttributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Records a metric for keychain availability.
|
||||
*/
|
||||
export function recordKeychainAvailability(
|
||||
config: Config,
|
||||
event: KeychainAvailabilityEvent,
|
||||
): void {
|
||||
if (!keychainAvailabilityCounter || !isMetricsInitialized) return;
|
||||
keychainAvailabilityCounter.add(1, {
|
||||
...baseMetricDefinition.getCommonAttributes(config),
|
||||
available: event.available,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Records a metric for token storage type initialization.
|
||||
*/
|
||||
export function recordTokenStorageInitialization(
|
||||
config: Config,
|
||||
event: TokenStorageInitializationEvent,
|
||||
): void {
|
||||
if (!tokenStorageTypeCounter || !isMetricsInitialized) return;
|
||||
tokenStorageTypeCounter.add(1, {
|
||||
...baseMetricDefinition.getCommonAttributes(config),
|
||||
type: event.type,
|
||||
forced: event.forced,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -53,6 +53,15 @@ import {
|
||||
import { TelemetryTarget } from './index.js';
|
||||
import { debugLogger } from '../utils/debugLogger.js';
|
||||
import { authEvents } from '../code_assist/oauth2.js';
|
||||
import { coreEvents, CoreEvent } from '../utils/events.js';
|
||||
import {
|
||||
logKeychainAvailability,
|
||||
logTokenStorageInitialization,
|
||||
} from './loggers.js';
|
||||
import type {
|
||||
KeychainAvailabilityEvent,
|
||||
TokenStorageInitializationEvent,
|
||||
} from './types.js';
|
||||
|
||||
// For troubleshooting, set the log level to DiagLogLevel.DEBUG
|
||||
class DiagLoggerAdapter {
|
||||
@@ -86,6 +95,12 @@ let telemetryInitialized = false;
|
||||
let callbackRegistered = false;
|
||||
let authListener: ((newCredentials: JWTInput) => Promise<void>) | undefined =
|
||||
undefined;
|
||||
let keychainAvailabilityListener:
|
||||
| ((event: KeychainAvailabilityEvent) => void)
|
||||
| undefined = undefined;
|
||||
let tokenStorageTypeListener:
|
||||
| ((event: TokenStorageInitializationEvent) => void)
|
||||
| undefined = undefined;
|
||||
const telemetryBuffer: Array<() => void | Promise<void>> = [];
|
||||
let activeTelemetryEmail: string | undefined;
|
||||
|
||||
@@ -196,6 +211,26 @@ export async function initializeTelemetry(
|
||||
'session.id': config.getSessionId(),
|
||||
});
|
||||
|
||||
if (!keychainAvailabilityListener) {
|
||||
keychainAvailabilityListener = (event: KeychainAvailabilityEvent) => {
|
||||
logKeychainAvailability(config, event);
|
||||
};
|
||||
coreEvents.on(
|
||||
CoreEvent.TelemetryKeychainAvailability,
|
||||
keychainAvailabilityListener,
|
||||
);
|
||||
}
|
||||
|
||||
if (!tokenStorageTypeListener) {
|
||||
tokenStorageTypeListener = (event: TokenStorageInitializationEvent) => {
|
||||
logTokenStorageInitialization(config, event);
|
||||
};
|
||||
coreEvents.on(
|
||||
CoreEvent.TelemetryTokenStorageType,
|
||||
tokenStorageTypeListener,
|
||||
);
|
||||
}
|
||||
|
||||
const otlpEndpoint = config.getTelemetryOtlpEndpoint();
|
||||
const otlpProtocol = config.getTelemetryOtlpProtocol();
|
||||
const telemetryTarget = config.getTelemetryTarget();
|
||||
@@ -376,6 +411,20 @@ export async function shutdownTelemetry(
|
||||
authEvents.off('post_auth', authListener);
|
||||
authListener = undefined;
|
||||
}
|
||||
if (keychainAvailabilityListener) {
|
||||
coreEvents.off(
|
||||
CoreEvent.TelemetryKeychainAvailability,
|
||||
keychainAvailabilityListener,
|
||||
);
|
||||
keychainAvailabilityListener = undefined;
|
||||
}
|
||||
if (tokenStorageTypeListener) {
|
||||
coreEvents.off(
|
||||
CoreEvent.TelemetryTokenStorageType,
|
||||
tokenStorageTypeListener,
|
||||
);
|
||||
tokenStorageTypeListener = undefined;
|
||||
}
|
||||
callbackRegistered = false;
|
||||
activeTelemetryEmail = undefined;
|
||||
}
|
||||
|
||||
@@ -2111,3 +2111,60 @@ export class HookCallEvent implements BaseTelemetryEvent {
|
||||
return `Hook call ${hookId} ${status} in ${this.duration_ms}ms`;
|
||||
}
|
||||
}
|
||||
|
||||
export const EVENT_KEYCHAIN_AVAILABILITY = 'gemini_cli.keychain.availability';
|
||||
export class KeychainAvailabilityEvent implements BaseTelemetryEvent {
|
||||
'event.name': 'keychain_availability';
|
||||
'event.timestamp': string;
|
||||
available: boolean;
|
||||
|
||||
constructor(available: boolean) {
|
||||
this['event.name'] = 'keychain_availability';
|
||||
this['event.timestamp'] = new Date().toISOString();
|
||||
this.available = available;
|
||||
}
|
||||
|
||||
toOpenTelemetryAttributes(config: Config): LogAttributes {
|
||||
const attributes: LogAttributes = {
|
||||
...getCommonAttributes(config),
|
||||
'event.name': EVENT_KEYCHAIN_AVAILABILITY,
|
||||
'event.timestamp': this['event.timestamp'],
|
||||
available: this.available,
|
||||
};
|
||||
return attributes;
|
||||
}
|
||||
|
||||
toLogBody(): string {
|
||||
return `Keychain availability: ${this.available}`;
|
||||
}
|
||||
}
|
||||
|
||||
export const EVENT_TOKEN_STORAGE_INITIALIZATION =
|
||||
'gemini_cli.token_storage.initialization';
|
||||
export class TokenStorageInitializationEvent implements BaseTelemetryEvent {
|
||||
'event.name': 'token_storage_initialization';
|
||||
'event.timestamp': string;
|
||||
type: string;
|
||||
forced: boolean;
|
||||
|
||||
constructor(type: string, forced: boolean) {
|
||||
this['event.name'] = 'token_storage_initialization';
|
||||
this['event.timestamp'] = new Date().toISOString();
|
||||
this.type = type;
|
||||
this.forced = forced;
|
||||
}
|
||||
|
||||
toOpenTelemetryAttributes(config: Config): LogAttributes {
|
||||
return {
|
||||
...getCommonAttributes(config),
|
||||
'event.name': EVENT_TOKEN_STORAGE_INITIALIZATION,
|
||||
'event.timestamp': this['event.timestamp'],
|
||||
type: this.type,
|
||||
forced: this.forced,
|
||||
};
|
||||
}
|
||||
|
||||
toLogBody(): string {
|
||||
return `Token storage initialized. Type: ${this.type}. Forced: ${this.forced}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@ import type { AgentDefinition } from '../agents/types.js';
|
||||
import type { McpClient } from '../tools/mcp-client.js';
|
||||
import type { ExtensionEvents } from './extensionLoader.js';
|
||||
import type { EditorType } from './editor.js';
|
||||
import type {
|
||||
TokenStorageInitializationEvent,
|
||||
KeychainAvailabilityEvent,
|
||||
} from '../telemetry/types.js';
|
||||
|
||||
/**
|
||||
* Defines the severity level for user-facing feedback.
|
||||
@@ -168,6 +172,8 @@ export enum CoreEvent {
|
||||
EditorSelected = 'editor-selected',
|
||||
SlashCommandConflicts = 'slash-command-conflicts',
|
||||
QuotaChanged = 'quota-changed',
|
||||
TelemetryKeychainAvailability = 'telemetry-keychain-availability',
|
||||
TelemetryTokenStorageType = 'telemetry-token-storage-type',
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,6 +204,8 @@ export interface CoreEvents extends ExtensionEvents {
|
||||
[CoreEvent.RequestEditorSelection]: never[];
|
||||
[CoreEvent.EditorSelected]: [EditorSelectedPayload];
|
||||
[CoreEvent.SlashCommandConflicts]: [SlashCommandConflictsPayload];
|
||||
[CoreEvent.TelemetryKeychainAvailability]: [KeychainAvailabilityEvent];
|
||||
[CoreEvent.TelemetryTokenStorageType]: [TokenStorageInitializationEvent];
|
||||
}
|
||||
|
||||
type EventBacklogItem = {
|
||||
@@ -367,6 +375,14 @@ export class CoreEventEmitter extends EventEmitter<CoreEvents> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
emitTelemetryKeychainAvailability(event: KeychainAvailabilityEvent): void {
|
||||
this._emitOrQueue(CoreEvent.TelemetryKeychainAvailability, event);
|
||||
}
|
||||
|
||||
emitTelemetryTokenStorageType(event: TokenStorageInitializationEvent): void {
|
||||
this._emitOrQueue(CoreEvent.TelemetryTokenStorageType, event);
|
||||
}
|
||||
}
|
||||
|
||||
export const coreEvents = new CoreEventEmitter();
|
||||
|
||||
Reference in New Issue
Block a user