feat(telemetry): Ensure experiment IDs are included in OpenTelemetry logs (#18747)

This commit is contained in:
Kevin Ramdass
2026-02-10 13:01:35 -08:00
committed by GitHub
parent 8b762111a8
commit a2174751de
5 changed files with 90 additions and 14 deletions
+51 -2
View File
@@ -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')
+17
View File
@@ -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,
+3
View File
@@ -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,
}),
}; };
} }