mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 12:54:07 -07:00
feat(core): set up onboarding telemetry (#23118)
Co-authored-by: Yuna Seol <yunaseol@google.com>
This commit is contained in:
@@ -904,6 +904,20 @@ Logs keychain availability checks.
|
|||||||
|
|
||||||
- `available` (boolean)
|
- `available` (boolean)
|
||||||
|
|
||||||
|
##### `gemini_cli.startup_stats`
|
||||||
|
|
||||||
|
Logs detailed startup performance statistics.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Attributes</summary>
|
||||||
|
|
||||||
|
- `phases` (json array of startup phases)
|
||||||
|
- `os_platform` (string)
|
||||||
|
- `os_release` (string)
|
||||||
|
- `is_docker` (boolean)
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### Metrics
|
### Metrics
|
||||||
@@ -920,6 +934,20 @@ Gemini CLI exports several custom metrics.
|
|||||||
|
|
||||||
Incremented once per CLI startup.
|
Incremented once per CLI startup.
|
||||||
|
|
||||||
|
##### Onboarding
|
||||||
|
|
||||||
|
Tracks onboarding flow from authentication to the user
|
||||||
|
|
||||||
|
- `gemini_cli.onboarding.start` (Counter, Int): Incremented when the
|
||||||
|
authentication flow begins.
|
||||||
|
|
||||||
|
- `gemini_cli.onboarding.success` (Counter, Int): Incremented when the user
|
||||||
|
onboarding flow completes successfully.
|
||||||
|
<details>
|
||||||
|
<summary>Attributes (Success)</summary>
|
||||||
|
|
||||||
|
- `user_tier` (string)
|
||||||
|
|
||||||
##### Tools
|
##### Tools
|
||||||
|
|
||||||
##### `gemini_cli.tool.call.count`
|
##### `gemini_cli.tool.call.count`
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ describe('codeAssist', () => {
|
|||||||
projectId: 'test-project',
|
projectId: 'test-project',
|
||||||
userTier: UserTierId.FREE,
|
userTier: UserTierId.FREE,
|
||||||
userTierName: 'free-tier-name',
|
userTierName: 'free-tier-name',
|
||||||
|
hasOnboardedPreviously: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
it('should create a server for LOGIN_WITH_GOOGLE', async () => {
|
it('should create a server for LOGIN_WITH_GOOGLE', async () => {
|
||||||
@@ -63,7 +64,7 @@ describe('codeAssist', () => {
|
|||||||
);
|
);
|
||||||
expect(setupUser).toHaveBeenCalledWith(
|
expect(setupUser).toHaveBeenCalledWith(
|
||||||
mockAuthClient,
|
mockAuthClient,
|
||||||
mockValidationHandler,
|
mockConfig,
|
||||||
httpOptions,
|
httpOptions,
|
||||||
);
|
);
|
||||||
expect(MockedCodeAssistServer).toHaveBeenCalledWith(
|
expect(MockedCodeAssistServer).toHaveBeenCalledWith(
|
||||||
@@ -95,7 +96,7 @@ describe('codeAssist', () => {
|
|||||||
);
|
);
|
||||||
expect(setupUser).toHaveBeenCalledWith(
|
expect(setupUser).toHaveBeenCalledWith(
|
||||||
mockAuthClient,
|
mockAuthClient,
|
||||||
mockValidationHandler,
|
mockConfig,
|
||||||
httpOptions,
|
httpOptions,
|
||||||
);
|
);
|
||||||
expect(MockedCodeAssistServer).toHaveBeenCalledWith(
|
expect(MockedCodeAssistServer).toHaveBeenCalledWith(
|
||||||
|
|||||||
@@ -22,11 +22,7 @@ export async function createCodeAssistContentGenerator(
|
|||||||
authType === AuthType.COMPUTE_ADC
|
authType === AuthType.COMPUTE_ADC
|
||||||
) {
|
) {
|
||||||
const authClient = await getOauthClient(authType, config);
|
const authClient = await getOauthClient(authType, config);
|
||||||
const userData = await setupUser(
|
const userData = await setupUser(authClient, config, httpOptions);
|
||||||
authClient,
|
|
||||||
config.getValidationHandler(),
|
|
||||||
httpOptions,
|
|
||||||
);
|
|
||||||
return new CodeAssistServer(
|
return new CodeAssistServer(
|
||||||
authClient,
|
authClient,
|
||||||
userData.projectId,
|
userData.projectId,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { ValidationRequiredError } from '../utils/googleQuotaErrors.js';
|
|||||||
import { CodeAssistServer } from '../code_assist/server.js';
|
import { CodeAssistServer } from '../code_assist/server.js';
|
||||||
import type { OAuth2Client } from 'google-auth-library';
|
import type { OAuth2Client } from 'google-auth-library';
|
||||||
import { UserTierId, type GeminiUserTier } from './types.js';
|
import { UserTierId, type GeminiUserTier } from './types.js';
|
||||||
|
import type { Config } from '../config/config.js';
|
||||||
|
|
||||||
vi.mock('../code_assist/server.js');
|
vi.mock('../code_assist/server.js');
|
||||||
|
|
||||||
@@ -35,6 +36,8 @@ describe('setupUser', () => {
|
|||||||
let mockLoad: ReturnType<typeof vi.fn>;
|
let mockLoad: ReturnType<typeof vi.fn>;
|
||||||
let mockOnboardUser: ReturnType<typeof vi.fn>;
|
let mockOnboardUser: ReturnType<typeof vi.fn>;
|
||||||
let mockGetOperation: ReturnType<typeof vi.fn>;
|
let mockGetOperation: ReturnType<typeof vi.fn>;
|
||||||
|
let mockConfig: Config;
|
||||||
|
let mockValidationHandler: ReturnType<typeof vi.fn>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.resetAllMocks();
|
vi.resetAllMocks();
|
||||||
@@ -60,6 +63,18 @@ describe('setupUser', () => {
|
|||||||
getOperation: mockGetOperation,
|
getOperation: mockGetOperation,
|
||||||
}) as unknown as CodeAssistServer,
|
}) as unknown as CodeAssistServer,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
mockValidationHandler = vi.fn();
|
||||||
|
mockConfig = {
|
||||||
|
getValidationHandler: () => mockValidationHandler,
|
||||||
|
getUsageStatisticsEnabled: () => true,
|
||||||
|
getSessionId: () => 'test-session-id',
|
||||||
|
getContentGeneratorConfig: () => ({
|
||||||
|
authType: 'google-login',
|
||||||
|
}),
|
||||||
|
isInteractive: () => false,
|
||||||
|
getExperiments: () => undefined,
|
||||||
|
} as unknown as Config;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -76,9 +91,9 @@ describe('setupUser', () => {
|
|||||||
|
|
||||||
const client = {} as OAuth2Client;
|
const client = {} as OAuth2Client;
|
||||||
// First call
|
// First call
|
||||||
await setupUser(client);
|
await setupUser(client, mockConfig);
|
||||||
// Second call
|
// Second call
|
||||||
await setupUser(client);
|
await setupUser(client, mockConfig);
|
||||||
|
|
||||||
expect(mockLoad).toHaveBeenCalledTimes(1);
|
expect(mockLoad).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
@@ -91,10 +106,10 @@ describe('setupUser', () => {
|
|||||||
|
|
||||||
const client = {} as OAuth2Client;
|
const client = {} as OAuth2Client;
|
||||||
vi.stubEnv('GOOGLE_CLOUD_PROJECT', 'p1');
|
vi.stubEnv('GOOGLE_CLOUD_PROJECT', 'p1');
|
||||||
await setupUser(client);
|
await setupUser(client, mockConfig);
|
||||||
|
|
||||||
vi.stubEnv('GOOGLE_CLOUD_PROJECT', 'p2');
|
vi.stubEnv('GOOGLE_CLOUD_PROJECT', 'p2');
|
||||||
await setupUser(client);
|
await setupUser(client, mockConfig);
|
||||||
|
|
||||||
expect(mockLoad).toHaveBeenCalledTimes(2);
|
expect(mockLoad).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
@@ -106,11 +121,11 @@ describe('setupUser', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const client = {} as OAuth2Client;
|
const client = {} as OAuth2Client;
|
||||||
await setupUser(client);
|
await setupUser(client, mockConfig);
|
||||||
|
|
||||||
vi.advanceTimersByTime(31000); // 31s > 30s expiration
|
vi.advanceTimersByTime(31000); // 31s > 30s expiration
|
||||||
|
|
||||||
await setupUser(client);
|
await setupUser(client, mockConfig);
|
||||||
|
|
||||||
expect(mockLoad).toHaveBeenCalledTimes(2);
|
expect(mockLoad).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
@@ -123,8 +138,10 @@ describe('setupUser', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const client = {} as OAuth2Client;
|
const client = {} as OAuth2Client;
|
||||||
await expect(setupUser(client)).rejects.toThrow('Network error');
|
await expect(setupUser(client, mockConfig)).rejects.toThrow(
|
||||||
await setupUser(client);
|
'Network error',
|
||||||
|
);
|
||||||
|
await setupUser(client, mockConfig);
|
||||||
|
|
||||||
expect(mockLoad).toHaveBeenCalledTimes(2);
|
expect(mockLoad).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
@@ -136,7 +153,7 @@ describe('setupUser', () => {
|
|||||||
mockLoad.mockResolvedValue({
|
mockLoad.mockResolvedValue({
|
||||||
currentTier: mockPaidTier,
|
currentTier: mockPaidTier,
|
||||||
});
|
});
|
||||||
await setupUser({} as OAuth2Client);
|
await setupUser({} as OAuth2Client, mockConfig);
|
||||||
expect(CodeAssistServer).toHaveBeenCalledWith(
|
expect(CodeAssistServer).toHaveBeenCalledWith(
|
||||||
{},
|
{},
|
||||||
'test-project',
|
'test-project',
|
||||||
@@ -157,7 +174,7 @@ describe('setupUser', () => {
|
|||||||
'User-Agent': 'GeminiCLI/1.0.0/gemini-2.0-flash (darwin; arm64)',
|
'User-Agent': 'GeminiCLI/1.0.0/gemini-2.0-flash (darwin; arm64)',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
await setupUser({} as OAuth2Client, undefined, httpOptions);
|
await setupUser({} as OAuth2Client, mockConfig, httpOptions);
|
||||||
expect(CodeAssistServer).toHaveBeenCalledWith(
|
expect(CodeAssistServer).toHaveBeenCalledWith(
|
||||||
{},
|
{},
|
||||||
'test-project',
|
'test-project',
|
||||||
@@ -174,7 +191,7 @@ describe('setupUser', () => {
|
|||||||
cloudaicompanionProject: 'server-project',
|
cloudaicompanionProject: 'server-project',
|
||||||
currentTier: mockPaidTier,
|
currentTier: mockPaidTier,
|
||||||
});
|
});
|
||||||
const result = await setupUser({} as OAuth2Client);
|
const result = await setupUser({} as OAuth2Client, mockConfig);
|
||||||
expect(result.projectId).toBe('server-project');
|
expect(result.projectId).toBe('server-project');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -185,7 +202,7 @@ describe('setupUser', () => {
|
|||||||
throw new ProjectIdRequiredError();
|
throw new ProjectIdRequiredError();
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(setupUser({} as OAuth2Client)).rejects.toThrow(
|
await expect(setupUser({} as OAuth2Client, mockConfig)).rejects.toThrow(
|
||||||
ProjectIdRequiredError,
|
ProjectIdRequiredError,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -197,7 +214,7 @@ describe('setupUser', () => {
|
|||||||
mockLoad.mockResolvedValue({
|
mockLoad.mockResolvedValue({
|
||||||
allowedTiers: [mockPaidTier],
|
allowedTiers: [mockPaidTier],
|
||||||
});
|
});
|
||||||
const userData = await setupUser({} as OAuth2Client);
|
const userData = await setupUser({} as OAuth2Client, mockConfig);
|
||||||
expect(mockOnboardUser).toHaveBeenCalledWith(
|
expect(mockOnboardUser).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
tierId: UserTierId.STANDARD,
|
tierId: UserTierId.STANDARD,
|
||||||
@@ -208,6 +225,7 @@ describe('setupUser', () => {
|
|||||||
projectId: 'server-project',
|
projectId: 'server-project',
|
||||||
userTier: UserTierId.STANDARD,
|
userTier: UserTierId.STANDARD,
|
||||||
userTierName: 'paid',
|
userTierName: 'paid',
|
||||||
|
hasOnboardedPreviously: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -216,7 +234,7 @@ describe('setupUser', () => {
|
|||||||
mockLoad.mockResolvedValue({
|
mockLoad.mockResolvedValue({
|
||||||
allowedTiers: [mockFreeTier],
|
allowedTiers: [mockFreeTier],
|
||||||
});
|
});
|
||||||
const userData = await setupUser({} as OAuth2Client);
|
const userData = await setupUser({} as OAuth2Client, mockConfig);
|
||||||
expect(mockOnboardUser).toHaveBeenCalledWith(
|
expect(mockOnboardUser).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
tierId: UserTierId.FREE,
|
tierId: UserTierId.FREE,
|
||||||
@@ -227,6 +245,7 @@ describe('setupUser', () => {
|
|||||||
projectId: 'server-project',
|
projectId: 'server-project',
|
||||||
userTier: UserTierId.FREE,
|
userTier: UserTierId.FREE,
|
||||||
userTierName: 'free',
|
userTierName: 'free',
|
||||||
|
hasOnboardedPreviously: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -241,11 +260,12 @@ describe('setupUser', () => {
|
|||||||
cloudaicompanionProject: undefined,
|
cloudaicompanionProject: undefined,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const userData = await setupUser({} as OAuth2Client);
|
const userData = await setupUser({} as OAuth2Client, mockConfig);
|
||||||
expect(userData).toEqual({
|
expect(userData).toEqual({
|
||||||
projectId: 'test-project',
|
projectId: 'test-project',
|
||||||
userTier: UserTierId.STANDARD,
|
userTier: UserTierId.STANDARD,
|
||||||
userTierName: 'paid',
|
userTierName: 'paid',
|
||||||
|
hasOnboardedPreviously: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -276,7 +296,7 @@ describe('setupUser', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const promise = setupUser({} as OAuth2Client);
|
const promise = setupUser({} as OAuth2Client, mockConfig);
|
||||||
|
|
||||||
await vi.advanceTimersByTimeAsync(5000);
|
await vi.advanceTimersByTimeAsync(5000);
|
||||||
await vi.advanceTimersByTimeAsync(5000);
|
await vi.advanceTimersByTimeAsync(5000);
|
||||||
@@ -308,10 +328,10 @@ describe('setupUser', () => {
|
|||||||
cloudaicompanionProject: 'p1',
|
cloudaicompanionProject: 'p1',
|
||||||
});
|
});
|
||||||
|
|
||||||
const mockHandler = vi.fn().mockResolvedValue('verify');
|
mockValidationHandler.mockResolvedValue('verify');
|
||||||
const result = await setupUser({} as OAuth2Client, mockHandler);
|
const result = await setupUser({} as OAuth2Client, mockConfig);
|
||||||
|
|
||||||
expect(mockHandler).toHaveBeenCalledWith(
|
expect(mockValidationHandler).toHaveBeenCalledWith(
|
||||||
'https://verify',
|
'https://verify',
|
||||||
'Verify please',
|
'Verify please',
|
||||||
);
|
);
|
||||||
@@ -333,9 +353,9 @@ describe('setupUser', () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const mockHandler = vi.fn().mockResolvedValue('cancel');
|
mockValidationHandler.mockResolvedValue('cancel');
|
||||||
|
|
||||||
await expect(setupUser({} as OAuth2Client, mockHandler)).rejects.toThrow(
|
await expect(setupUser({} as OAuth2Client, mockConfig)).rejects.toThrow(
|
||||||
ValidationCancelledError,
|
ValidationCancelledError,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -343,7 +363,7 @@ describe('setupUser', () => {
|
|||||||
it('should throw error if LoadCodeAssist returns empty response', async () => {
|
it('should throw error if LoadCodeAssist returns empty response', async () => {
|
||||||
mockLoad.mockResolvedValue(null);
|
mockLoad.mockResolvedValue(null);
|
||||||
|
|
||||||
await expect(setupUser({} as OAuth2Client)).rejects.toThrow(
|
await expect(setupUser({} as OAuth2Client, mockConfig)).rejects.toThrow(
|
||||||
'LoadCodeAssist returned empty response',
|
'LoadCodeAssist returned empty response',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,11 +15,17 @@ import {
|
|||||||
} from './types.js';
|
} from './types.js';
|
||||||
import { CodeAssistServer, type HttpOptions } from './server.js';
|
import { CodeAssistServer, type HttpOptions } from './server.js';
|
||||||
import type { AuthClient } from 'google-auth-library';
|
import type { AuthClient } from 'google-auth-library';
|
||||||
import type { ValidationHandler } from '../fallback/types.js';
|
|
||||||
import { ChangeAuthRequestedError } from '../utils/errors.js';
|
import { ChangeAuthRequestedError } from '../utils/errors.js';
|
||||||
import { ValidationRequiredError } from '../utils/googleQuotaErrors.js';
|
import { ValidationRequiredError } from '../utils/googleQuotaErrors.js';
|
||||||
import { debugLogger } from '../utils/debugLogger.js';
|
import { debugLogger } from '../utils/debugLogger.js';
|
||||||
import { createCache, type CacheService } from '../utils/cache.js';
|
import { createCache, type CacheService } from '../utils/cache.js';
|
||||||
|
import type { Config } from '../config/config.js';
|
||||||
|
import {
|
||||||
|
logOnboardingStart,
|
||||||
|
logOnboardingSuccess,
|
||||||
|
OnboardingStartEvent,
|
||||||
|
OnboardingSuccessEvent,
|
||||||
|
} from '../telemetry/index.js';
|
||||||
|
|
||||||
export class ProjectIdRequiredError extends Error {
|
export class ProjectIdRequiredError extends Error {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -54,6 +60,7 @@ export interface UserData {
|
|||||||
userTier: UserTierId;
|
userTier: UserTierId;
|
||||||
userTierName?: string;
|
userTierName?: string;
|
||||||
paidTier?: GeminiUserTier;
|
paidTier?: GeminiUserTier;
|
||||||
|
hasOnboardedPreviously?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache to store the results of setupUser to avoid redundant network calls.
|
// Cache to store the results of setupUser to avoid redundant network calls.
|
||||||
@@ -94,7 +101,8 @@ export function resetUserDataCacheForTesting() {
|
|||||||
* retry, auth change, or cancellation.
|
* retry, auth change, or cancellation.
|
||||||
*
|
*
|
||||||
* @param client - The authenticated client to use for API calls
|
* @param client - The authenticated client to use for API calls
|
||||||
* @param validationHandler - Optional handler for account validation flow
|
* @param config - The CLI configuration
|
||||||
|
* @param httpOptions - Optional HTTP options
|
||||||
* @returns The user's project ID, tier ID, and tier name
|
* @returns The user's project ID, tier ID, and tier name
|
||||||
* @throws {ValidationRequiredError} If account validation is required
|
* @throws {ValidationRequiredError} If account validation is required
|
||||||
* @throws {ProjectIdRequiredError} If no project ID is available and required
|
* @throws {ProjectIdRequiredError} If no project ID is available and required
|
||||||
@@ -103,7 +111,7 @@ export function resetUserDataCacheForTesting() {
|
|||||||
*/
|
*/
|
||||||
export async function setupUser(
|
export async function setupUser(
|
||||||
client: AuthClient,
|
client: AuthClient,
|
||||||
validationHandler?: ValidationHandler,
|
config: Config,
|
||||||
httpOptions: HttpOptions = {},
|
httpOptions: HttpOptions = {},
|
||||||
): Promise<UserData> {
|
): Promise<UserData> {
|
||||||
const projectId =
|
const projectId =
|
||||||
@@ -119,7 +127,7 @@ export async function setupUser(
|
|||||||
);
|
);
|
||||||
|
|
||||||
return projectCache.getOrCreate(projectId, () =>
|
return projectCache.getOrCreate(projectId, () =>
|
||||||
_doSetupUser(client, projectId, validationHandler, httpOptions),
|
_doSetupUser(client, projectId, config, httpOptions),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +137,7 @@ export async function setupUser(
|
|||||||
async function _doSetupUser(
|
async function _doSetupUser(
|
||||||
client: AuthClient,
|
client: AuthClient,
|
||||||
projectId: string | undefined,
|
projectId: string | undefined,
|
||||||
validationHandler?: ValidationHandler,
|
config: Config,
|
||||||
httpOptions: HttpOptions = {},
|
httpOptions: HttpOptions = {},
|
||||||
): Promise<UserData> {
|
): Promise<UserData> {
|
||||||
const caServer = new CodeAssistServer(
|
const caServer = new CodeAssistServer(
|
||||||
@@ -146,6 +154,8 @@ async function _doSetupUser(
|
|||||||
pluginType: 'GEMINI',
|
pluginType: 'GEMINI',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const validationHandler = config.getValidationHandler();
|
||||||
|
|
||||||
let loadRes: LoadCodeAssistResponse;
|
let loadRes: LoadCodeAssistResponse;
|
||||||
while (true) {
|
while (true) {
|
||||||
loadRes = await caServer.loadCodeAssist({
|
loadRes = await caServer.loadCodeAssist({
|
||||||
@@ -194,6 +204,8 @@ async function _doSetupUser(
|
|||||||
UserTierId.STANDARD,
|
UserTierId.STANDARD,
|
||||||
userTierName: loadRes.paidTier?.name ?? loadRes.currentTier.name,
|
userTierName: loadRes.paidTier?.name ?? loadRes.currentTier.name,
|
||||||
paidTier: loadRes.paidTier ?? undefined,
|
paidTier: loadRes.paidTier ?? undefined,
|
||||||
|
hasOnboardedPreviously:
|
||||||
|
loadRes.currentTier.hasOnboardedPreviously ?? true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,6 +218,8 @@ async function _doSetupUser(
|
|||||||
loadRes.paidTier?.id ?? loadRes.currentTier.id ?? UserTierId.STANDARD,
|
loadRes.paidTier?.id ?? loadRes.currentTier.id ?? UserTierId.STANDARD,
|
||||||
userTierName: loadRes.paidTier?.name ?? loadRes.currentTier.name,
|
userTierName: loadRes.paidTier?.name ?? loadRes.currentTier.name,
|
||||||
paidTier: loadRes.paidTier ?? undefined,
|
paidTier: loadRes.paidTier ?? undefined,
|
||||||
|
hasOnboardedPreviously:
|
||||||
|
loadRes.currentTier.hasOnboardedPreviously ?? true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,6 +250,8 @@ async function _doSetupUser(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logOnboardingStart(config, new OnboardingStartEvent());
|
||||||
|
|
||||||
let lroRes = await caServer.onboardUser(onboardReq);
|
let lroRes = await caServer.onboardUser(onboardReq);
|
||||||
if (!lroRes.done && lroRes.name) {
|
if (!lroRes.done && lroRes.name) {
|
||||||
const operationName = lroRes.name;
|
const operationName = lroRes.name;
|
||||||
@@ -245,12 +261,16 @@ async function _doSetupUser(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userTier = tier.id ?? UserTierId.STANDARD;
|
||||||
|
logOnboardingSuccess(config, new OnboardingSuccessEvent(userTier));
|
||||||
|
|
||||||
if (!lroRes.response?.cloudaicompanionProject?.id) {
|
if (!lroRes.response?.cloudaicompanionProject?.id) {
|
||||||
if (projectId) {
|
if (projectId) {
|
||||||
return {
|
return {
|
||||||
projectId,
|
projectId,
|
||||||
userTier: tier.id ?? UserTierId.STANDARD,
|
userTier: tier.id ?? UserTierId.STANDARD,
|
||||||
userTierName: tier.name,
|
userTierName: tier.name,
|
||||||
|
hasOnboardedPreviously: tier.hasOnboardedPreviously ?? false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,6 +281,7 @@ async function _doSetupUser(
|
|||||||
projectId: lroRes.response.cloudaicompanionProject.id,
|
projectId: lroRes.response.cloudaicompanionProject.id,
|
||||||
userTier: tier.id ?? UserTierId.STANDARD,
|
userTier: tier.id ?? UserTierId.STANDARD,
|
||||||
userTierName: tier.name,
|
userTierName: tier.name,
|
||||||
|
hasOnboardedPreviously: tier.hasOnboardedPreviously ?? false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ import {
|
|||||||
AgentFinishEvent,
|
AgentFinishEvent,
|
||||||
WebFetchFallbackAttemptEvent,
|
WebFetchFallbackAttemptEvent,
|
||||||
HookCallEvent,
|
HookCallEvent,
|
||||||
|
OnboardingStartEvent,
|
||||||
|
OnboardingSuccessEvent,
|
||||||
} from '../types.js';
|
} from '../types.js';
|
||||||
import { HookType } from '../../hooks/types.js';
|
import { HookType } from '../../hooks/types.js';
|
||||||
import { AgentTerminateMode } from '../../agents/types.js';
|
import { AgentTerminateMode } from '../../agents/types.js';
|
||||||
@@ -1652,4 +1654,38 @@ describe('ClearcutLogger', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('logOnboardingStartEvent', () => {
|
||||||
|
it('logs an event with proper name and start key', () => {
|
||||||
|
const { logger } = setup();
|
||||||
|
const event = new OnboardingStartEvent();
|
||||||
|
|
||||||
|
logger?.logOnboardingStartEvent(event);
|
||||||
|
|
||||||
|
const events = getEvents(logger!);
|
||||||
|
expect(events.length).toBe(1);
|
||||||
|
expect(events[0]).toHaveEventName(EventNames.ONBOARDING_START);
|
||||||
|
expect(events[0]).toHaveMetadataValue([
|
||||||
|
EventMetadataKey.GEMINI_CLI_ONBOARDING_START,
|
||||||
|
'true',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('logOnboardingSuccessEvent', () => {
|
||||||
|
it('logs an event with proper name and user tier', () => {
|
||||||
|
const { logger } = setup();
|
||||||
|
const event = new OnboardingSuccessEvent('standard-tier');
|
||||||
|
|
||||||
|
logger?.logOnboardingSuccessEvent(event);
|
||||||
|
|
||||||
|
const events = getEvents(logger!);
|
||||||
|
expect(events.length).toBe(1);
|
||||||
|
expect(events[0]).toHaveEventName(EventNames.ONBOARDING_SUCCESS);
|
||||||
|
expect(events[0]).toHaveMetadataValue([
|
||||||
|
EventMetadataKey.GEMINI_CLI_ONBOARDING_USER_TIER,
|
||||||
|
'standard-tier',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ import type {
|
|||||||
KeychainAvailabilityEvent,
|
KeychainAvailabilityEvent,
|
||||||
TokenStorageInitializationEvent,
|
TokenStorageInitializationEvent,
|
||||||
StartupStatsEvent,
|
StartupStatsEvent,
|
||||||
|
OnboardingStartEvent,
|
||||||
|
OnboardingSuccessEvent,
|
||||||
} from '../types.js';
|
} from '../types.js';
|
||||||
import type {
|
import type {
|
||||||
CreditsUsedEvent,
|
CreditsUsedEvent,
|
||||||
@@ -124,6 +126,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',
|
||||||
|
ONBOARDING_START = 'onboarding_start',
|
||||||
|
ONBOARDING_SUCCESS = 'onboarding_success',
|
||||||
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',
|
||||||
@@ -1796,6 +1800,33 @@ export class ClearcutLogger {
|
|||||||
this.flushIfNeeded();
|
this.flushIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logOnboardingStartEvent(_event: OnboardingStartEvent): void {
|
||||||
|
const data: EventValue[] = [
|
||||||
|
{
|
||||||
|
gemini_cli_key: EventMetadataKey.GEMINI_CLI_ONBOARDING_START,
|
||||||
|
value: 'true',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
this.enqueueLogEvent(
|
||||||
|
this.createLogEvent(EventNames.ONBOARDING_START, data),
|
||||||
|
);
|
||||||
|
this.flushIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
logOnboardingSuccessEvent(event: OnboardingSuccessEvent): void {
|
||||||
|
const data: EventValue[] = [];
|
||||||
|
if (event.userTier) {
|
||||||
|
data.push({
|
||||||
|
gemini_cli_key: EventMetadataKey.GEMINI_CLI_ONBOARDING_USER_TIER,
|
||||||
|
value: event.userTier,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.enqueueLogEvent(
|
||||||
|
this.createLogEvent(EventNames.ONBOARDING_SUCCESS, data),
|
||||||
|
);
|
||||||
|
this.flushIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
logStartupStatsEvent(event: StartupStatsEvent): void {
|
logStartupStatsEvent(event: StartupStatsEvent): void {
|
||||||
const data: EventValue[] = [
|
const data: EventValue[] = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
// Defines valid event metadata keys for Clearcut logging.
|
// Defines valid event metadata keys for Clearcut logging.
|
||||||
export enum EventMetadataKey {
|
export enum EventMetadataKey {
|
||||||
// Deleted enums: 24
|
// Deleted enums: 24
|
||||||
// Next ID: 191
|
// Next ID: 194
|
||||||
|
|
||||||
GEMINI_CLI_KEY_UNKNOWN = 0,
|
GEMINI_CLI_KEY_UNKNOWN = 0,
|
||||||
|
|
||||||
@@ -712,4 +712,14 @@ export enum EventMetadataKey {
|
|||||||
|
|
||||||
// Logs the source of a credit purchase click (e.g. overage_menu, empty_wallet_menu, manage).
|
// Logs the source of a credit purchase click (e.g. overage_menu, empty_wallet_menu, manage).
|
||||||
GEMINI_CLI_BILLING_PURCHASE_SOURCE = 190,
|
GEMINI_CLI_BILLING_PURCHASE_SOURCE = 190,
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// Gemini Enterprise (GE) Event Keys
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
// Logs the start of the onboarding process.
|
||||||
|
GEMINI_CLI_ONBOARDING_START = 192,
|
||||||
|
|
||||||
|
// Logs the user tier for onboarding success events.
|
||||||
|
GEMINI_CLI_ONBOARDING_USER_TIER = 193,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ export {
|
|||||||
logWebFetchFallbackAttempt,
|
logWebFetchFallbackAttempt,
|
||||||
logNetworkRetryAttempt,
|
logNetworkRetryAttempt,
|
||||||
logRewind,
|
logRewind,
|
||||||
|
logOnboardingStart,
|
||||||
|
logOnboardingSuccess,
|
||||||
} from './loggers.js';
|
} from './loggers.js';
|
||||||
export {
|
export {
|
||||||
logConsecaPolicyGeneration,
|
logConsecaPolicyGeneration,
|
||||||
@@ -70,6 +72,8 @@ export {
|
|||||||
NetworkRetryAttemptEvent,
|
NetworkRetryAttemptEvent,
|
||||||
ToolCallDecision,
|
ToolCallDecision,
|
||||||
RewindEvent,
|
RewindEvent,
|
||||||
|
OnboardingStartEvent,
|
||||||
|
OnboardingSuccessEvent,
|
||||||
ConsecaPolicyGenerationEvent,
|
ConsecaPolicyGenerationEvent,
|
||||||
ConsecaVerdictEvent,
|
ConsecaVerdictEvent,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ import {
|
|||||||
logNetworkRetryAttempt,
|
logNetworkRetryAttempt,
|
||||||
logExtensionUpdateEvent,
|
logExtensionUpdateEvent,
|
||||||
logHookCall,
|
logHookCall,
|
||||||
|
logOnboardingStart,
|
||||||
|
logOnboardingSuccess,
|
||||||
} from './loggers.js';
|
} from './loggers.js';
|
||||||
import { ToolCallDecision } from './tool-call-decision.js';
|
import { ToolCallDecision } from './tool-call-decision.js';
|
||||||
import {
|
import {
|
||||||
@@ -72,6 +74,8 @@ import {
|
|||||||
EVENT_WEB_FETCH_FALLBACK_ATTEMPT,
|
EVENT_WEB_FETCH_FALLBACK_ATTEMPT,
|
||||||
EVENT_INVALID_CHUNK,
|
EVENT_INVALID_CHUNK,
|
||||||
EVENT_NETWORK_RETRY_ATTEMPT,
|
EVENT_NETWORK_RETRY_ATTEMPT,
|
||||||
|
EVENT_ONBOARDING_START,
|
||||||
|
EVENT_ONBOARDING_SUCCESS,
|
||||||
ApiErrorEvent,
|
ApiErrorEvent,
|
||||||
ApiRequestEvent,
|
ApiRequestEvent,
|
||||||
ApiResponseEvent,
|
ApiResponseEvent,
|
||||||
@@ -98,6 +102,8 @@ import {
|
|||||||
EVENT_EXTENSION_UPDATE,
|
EVENT_EXTENSION_UPDATE,
|
||||||
HookCallEvent,
|
HookCallEvent,
|
||||||
EVENT_HOOK_CALL,
|
EVENT_HOOK_CALL,
|
||||||
|
OnboardingStartEvent,
|
||||||
|
OnboardingSuccessEvent,
|
||||||
LlmRole,
|
LlmRole,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
import { HookType } from '../hooks/types.js';
|
import { HookType } from '../hooks/types.js';
|
||||||
@@ -2508,6 +2514,76 @@ describe('loggers', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('logOnboardingStart', () => {
|
||||||
|
const mockConfig = makeFakeConfig();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.spyOn(ClearcutLogger.prototype, 'logOnboardingStartEvent');
|
||||||
|
vi.spyOn(metrics, 'recordOnboardingStart');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should log onboarding start event to Clearcut and OTEL, and record metrics', () => {
|
||||||
|
const event = new OnboardingStartEvent();
|
||||||
|
|
||||||
|
logOnboardingStart(mockConfig, event);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
ClearcutLogger.prototype.logOnboardingStartEvent,
|
||||||
|
).toHaveBeenCalledWith(event);
|
||||||
|
|
||||||
|
expect(mockLogger.emit).toHaveBeenCalledWith({
|
||||||
|
body: 'Onboarding started.',
|
||||||
|
attributes: {
|
||||||
|
'session.id': 'test-session-id',
|
||||||
|
'user.email': 'test-user@example.com',
|
||||||
|
'installation.id': 'test-installation-id',
|
||||||
|
'event.name': EVENT_ONBOARDING_START,
|
||||||
|
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
||||||
|
interactive: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(metrics.recordOnboardingStart).toHaveBeenCalledWith(mockConfig);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('logOnboardingSuccess', () => {
|
||||||
|
const mockConfig = makeFakeConfig();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.spyOn(ClearcutLogger.prototype, 'logOnboardingSuccessEvent');
|
||||||
|
vi.spyOn(metrics, 'recordOnboardingSuccess');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should log onboarding success event to Clearcut and OTEL, and record metrics', () => {
|
||||||
|
const event = new OnboardingSuccessEvent('standard-tier');
|
||||||
|
|
||||||
|
logOnboardingSuccess(mockConfig, event);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
ClearcutLogger.prototype.logOnboardingSuccessEvent,
|
||||||
|
).toHaveBeenCalledWith(event);
|
||||||
|
|
||||||
|
expect(mockLogger.emit).toHaveBeenCalledWith({
|
||||||
|
body: 'Onboarding succeeded. Tier: standard-tier',
|
||||||
|
attributes: {
|
||||||
|
'session.id': 'test-session-id',
|
||||||
|
'user.email': 'test-user@example.com',
|
||||||
|
'installation.id': 'test-installation-id',
|
||||||
|
'event.name': EVENT_ONBOARDING_SUCCESS,
|
||||||
|
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
||||||
|
interactive: false,
|
||||||
|
user_tier: 'standard-tier',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(metrics.recordOnboardingSuccess).toHaveBeenCalledWith(
|
||||||
|
mockConfig,
|
||||||
|
'standard-tier',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Telemetry Buffering', () => {
|
describe('Telemetry Buffering', () => {
|
||||||
it('should buffer events when SDK is not initialized', async () => {
|
it('should buffer events when SDK is not initialized', async () => {
|
||||||
vi.spyOn(sdk, 'isTelemetrySdkInitialized').mockReturnValue(false);
|
vi.spyOn(sdk, 'isTelemetrySdkInitialized').mockReturnValue(false);
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ import {
|
|||||||
type ToolOutputMaskingEvent,
|
type ToolOutputMaskingEvent,
|
||||||
type KeychainAvailabilityEvent,
|
type KeychainAvailabilityEvent,
|
||||||
type TokenStorageInitializationEvent,
|
type TokenStorageInitializationEvent,
|
||||||
|
type OnboardingStartEvent,
|
||||||
|
type OnboardingSuccessEvent,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
import {
|
import {
|
||||||
recordApiErrorMetrics,
|
recordApiErrorMetrics,
|
||||||
@@ -79,6 +81,8 @@ import {
|
|||||||
recordKeychainAvailability,
|
recordKeychainAvailability,
|
||||||
recordTokenStorageInitialization,
|
recordTokenStorageInitialization,
|
||||||
recordInvalidChunk,
|
recordInvalidChunk,
|
||||||
|
recordOnboardingStart,
|
||||||
|
recordOnboardingSuccess,
|
||||||
} 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';
|
||||||
@@ -871,6 +875,40 @@ export function logTokenStorageInitialization(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function logOnboardingStart(
|
||||||
|
config: Config,
|
||||||
|
event: OnboardingStartEvent,
|
||||||
|
): void {
|
||||||
|
ClearcutLogger.getInstance(config)?.logOnboardingStartEvent(event);
|
||||||
|
bufferTelemetryEvent(() => {
|
||||||
|
const logger = logs.getLogger(SERVICE_NAME);
|
||||||
|
const logRecord: LogRecord = {
|
||||||
|
body: event.toLogBody(),
|
||||||
|
attributes: event.toOpenTelemetryAttributes(config),
|
||||||
|
};
|
||||||
|
logger.emit(logRecord);
|
||||||
|
|
||||||
|
recordOnboardingStart(config);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logOnboardingSuccess(
|
||||||
|
config: Config,
|
||||||
|
event: OnboardingSuccessEvent,
|
||||||
|
): void {
|
||||||
|
ClearcutLogger.getInstance(config)?.logOnboardingSuccessEvent(event);
|
||||||
|
bufferTelemetryEvent(() => {
|
||||||
|
const logger = logs.getLogger(SERVICE_NAME);
|
||||||
|
const logRecord: LogRecord = {
|
||||||
|
body: event.toLogBody(),
|
||||||
|
attributes: event.toOpenTelemetryAttributes(config),
|
||||||
|
};
|
||||||
|
logger.emit(logRecord);
|
||||||
|
|
||||||
|
recordOnboardingSuccess(config, event.userTier);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function logBillingEvent(
|
export function logBillingEvent(
|
||||||
config: Config,
|
config: Config,
|
||||||
event: BillingTelemetryEvent,
|
event: BillingTelemetryEvent,
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ const KEYCHAIN_AVAILABILITY_COUNT = 'gemini_cli.keychain.availability.count';
|
|||||||
const TOKEN_STORAGE_TYPE_COUNT = 'gemini_cli.token_storage.type.count';
|
const TOKEN_STORAGE_TYPE_COUNT = 'gemini_cli.token_storage.type.count';
|
||||||
const OVERAGE_OPTION_COUNT = 'gemini_cli.overage_option.count';
|
const OVERAGE_OPTION_COUNT = 'gemini_cli.overage_option.count';
|
||||||
const CREDIT_PURCHASE_COUNT = 'gemini_cli.credit_purchase.count';
|
const CREDIT_PURCHASE_COUNT = 'gemini_cli.credit_purchase.count';
|
||||||
|
const EVENT_ONBOARDING_START = 'gemini_cli.onboarding.start';
|
||||||
|
const EVENT_ONBOARDING_SUCCESS = 'gemini_cli.onboarding.success';
|
||||||
|
|
||||||
// Agent Metrics
|
// Agent Metrics
|
||||||
const AGENT_RUN_COUNT = 'gemini_cli.agent.run.count';
|
const AGENT_RUN_COUNT = 'gemini_cli.agent.run.count';
|
||||||
@@ -299,6 +301,20 @@ const COUNTER_DEFINITIONS = {
|
|||||||
model: string;
|
model: string;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
[EVENT_ONBOARDING_START]: {
|
||||||
|
description: 'Counts onboarding started',
|
||||||
|
valueType: ValueType.INT,
|
||||||
|
assign: (c: Counter) => (onboardingStartCounter = c),
|
||||||
|
attributes: {} as Record<string, never>,
|
||||||
|
},
|
||||||
|
[EVENT_ONBOARDING_SUCCESS]: {
|
||||||
|
description: 'Counts onboarding succeeded',
|
||||||
|
valueType: ValueType.INT,
|
||||||
|
assign: (c: Counter) => (onboardingSuccessCounter = c),
|
||||||
|
attributes: {} as {
|
||||||
|
user_tier?: string;
|
||||||
|
},
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const HISTOGRAM_DEFINITIONS = {
|
const HISTOGRAM_DEFINITIONS = {
|
||||||
@@ -640,6 +656,8 @@ let keychainAvailabilityCounter: Counter | undefined;
|
|||||||
let tokenStorageTypeCounter: Counter | undefined;
|
let tokenStorageTypeCounter: Counter | undefined;
|
||||||
let overageOptionCounter: Counter | undefined;
|
let overageOptionCounter: Counter | undefined;
|
||||||
let creditPurchaseCounter: Counter | undefined;
|
let creditPurchaseCounter: Counter | undefined;
|
||||||
|
let onboardingStartCounter: Counter | undefined;
|
||||||
|
let onboardingSuccessCounter: Counter | undefined;
|
||||||
|
|
||||||
// OpenTelemetry GenAI Semantic Convention Metrics
|
// OpenTelemetry GenAI Semantic Convention Metrics
|
||||||
let genAiClientTokenUsageHistogram: Histogram | undefined;
|
let genAiClientTokenUsageHistogram: Histogram | undefined;
|
||||||
@@ -812,6 +830,31 @@ export function recordLinesChanged(
|
|||||||
|
|
||||||
// --- New Metric Recording Functions ---
|
// --- New Metric Recording Functions ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records a metric for when the Google auth process starts.
|
||||||
|
*/
|
||||||
|
export function recordOnboardingStart(config: Config): void {
|
||||||
|
if (!onboardingStartCounter || !isMetricsInitialized) return;
|
||||||
|
onboardingStartCounter.add(
|
||||||
|
1,
|
||||||
|
baseMetricDefinition.getCommonAttributes(config),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records a metric for when the Google auth process ends successfully.
|
||||||
|
*/
|
||||||
|
export function recordOnboardingSuccess(
|
||||||
|
config: Config,
|
||||||
|
userTier?: string,
|
||||||
|
): void {
|
||||||
|
if (!onboardingSuccessCounter || !isMetricsInitialized) return;
|
||||||
|
onboardingSuccessCounter.add(1, {
|
||||||
|
...baseMetricDefinition.getCommonAttributes(config),
|
||||||
|
...(userTier && { user_tier: userTier }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Records a metric for when a UI frame flickers.
|
* Records a metric for when a UI frame flickers.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -344,9 +344,9 @@ export async function initializeTelemetry(
|
|||||||
if (config.getDebugMode()) {
|
if (config.getDebugMode()) {
|
||||||
debugLogger.log('OpenTelemetry SDK started successfully.');
|
debugLogger.log('OpenTelemetry SDK started successfully.');
|
||||||
}
|
}
|
||||||
telemetryInitialized = true;
|
|
||||||
activeTelemetryEmail = credentials?.client_email;
|
activeTelemetryEmail = credentials?.client_email;
|
||||||
initializeMetrics(config);
|
initializeMetrics(config);
|
||||||
|
telemetryInitialized = true;
|
||||||
void flushTelemetryBuffer();
|
void flushTelemetryBuffer();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
debugLogger.error('Error starting OpenTelemetry SDK:', error);
|
debugLogger.error('Error starting OpenTelemetry SDK:', error);
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import { getFileDiffFromResultDisplay } from '../utils/fileDiffUtils.js';
|
|||||||
import { LlmRole } from './llmRole.js';
|
import { LlmRole } from './llmRole.js';
|
||||||
export { LlmRole };
|
export { LlmRole };
|
||||||
import type { HookType } from '../hooks/types.js';
|
import type { HookType } from '../hooks/types.js';
|
||||||
|
import type { UserTierId } from '../code_assist/types.js';
|
||||||
|
|
||||||
export interface BaseTelemetryEvent {
|
export interface BaseTelemetryEvent {
|
||||||
'event.name': string;
|
'event.name': string;
|
||||||
@@ -2360,6 +2361,55 @@ export class KeychainAvailabilityEvent implements BaseTelemetryEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const EVENT_ONBOARDING_START = 'gemini_cli.onboarding.start';
|
||||||
|
export class OnboardingStartEvent implements BaseTelemetryEvent {
|
||||||
|
'event.name': 'onboarding_start';
|
||||||
|
'event.timestamp': string;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this['event.name'] = 'onboarding_start';
|
||||||
|
this['event.timestamp'] = new Date().toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
toOpenTelemetryAttributes(config: Config): LogAttributes {
|
||||||
|
return {
|
||||||
|
...getCommonAttributes(config),
|
||||||
|
'event.name': EVENT_ONBOARDING_START,
|
||||||
|
'event.timestamp': this['event.timestamp'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
toLogBody(): string {
|
||||||
|
return 'Onboarding started.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EVENT_ONBOARDING_SUCCESS = 'gemini_cli.onboarding.success';
|
||||||
|
export class OnboardingSuccessEvent implements BaseTelemetryEvent {
|
||||||
|
'event.name': 'onboarding_success';
|
||||||
|
'event.timestamp': string;
|
||||||
|
userTier?: UserTierId;
|
||||||
|
|
||||||
|
constructor(userTier?: UserTierId) {
|
||||||
|
this['event.name'] = 'onboarding_success';
|
||||||
|
this['event.timestamp'] = new Date().toISOString();
|
||||||
|
this.userTier = userTier;
|
||||||
|
}
|
||||||
|
|
||||||
|
toOpenTelemetryAttributes(config: Config): LogAttributes {
|
||||||
|
return {
|
||||||
|
...getCommonAttributes(config),
|
||||||
|
'event.name': EVENT_ONBOARDING_SUCCESS,
|
||||||
|
'event.timestamp': this['event.timestamp'],
|
||||||
|
user_tier: this.userTier ?? '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
toLogBody(): string {
|
||||||
|
return `Onboarding succeeded.${this.userTier ? ` Tier: ${this.userTier}` : ''}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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