mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 05:12:55 -07:00
feat(telemetry): Ensure experiment IDs are included in OpenTelemetry logs (#18747)
This commit is contained in:
@@ -187,7 +187,7 @@ describe('loggers', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('logCliConfiguration', () => {
|
describe('logCliConfiguration', () => {
|
||||||
it('should log the cli configuration', () => {
|
it('should log the cli configuration', async () => {
|
||||||
const mockConfig = {
|
const mockConfig = {
|
||||||
getSessionId: () => 'test-session-id',
|
getSessionId: () => 'test-session-id',
|
||||||
getModel: () => 'test-model',
|
getModel: () => 'test-model',
|
||||||
@@ -226,11 +226,14 @@ describe('loggers', () => {
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
isInteractive: () => false,
|
isInteractive: () => false,
|
||||||
|
getExperiments: () => undefined,
|
||||||
|
getExperimentsAsync: async () => undefined,
|
||||||
} as unknown as Config;
|
} as unknown as Config;
|
||||||
|
|
||||||
const startSessionEvent = new StartSessionEvent(mockConfig);
|
const startSessionEvent = new StartSessionEvent(mockConfig);
|
||||||
logCliConfiguration(mockConfig, startSessionEvent);
|
logCliConfiguration(mockConfig, startSessionEvent);
|
||||||
|
|
||||||
|
await new Promise(process.nextTick);
|
||||||
expect(mockLogger.emit).toHaveBeenCalledWith({
|
expect(mockLogger.emit).toHaveBeenCalledWith({
|
||||||
body: 'CLI configuration loaded.',
|
body: 'CLI configuration loaded.',
|
||||||
attributes: {
|
attributes: {
|
||||||
@@ -271,6 +274,8 @@ describe('loggers', () => {
|
|||||||
getTelemetryLogPromptsEnabled: () => true,
|
getTelemetryLogPromptsEnabled: () => true,
|
||||||
getUsageStatisticsEnabled: () => true,
|
getUsageStatisticsEnabled: () => true,
|
||||||
isInteractive: () => false,
|
isInteractive: () => false,
|
||||||
|
getExperiments: () => undefined,
|
||||||
|
getExperimentsAsync: async () => undefined,
|
||||||
} as unknown as Config;
|
} as unknown as Config;
|
||||||
|
|
||||||
it('should log a user prompt', () => {
|
it('should log a user prompt', () => {
|
||||||
@@ -308,6 +313,8 @@ describe('loggers', () => {
|
|||||||
getTargetDir: () => 'target-dir',
|
getTargetDir: () => 'target-dir',
|
||||||
getUsageStatisticsEnabled: () => true,
|
getUsageStatisticsEnabled: () => true,
|
||||||
isInteractive: () => false,
|
isInteractive: () => false,
|
||||||
|
getExperiments: () => undefined,
|
||||||
|
getExperimentsAsync: async () => undefined,
|
||||||
} as unknown as Config;
|
} as unknown as Config;
|
||||||
const event = new UserPromptEvent(
|
const event = new UserPromptEvent(
|
||||||
11,
|
11,
|
||||||
@@ -343,6 +350,8 @@ describe('loggers', () => {
|
|||||||
getTelemetryEnabled: () => true,
|
getTelemetryEnabled: () => true,
|
||||||
getTelemetryLogPromptsEnabled: () => true,
|
getTelemetryLogPromptsEnabled: () => true,
|
||||||
isInteractive: () => false,
|
isInteractive: () => false,
|
||||||
|
getExperiments: () => undefined,
|
||||||
|
getExperimentsAsync: async () => undefined,
|
||||||
} as Config;
|
} as Config;
|
||||||
|
|
||||||
const mockMetrics = {
|
const mockMetrics = {
|
||||||
@@ -519,6 +528,8 @@ describe('loggers', () => {
|
|||||||
getTelemetryEnabled: () => true,
|
getTelemetryEnabled: () => true,
|
||||||
getTelemetryLogPromptsEnabled: () => true,
|
getTelemetryLogPromptsEnabled: () => true,
|
||||||
isInteractive: () => false,
|
isInteractive: () => false,
|
||||||
|
getExperiments: () => undefined,
|
||||||
|
getExperimentsAsync: async () => undefined,
|
||||||
} as Config;
|
} as Config;
|
||||||
|
|
||||||
const mockMetrics = {
|
const mockMetrics = {
|
||||||
@@ -651,6 +662,8 @@ describe('loggers', () => {
|
|||||||
getTelemetryEnabled: () => true,
|
getTelemetryEnabled: () => true,
|
||||||
getTelemetryLogPromptsEnabled: () => true,
|
getTelemetryLogPromptsEnabled: () => true,
|
||||||
isInteractive: () => false,
|
isInteractive: () => false,
|
||||||
|
getExperiments: () => undefined,
|
||||||
|
getExperimentsAsync: async () => undefined,
|
||||||
getContentGeneratorConfig: () => ({
|
getContentGeneratorConfig: () => ({
|
||||||
authType: AuthType.LOGIN_WITH_GOOGLE,
|
authType: AuthType.LOGIN_WITH_GOOGLE,
|
||||||
}),
|
}),
|
||||||
@@ -727,6 +740,8 @@ describe('loggers', () => {
|
|||||||
getTelemetryEnabled: () => true,
|
getTelemetryEnabled: () => true,
|
||||||
getTelemetryLogPromptsEnabled: () => true, // Enabled
|
getTelemetryLogPromptsEnabled: () => true, // Enabled
|
||||||
isInteractive: () => false,
|
isInteractive: () => false,
|
||||||
|
getExperiments: () => undefined,
|
||||||
|
getExperimentsAsync: async () => undefined,
|
||||||
getContentGeneratorConfig: () => ({
|
getContentGeneratorConfig: () => ({
|
||||||
authType: AuthType.USE_GEMINI,
|
authType: AuthType.USE_GEMINI,
|
||||||
}),
|
}),
|
||||||
@@ -814,6 +829,8 @@ describe('loggers', () => {
|
|||||||
getTelemetryEnabled: () => true,
|
getTelemetryEnabled: () => true,
|
||||||
getTelemetryLogPromptsEnabled: () => false, // Disabled
|
getTelemetryLogPromptsEnabled: () => false, // Disabled
|
||||||
isInteractive: () => false,
|
isInteractive: () => false,
|
||||||
|
getExperiments: () => undefined,
|
||||||
|
getExperimentsAsync: async () => undefined,
|
||||||
getContentGeneratorConfig: () => ({
|
getContentGeneratorConfig: () => ({
|
||||||
authType: AuthType.USE_VERTEX_AI,
|
authType: AuthType.USE_VERTEX_AI,
|
||||||
}),
|
}),
|
||||||
@@ -867,6 +884,8 @@ describe('loggers', () => {
|
|||||||
getTelemetryEnabled: () => true,
|
getTelemetryEnabled: () => true,
|
||||||
getTelemetryLogPromptsEnabled: () => true,
|
getTelemetryLogPromptsEnabled: () => true,
|
||||||
isInteractive: () => false,
|
isInteractive: () => false,
|
||||||
|
getExperiments: () => undefined,
|
||||||
|
getExperimentsAsync: async () => undefined,
|
||||||
getUsageStatisticsEnabled: () => true,
|
getUsageStatisticsEnabled: () => true,
|
||||||
getContentGeneratorConfig: () => ({
|
getContentGeneratorConfig: () => ({
|
||||||
authType: AuthType.USE_GEMINI,
|
authType: AuthType.USE_GEMINI,
|
||||||
@@ -903,6 +922,8 @@ describe('loggers', () => {
|
|||||||
getSessionId: () => 'test-session-id',
|
getSessionId: () => 'test-session-id',
|
||||||
getUsageStatisticsEnabled: () => true,
|
getUsageStatisticsEnabled: () => true,
|
||||||
isInteractive: () => false,
|
isInteractive: () => false,
|
||||||
|
getExperiments: () => undefined,
|
||||||
|
getExperimentsAsync: async () => undefined,
|
||||||
} as unknown as Config;
|
} as unknown as Config;
|
||||||
|
|
||||||
it('should log flash fallback event', () => {
|
it('should log flash fallback event', () => {
|
||||||
@@ -930,6 +951,8 @@ describe('loggers', () => {
|
|||||||
getSessionId: () => 'test-session-id',
|
getSessionId: () => 'test-session-id',
|
||||||
getUsageStatisticsEnabled: () => true,
|
getUsageStatisticsEnabled: () => true,
|
||||||
isInteractive: () => false,
|
isInteractive: () => false,
|
||||||
|
getExperiments: () => undefined,
|
||||||
|
getExperimentsAsync: async () => undefined,
|
||||||
} as unknown as Config;
|
} as unknown as Config;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -1024,6 +1047,8 @@ describe('loggers', () => {
|
|||||||
getTelemetryEnabled: () => true,
|
getTelemetryEnabled: () => true,
|
||||||
getTelemetryLogPromptsEnabled: () => true,
|
getTelemetryLogPromptsEnabled: () => true,
|
||||||
isInteractive: () => false,
|
isInteractive: () => false,
|
||||||
|
getExperiments: () => undefined,
|
||||||
|
getExperimentsAsync: async () => undefined,
|
||||||
} as Config;
|
} as Config;
|
||||||
|
|
||||||
const mockMetrics = {
|
const mockMetrics = {
|
||||||
@@ -1595,6 +1620,8 @@ describe('loggers', () => {
|
|||||||
getTelemetryEnabled: () => true,
|
getTelemetryEnabled: () => true,
|
||||||
getTelemetryLogPromptsEnabled: () => true,
|
getTelemetryLogPromptsEnabled: () => true,
|
||||||
isInteractive: () => false,
|
isInteractive: () => false,
|
||||||
|
getExperiments: () => undefined,
|
||||||
|
getExperimentsAsync: async () => undefined,
|
||||||
} as Config;
|
} as Config;
|
||||||
|
|
||||||
const mockMetrics = {
|
const mockMetrics = {
|
||||||
@@ -1655,6 +1682,8 @@ describe('loggers', () => {
|
|||||||
getSessionId: () => 'test-session-id',
|
getSessionId: () => 'test-session-id',
|
||||||
getUsageStatisticsEnabled: () => true,
|
getUsageStatisticsEnabled: () => true,
|
||||||
isInteractive: () => false,
|
isInteractive: () => false,
|
||||||
|
getExperiments: () => undefined,
|
||||||
|
getExperimentsAsync: async () => undefined,
|
||||||
} as unknown as Config;
|
} as unknown as Config;
|
||||||
|
|
||||||
it('should log a tool output truncated event', () => {
|
it('should log a tool output truncated event', () => {
|
||||||
@@ -1692,6 +1721,8 @@ describe('loggers', () => {
|
|||||||
getSessionId: () => 'test-session-id',
|
getSessionId: () => 'test-session-id',
|
||||||
getUsageStatisticsEnabled: () => true,
|
getUsageStatisticsEnabled: () => true,
|
||||||
isInteractive: () => false,
|
isInteractive: () => false,
|
||||||
|
getExperiments: () => undefined,
|
||||||
|
getExperimentsAsync: async () => undefined,
|
||||||
} as unknown as Config;
|
} as unknown as Config;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -1792,6 +1823,8 @@ describe('loggers', () => {
|
|||||||
getUsageStatisticsEnabled: () => true,
|
getUsageStatisticsEnabled: () => true,
|
||||||
getContentGeneratorConfig: () => null,
|
getContentGeneratorConfig: () => null,
|
||||||
isInteractive: () => false,
|
isInteractive: () => false,
|
||||||
|
getExperiments: () => undefined,
|
||||||
|
getExperimentsAsync: async () => undefined,
|
||||||
} as unknown as Config;
|
} as unknown as Config;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -1842,6 +1875,8 @@ describe('loggers', () => {
|
|||||||
getUsageStatisticsEnabled: () => true,
|
getUsageStatisticsEnabled: () => true,
|
||||||
getContentGeneratorConfig: () => null,
|
getContentGeneratorConfig: () => null,
|
||||||
isInteractive: () => false,
|
isInteractive: () => false,
|
||||||
|
getExperiments: () => undefined,
|
||||||
|
getExperimentsAsync: async () => undefined,
|
||||||
} as unknown as Config;
|
} as unknown as Config;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -1894,6 +1929,8 @@ describe('loggers', () => {
|
|||||||
getUsageStatisticsEnabled: () => true,
|
getUsageStatisticsEnabled: () => true,
|
||||||
getContentGeneratorConfig: () => null,
|
getContentGeneratorConfig: () => null,
|
||||||
isInteractive: () => false,
|
isInteractive: () => false,
|
||||||
|
getExperiments: () => undefined,
|
||||||
|
getExperimentsAsync: async () => undefined,
|
||||||
} as unknown as Config;
|
} as unknown as Config;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -1938,6 +1975,8 @@ describe('loggers', () => {
|
|||||||
getSessionId: () => 'test-session-id',
|
getSessionId: () => 'test-session-id',
|
||||||
getUsageStatisticsEnabled: () => true,
|
getUsageStatisticsEnabled: () => true,
|
||||||
isInteractive: () => false,
|
isInteractive: () => false,
|
||||||
|
getExperiments: () => undefined,
|
||||||
|
getExperimentsAsync: async () => undefined,
|
||||||
} as unknown as Config;
|
} as unknown as Config;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -1983,6 +2022,8 @@ describe('loggers', () => {
|
|||||||
getSessionId: () => 'test-session-id',
|
getSessionId: () => 'test-session-id',
|
||||||
getUsageStatisticsEnabled: () => true,
|
getUsageStatisticsEnabled: () => true,
|
||||||
isInteractive: () => false,
|
isInteractive: () => false,
|
||||||
|
getExperiments: () => undefined,
|
||||||
|
getExperimentsAsync: async () => undefined,
|
||||||
} as unknown as Config;
|
} as unknown as Config;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -2028,6 +2069,8 @@ describe('loggers', () => {
|
|||||||
getSessionId: () => 'test-session-id',
|
getSessionId: () => 'test-session-id',
|
||||||
getUsageStatisticsEnabled: () => true,
|
getUsageStatisticsEnabled: () => true,
|
||||||
isInteractive: () => false,
|
isInteractive: () => false,
|
||||||
|
getExperiments: () => undefined,
|
||||||
|
getExperimentsAsync: async () => undefined,
|
||||||
} as unknown as Config;
|
} as unknown as Config;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -2064,6 +2107,8 @@ describe('loggers', () => {
|
|||||||
getSessionId: () => 'test-session-id',
|
getSessionId: () => 'test-session-id',
|
||||||
getUsageStatisticsEnabled: () => true,
|
getUsageStatisticsEnabled: () => true,
|
||||||
isInteractive: () => false,
|
isInteractive: () => false,
|
||||||
|
getExperiments: () => undefined,
|
||||||
|
getExperimentsAsync: async () => undefined,
|
||||||
} as unknown as Config;
|
} as unknown as Config;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -2115,6 +2160,8 @@ describe('loggers', () => {
|
|||||||
getSessionId: () => 'test-session-id',
|
getSessionId: () => 'test-session-id',
|
||||||
getUsageStatisticsEnabled: () => true,
|
getUsageStatisticsEnabled: () => true,
|
||||||
isInteractive: () => false,
|
isInteractive: () => false,
|
||||||
|
getExperiments: () => undefined,
|
||||||
|
getExperimentsAsync: async () => undefined,
|
||||||
} as unknown as Config;
|
} as unknown as Config;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -2150,6 +2197,8 @@ describe('loggers', () => {
|
|||||||
getSessionId: () => 'test-session-id',
|
getSessionId: () => 'test-session-id',
|
||||||
getUsageStatisticsEnabled: () => true,
|
getUsageStatisticsEnabled: () => true,
|
||||||
isInteractive: () => false,
|
isInteractive: () => false,
|
||||||
|
getExperiments: () => undefined,
|
||||||
|
getExperimentsAsync: async () => undefined,
|
||||||
getTelemetryLogPromptsEnabled: () => false,
|
getTelemetryLogPromptsEnabled: () => false,
|
||||||
} as unknown as Config;
|
} as unknown as Config;
|
||||||
|
|
||||||
@@ -2205,7 +2254,7 @@ describe('loggers', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Telemetry Buffering', () => {
|
describe('Telemetry Buffering', () => {
|
||||||
it('should buffer events when SDK is not initialized', () => {
|
it('should buffer events when SDK is not initialized', async () => {
|
||||||
vi.spyOn(sdk, 'isTelemetrySdkInitialized').mockReturnValue(false);
|
vi.spyOn(sdk, 'isTelemetrySdkInitialized').mockReturnValue(false);
|
||||||
const bufferSpy = vi
|
const bufferSpy = vi
|
||||||
.spyOn(sdk, 'bufferTelemetryEvent')
|
.spyOn(sdk, 'bufferTelemetryEvent')
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ import { bufferTelemetryEvent } from './sdk.js';
|
|||||||
import type { UiEvent } from './uiTelemetry.js';
|
import type { UiEvent } from './uiTelemetry.js';
|
||||||
import { uiTelemetryService } from './uiTelemetry.js';
|
import { uiTelemetryService } from './uiTelemetry.js';
|
||||||
import { ClearcutLogger } from './clearcut-logger/clearcut-logger.js';
|
import { ClearcutLogger } from './clearcut-logger/clearcut-logger.js';
|
||||||
|
import { debugLogger } from '../utils/debugLogger.js';
|
||||||
|
|
||||||
export function logCliConfiguration(
|
export function logCliConfiguration(
|
||||||
config: Config,
|
config: Config,
|
||||||
@@ -88,12 +89,20 @@ export function logCliConfiguration(
|
|||||||
): void {
|
): void {
|
||||||
void ClearcutLogger.getInstance(config)?.logStartSessionEvent(event);
|
void ClearcutLogger.getInstance(config)?.logStartSessionEvent(event);
|
||||||
bufferTelemetryEvent(() => {
|
bufferTelemetryEvent(() => {
|
||||||
|
// Wait for experiments to load before emitting so we capture experimentIds
|
||||||
|
void config
|
||||||
|
.getExperimentsAsync()
|
||||||
|
.then(() => {
|
||||||
const logger = logs.getLogger(SERVICE_NAME);
|
const logger = logs.getLogger(SERVICE_NAME);
|
||||||
const logRecord: LogRecord = {
|
const logRecord: LogRecord = {
|
||||||
body: event.toLogBody(),
|
body: event.toLogBody(),
|
||||||
attributes: event.toOpenTelemetryAttributes(config),
|
attributes: event.toOpenTelemetryAttributes(config),
|
||||||
};
|
};
|
||||||
logger.emit(logRecord);
|
logger.emit(logRecord);
|
||||||
|
})
|
||||||
|
.catch((e: unknown) => {
|
||||||
|
debugLogger.error('Failed to log telemetry event', e);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -780,11 +789,19 @@ export function logStartupStats(
|
|||||||
event: StartupStatsEvent,
|
event: StartupStatsEvent,
|
||||||
): void {
|
): void {
|
||||||
bufferTelemetryEvent(() => {
|
bufferTelemetryEvent(() => {
|
||||||
|
// Wait for experiments to load before emitting so we capture experimentIds
|
||||||
|
void config
|
||||||
|
.getExperimentsAsync()
|
||||||
|
.then(() => {
|
||||||
const logger = logs.getLogger(SERVICE_NAME);
|
const logger = logs.getLogger(SERVICE_NAME);
|
||||||
const logRecord: LogRecord = {
|
const logRecord: LogRecord = {
|
||||||
body: event.toLogBody(),
|
body: event.toLogBody(),
|
||||||
attributes: event.toOpenTelemetryAttributes(config),
|
attributes: event.toOpenTelemetryAttributes(config),
|
||||||
};
|
};
|
||||||
logger.emit(logRecord);
|
logger.emit(logRecord);
|
||||||
|
})
|
||||||
|
.catch((e: unknown) => {
|
||||||
|
debugLogger.error('Failed to log telemetry event', e);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ function createMockConfig(logPromptsEnabled: boolean): Config {
|
|||||||
return {
|
return {
|
||||||
getTelemetryLogPromptsEnabled: () => logPromptsEnabled,
|
getTelemetryLogPromptsEnabled: () => logPromptsEnabled,
|
||||||
getSessionId: () => 'test-session-id',
|
getSessionId: () => 'test-session-id',
|
||||||
|
getExperiments: () => undefined,
|
||||||
|
getExperimentsAsync: async () => undefined,
|
||||||
getModel: () => 'gemini-1.5-flash',
|
getModel: () => 'gemini-1.5-flash',
|
||||||
isInteractive: () => true,
|
isInteractive: () => true,
|
||||||
getUserEmail: () => undefined,
|
getUserEmail: () => undefined,
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ describe('Telemetry SDK', () => {
|
|||||||
getSessionId: () => 'test-session',
|
getSessionId: () => 'test-session',
|
||||||
getTelemetryUseCliAuth: () => false,
|
getTelemetryUseCliAuth: () => false,
|
||||||
isInteractive: () => false,
|
isInteractive: () => false,
|
||||||
|
getExperiments: () => undefined,
|
||||||
|
getExperimentsAsync: async () => undefined,
|
||||||
} as unknown as Config;
|
} as unknown as Config;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -353,6 +355,7 @@ describe('Telemetry SDK', () => {
|
|||||||
expect(callback).not.toHaveBeenCalled();
|
expect(callback).not.toHaveBeenCalled();
|
||||||
|
|
||||||
await initializeTelemetry(mockConfig);
|
await initializeTelemetry(mockConfig);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||||
expect(callback).toHaveBeenCalled();
|
expect(callback).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,10 +14,15 @@ const installationManager = new InstallationManager();
|
|||||||
|
|
||||||
export function getCommonAttributes(config: Config): Attributes {
|
export function getCommonAttributes(config: Config): Attributes {
|
||||||
const email = userAccountManager.getCachedGoogleAccount();
|
const email = userAccountManager.getCachedGoogleAccount();
|
||||||
|
const experiments = config.getExperiments();
|
||||||
return {
|
return {
|
||||||
'session.id': config.getSessionId(),
|
'session.id': config.getSessionId(),
|
||||||
'installation.id': installationManager.getInstallationId(),
|
'installation.id': installationManager.getInstallationId(),
|
||||||
interactive: config.isInteractive(),
|
interactive: config.isInteractive(),
|
||||||
...(email && { 'user.email': email }),
|
...(email && { 'user.email': email }),
|
||||||
|
...(experiments &&
|
||||||
|
experiments.experimentIds.length > 0 && {
|
||||||
|
'experiments.ids': experiments.experimentIds,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user