mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-23 03:24:42 -07:00
feat(telemetry): add flag for enabling traces specifically (#25343)
This commit is contained in:
@@ -194,6 +194,7 @@ class DelegateInvocation extends BaseToolInvocation<
|
||||
{
|
||||
operation: GeminiCliOperation.AgentCall,
|
||||
logPrompts: this.context.config.getTelemetryLogPromptsEnabled(),
|
||||
tracesEnabled: this.context.config.getTelemetryTracesEnabled(),
|
||||
sessionId: this.context.config.getSessionId(),
|
||||
attributes: {
|
||||
[GEN_AI_AGENT_NAME]: this.definition.name,
|
||||
|
||||
@@ -205,6 +205,7 @@ export interface PlanSettings {
|
||||
|
||||
export interface TelemetrySettings {
|
||||
enabled?: boolean;
|
||||
traces?: boolean;
|
||||
target?: TelemetryTarget;
|
||||
otlpEndpoint?: string;
|
||||
otlpProtocol?: 'grpc' | 'http';
|
||||
@@ -1061,6 +1062,7 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
this.accessibility = params.accessibility ?? {};
|
||||
this.telemetrySettings = {
|
||||
enabled: params.telemetry?.enabled ?? false,
|
||||
traces: params.telemetry?.traces ?? false,
|
||||
target: params.telemetry?.target ?? DEFAULT_TELEMETRY_TARGET,
|
||||
otlpEndpoint: params.telemetry?.otlpEndpoint ?? DEFAULT_OTLP_ENDPOINT,
|
||||
otlpProtocol: params.telemetry?.otlpProtocol,
|
||||
@@ -2732,6 +2734,10 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
return this.telemetrySettings.enabled ?? false;
|
||||
}
|
||||
|
||||
getTelemetryTracesEnabled(): boolean {
|
||||
return this.telemetrySettings.traces ?? false;
|
||||
}
|
||||
|
||||
getTelemetryLogPromptsEnabled(): boolean {
|
||||
return this.telemetrySettings.logPrompts ?? true;
|
||||
}
|
||||
|
||||
@@ -153,6 +153,7 @@ describe('GeminiChat', () => {
|
||||
promptId: 'test-session-id',
|
||||
getSessionId: () => 'test-session-id',
|
||||
getTelemetryLogPromptsEnabled: () => true,
|
||||
getTelemetryTracesEnabled: () => false,
|
||||
getUsageStatisticsEnabled: () => true,
|
||||
getDebugMode: () => false,
|
||||
getContentGeneratorConfig: vi.fn().mockImplementation(() => ({
|
||||
|
||||
@@ -96,6 +96,7 @@ describe('GeminiChat Network Retries', () => {
|
||||
promptId: 'test-session-id',
|
||||
getSessionId: () => 'test-session-id',
|
||||
getTelemetryLogPromptsEnabled: () => true,
|
||||
getTelemetryTracesEnabled: () => false,
|
||||
getUsageStatisticsEnabled: () => true,
|
||||
getDebugMode: () => false,
|
||||
getContentGeneratorConfig: vi.fn().mockReturnValue({
|
||||
|
||||
@@ -73,6 +73,7 @@ describe('LoggingContentGenerator', () => {
|
||||
authType: 'API_KEY',
|
||||
}),
|
||||
getTelemetryLogPromptsEnabled: vi.fn().mockReturnValue(true),
|
||||
getTelemetryTracesEnabled: vi.fn().mockReturnValue(false),
|
||||
refreshUserQuotaIfStale: vi.fn().mockResolvedValue(undefined),
|
||||
getSessionId: vi.fn().mockReturnValue('test-session-id'),
|
||||
} as unknown as Config;
|
||||
|
||||
@@ -361,6 +361,7 @@ export class LoggingContentGenerator implements ContentGenerator {
|
||||
{
|
||||
operation: GeminiCliOperation.LLMCall,
|
||||
logPrompts: this.config.getTelemetryLogPromptsEnabled(),
|
||||
tracesEnabled: this.config.getTelemetryTracesEnabled(),
|
||||
sessionId: this.config.getSessionId(),
|
||||
attributes: {
|
||||
[GEN_AI_REQUEST_MODEL]: req.model,
|
||||
@@ -452,6 +453,7 @@ export class LoggingContentGenerator implements ContentGenerator {
|
||||
{
|
||||
operation: GeminiCliOperation.LLMCall,
|
||||
logPrompts: this.config.getTelemetryLogPromptsEnabled(),
|
||||
tracesEnabled: this.config.getTelemetryTracesEnabled(),
|
||||
sessionId: this.config.getSessionId(),
|
||||
attributes: {
|
||||
[GEN_AI_REQUEST_MODEL]: req.model,
|
||||
@@ -607,6 +609,7 @@ export class LoggingContentGenerator implements ContentGenerator {
|
||||
{
|
||||
operation: GeminiCliOperation.LLMCall,
|
||||
logPrompts: this.config.getTelemetryLogPromptsEnabled(),
|
||||
tracesEnabled: this.config.getTelemetryTracesEnabled(),
|
||||
sessionId: this.config.getSessionId(),
|
||||
attributes: {
|
||||
[GEN_AI_REQUEST_MODEL]: req.model,
|
||||
|
||||
@@ -905,6 +905,7 @@ describe('Plan Mode Denial Consistency', () => {
|
||||
getEnableHooks: vi.fn().mockReturnValue(false),
|
||||
getApprovalMode: vi.fn().mockReturnValue(ApprovalMode.PLAN), // Key: Plan Mode
|
||||
getTelemetryLogPromptsEnabled: vi.fn().mockReturnValue(false),
|
||||
getTelemetryTracesEnabled: vi.fn().mockReturnValue(false),
|
||||
setApprovalMode: vi.fn(),
|
||||
getSessionId: vi.fn().mockReturnValue('test-session-id'),
|
||||
getUsageStatisticsEnabled: vi.fn().mockReturnValue(false),
|
||||
|
||||
@@ -178,6 +178,7 @@ describe('Scheduler (Orchestrator)', () => {
|
||||
setApprovalMode: vi.fn(),
|
||||
getApprovalMode: vi.fn().mockReturnValue(ApprovalMode.DEFAULT),
|
||||
getTelemetryLogPromptsEnabled: vi.fn().mockReturnValue(false),
|
||||
getTelemetryTracesEnabled: vi.fn().mockReturnValue(false),
|
||||
getSessionId: vi.fn().mockReturnValue('test-session-id'),
|
||||
} as unknown as Mocked<Config>;
|
||||
|
||||
@@ -1517,6 +1518,7 @@ describe('Scheduler MCP Progress', () => {
|
||||
setApprovalMode: vi.fn(),
|
||||
getApprovalMode: vi.fn().mockReturnValue(ApprovalMode.DEFAULT),
|
||||
getTelemetryLogPromptsEnabled: vi.fn().mockReturnValue(false),
|
||||
getTelemetryTracesEnabled: vi.fn().mockReturnValue(false),
|
||||
getSessionId: vi.fn().mockReturnValue('test-session-id'),
|
||||
} as unknown as Mocked<Config>;
|
||||
|
||||
|
||||
@@ -196,6 +196,7 @@ export class Scheduler {
|
||||
{
|
||||
operation: GeminiCliOperation.ScheduleToolCalls,
|
||||
logPrompts: this.context.config.getTelemetryLogPromptsEnabled(),
|
||||
tracesEnabled: this.context.config.getTelemetryTracesEnabled(),
|
||||
sessionId: this.context.config.getSessionId(),
|
||||
},
|
||||
async ({ metadata: spanMetadata }) => {
|
||||
|
||||
@@ -71,6 +71,7 @@ function createMockConfig(overrides: Partial<Config> = {}): Config {
|
||||
getEnableHooks: () => true,
|
||||
getExperiments: () => {},
|
||||
getTelemetryLogPromptsEnabled: () => false,
|
||||
getTelemetryTracesEnabled: () => false,
|
||||
getPolicyEngine: () =>
|
||||
({
|
||||
check: async () => ({ decision: 'allow' }),
|
||||
|
||||
@@ -218,6 +218,7 @@ describe('Scheduler Parallel Execution', () => {
|
||||
setApprovalMode: vi.fn(),
|
||||
getApprovalMode: vi.fn().mockReturnValue(ApprovalMode.DEFAULT),
|
||||
getTelemetryLogPromptsEnabled: vi.fn().mockReturnValue(false),
|
||||
getTelemetryTracesEnabled: vi.fn().mockReturnValue(false),
|
||||
getSessionId: vi.fn().mockReturnValue('test-session-id'),
|
||||
} as unknown as Mocked<Config>;
|
||||
|
||||
|
||||
@@ -84,6 +84,7 @@ export class ToolExecutor {
|
||||
{
|
||||
operation: GeminiCliOperation.ToolCall,
|
||||
logPrompts: this.config.getTelemetryLogPromptsEnabled(),
|
||||
tracesEnabled: this.config.getTelemetryTracesEnabled(),
|
||||
sessionId: this.config.getSessionId(),
|
||||
attributes: {
|
||||
[GEN_AI_TOOL_NAME]: toolName,
|
||||
|
||||
@@ -60,6 +60,10 @@ export async function resolveTelemetrySettings(options: {
|
||||
parseBooleanEnvFlag(env['GEMINI_TELEMETRY_ENABLED']) ??
|
||||
settings.enabled;
|
||||
|
||||
const traces =
|
||||
parseBooleanEnvFlag(env['GEMINI_TELEMETRY_TRACES_ENABLED']) ??
|
||||
settings.traces;
|
||||
|
||||
const rawTarget =
|
||||
argv.telemetryTarget ??
|
||||
env['GEMINI_TELEMETRY_TARGET'] ??
|
||||
@@ -110,6 +114,7 @@ export async function resolveTelemetrySettings(options: {
|
||||
|
||||
return {
|
||||
enabled,
|
||||
traces,
|
||||
target,
|
||||
otlpEndpoint,
|
||||
otlpProtocol,
|
||||
|
||||
@@ -37,6 +37,7 @@ describe('conseca-logger', () => {
|
||||
getTelemetryEnabled: vi.fn().mockReturnValue(true),
|
||||
getSessionId: vi.fn().mockReturnValue('test-session-id'),
|
||||
getTelemetryLogPromptsEnabled: vi.fn().mockReturnValue(true),
|
||||
getTelemetryTracesEnabled: vi.fn().mockReturnValue(false),
|
||||
isInteractive: vi.fn().mockReturnValue(true),
|
||||
getExperiments: vi.fn().mockReturnValue({ experimentIds: [] }),
|
||||
getContentGeneratorConfig: vi.fn().mockReturnValue({ authType: 'oauth' }),
|
||||
|
||||
@@ -216,6 +216,7 @@ describe('loggers', () => {
|
||||
getTelemetryEnabled: () => true,
|
||||
getUsageStatisticsEnabled: () => true,
|
||||
getTelemetryLogPromptsEnabled: () => true,
|
||||
getTelemetryTracesEnabled: () => false,
|
||||
getFileFilteringRespectGitIgnore: () => true,
|
||||
getFileFilteringAllowBuildArtifacts: () => false,
|
||||
getDebugMode: () => true,
|
||||
@@ -313,6 +314,7 @@ describe('loggers', () => {
|
||||
getSessionId: () => 'test-session-id',
|
||||
getTelemetryEnabled: () => true,
|
||||
getTelemetryLogPromptsEnabled: () => true,
|
||||
getTelemetryTracesEnabled: () => false,
|
||||
getUsageStatisticsEnabled: () => true,
|
||||
isInteractive: () => false,
|
||||
getExperiments: () => undefined,
|
||||
@@ -352,6 +354,7 @@ describe('loggers', () => {
|
||||
getSessionId: () => 'test-session-id',
|
||||
getTelemetryEnabled: () => true,
|
||||
getTelemetryLogPromptsEnabled: () => false,
|
||||
getTelemetryTracesEnabled: () => false,
|
||||
getTargetDir: () => 'target-dir',
|
||||
getUsageStatisticsEnabled: () => true,
|
||||
isInteractive: () => false,
|
||||
@@ -392,6 +395,7 @@ describe('loggers', () => {
|
||||
getUsageStatisticsEnabled: () => true,
|
||||
getTelemetryEnabled: () => true,
|
||||
getTelemetryLogPromptsEnabled: () => true,
|
||||
getTelemetryTracesEnabled: () => true,
|
||||
isInteractive: () => false,
|
||||
getExperiments: () => undefined,
|
||||
getExperimentsAsync: async () => undefined,
|
||||
@@ -493,10 +497,10 @@ describe('loggers', () => {
|
||||
'gen_ai.output.messages':
|
||||
'[{"finish_reason":"stop","role":"system","parts":[{"type":"text","content":"candidate 1"}]}]',
|
||||
'gen_ai.response.finish_reasons': ['stop'],
|
||||
'gen_ai.operation.name': 'generate_content',
|
||||
'gen_ai.response.model': 'test-model',
|
||||
'gen_ai.usage.input_tokens': 17,
|
||||
'gen_ai.usage.output_tokens': 50,
|
||||
'gen_ai.operation.name': 'generate_content',
|
||||
'gen_ai.output.type': 'text',
|
||||
'gen_ai.request.choice.count': 1,
|
||||
'gen_ai.request.seed': 678,
|
||||
@@ -564,6 +568,57 @@ describe('loggers', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should not log input and output messages when traces are disabled', () => {
|
||||
const mockConfigNoTraces = {
|
||||
getSessionId: () => 'test-session-id',
|
||||
getTargetDir: () => 'target-dir',
|
||||
getUsageStatisticsEnabled: () => true,
|
||||
getTelemetryEnabled: () => true,
|
||||
getTelemetryLogPromptsEnabled: () => true,
|
||||
getTelemetryTracesEnabled: () => false, // Disabled
|
||||
isInteractive: () => false,
|
||||
getExperiments: () => undefined,
|
||||
getExperimentsAsync: async () => undefined,
|
||||
getContentGeneratorConfig: () => undefined,
|
||||
} as unknown as Config;
|
||||
|
||||
const event = new ApiResponseEvent(
|
||||
'test-model',
|
||||
100,
|
||||
{ prompt_id: 'prompt-id-1', contents: [] },
|
||||
{ candidates: [] },
|
||||
AuthType.LOGIN_WITH_GOOGLE,
|
||||
undefined,
|
||||
'test-response',
|
||||
);
|
||||
|
||||
logApiResponse(mockConfigNoTraces, event);
|
||||
|
||||
expect(mockLogger.emit).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
body: 'GenAI operation details from test-model. Status: 200. Duration: 100ms.',
|
||||
attributes: expect.objectContaining({
|
||||
'event.name': 'gen_ai.client.inference.operation.details',
|
||||
'gen_ai.operation.name': 'generate_content',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
const emitCalls = mockLogger.emit.mock.calls;
|
||||
const detailsCall = emitCalls.find(
|
||||
(call) =>
|
||||
call[0].attributes &&
|
||||
call[0].attributes['event.name'] ===
|
||||
'gen_ai.client.inference.operation.details',
|
||||
);
|
||||
expect(
|
||||
detailsCall![0].attributes['gen_ai.input.messages'],
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
detailsCall![0].attributes['gen_ai.output.messages'],
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should log an API response with a role', () => {
|
||||
const event = new ApiResponseEvent(
|
||||
'test-model',
|
||||
@@ -596,6 +651,7 @@ describe('loggers', () => {
|
||||
getUsageStatisticsEnabled: () => true,
|
||||
getTelemetryEnabled: () => true,
|
||||
getTelemetryLogPromptsEnabled: () => true,
|
||||
getTelemetryTracesEnabled: () => true,
|
||||
isInteractive: () => false,
|
||||
getExperiments: () => undefined,
|
||||
getExperimentsAsync: async () => undefined,
|
||||
@@ -674,8 +730,6 @@ describe('loggers', () => {
|
||||
'gen_ai.request.temperature': 1,
|
||||
'gen_ai.request.top_p': 2,
|
||||
'gen_ai.request.top_k': 3,
|
||||
'gen_ai.input.messages':
|
||||
'[{"role":"user","parts":[{"type":"text","content":"Hello"}]}]',
|
||||
'gen_ai.operation.name': 'generate_content',
|
||||
'gen_ai.output.type': 'text',
|
||||
'gen_ai.request.choice.count': 1,
|
||||
@@ -683,6 +737,8 @@ describe('loggers', () => {
|
||||
'gen_ai.request.frequency_penalty': 10,
|
||||
'gen_ai.request.presence_penalty': 6,
|
||||
'gen_ai.request.max_tokens': 8000,
|
||||
'gen_ai.input.messages':
|
||||
'[{"role":"user","parts":[{"type":"text","content":"Hello"}]}]',
|
||||
'server.address': 'foo.com',
|
||||
'server.port': 8080,
|
||||
'gen_ai.request.stop_sequences': ['stop', 'please stop'],
|
||||
@@ -724,6 +780,52 @@ describe('loggers', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should not log input messages when traces are disabled', () => {
|
||||
const mockConfigNoTraces = {
|
||||
getSessionId: () => 'test-session-id',
|
||||
getTargetDir: () => 'target-dir',
|
||||
getUsageStatisticsEnabled: () => true,
|
||||
getTelemetryEnabled: () => true,
|
||||
getTelemetryLogPromptsEnabled: () => true,
|
||||
getTelemetryTracesEnabled: () => false, // Disabled
|
||||
isInteractive: () => false,
|
||||
getExperiments: () => undefined,
|
||||
getExperimentsAsync: async () => undefined,
|
||||
getContentGeneratorConfig: () => undefined,
|
||||
} as unknown as Config;
|
||||
|
||||
const event = new ApiErrorEvent(
|
||||
'test-model',
|
||||
'error',
|
||||
100,
|
||||
{ prompt_id: 'prompt-id-1', contents: [] },
|
||||
AuthType.LOGIN_WITH_GOOGLE,
|
||||
'ApiError',
|
||||
500,
|
||||
);
|
||||
|
||||
logApiError(mockConfigNoTraces, event);
|
||||
|
||||
expect(mockLogger.emit).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
attributes: expect.objectContaining({
|
||||
'event.name': 'gen_ai.client.inference.operation.details',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
const emitCalls = mockLogger.emit.mock.calls;
|
||||
const detailsCall = emitCalls.find(
|
||||
(call) =>
|
||||
call[0].attributes &&
|
||||
call[0].attributes['event.name'] ===
|
||||
'gen_ai.client.inference.operation.details',
|
||||
);
|
||||
expect(
|
||||
detailsCall![0].attributes['gen_ai.input.messages'],
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should log an API error with a role', () => {
|
||||
const event = new ApiErrorEvent(
|
||||
'test-model',
|
||||
@@ -756,6 +858,7 @@ describe('loggers', () => {
|
||||
getUsageStatisticsEnabled: () => true,
|
||||
getTelemetryEnabled: () => true,
|
||||
getTelemetryLogPromptsEnabled: () => true,
|
||||
getTelemetryTracesEnabled: () => false,
|
||||
isInteractive: () => false,
|
||||
getExperiments: () => undefined,
|
||||
getExperimentsAsync: async () => undefined,
|
||||
@@ -833,7 +936,8 @@ describe('loggers', () => {
|
||||
getTargetDir: () => 'target-dir',
|
||||
getUsageStatisticsEnabled: () => true,
|
||||
getTelemetryEnabled: () => true,
|
||||
getTelemetryLogPromptsEnabled: () => true, // Enabled
|
||||
getTelemetryLogPromptsEnabled: () => true,
|
||||
getTelemetryTracesEnabled: () => true, // Enabled
|
||||
isInteractive: () => false,
|
||||
getExperiments: () => undefined,
|
||||
getExperimentsAsync: async () => undefined,
|
||||
@@ -922,7 +1026,8 @@ describe('loggers', () => {
|
||||
getTargetDir: () => 'target-dir',
|
||||
getUsageStatisticsEnabled: () => true,
|
||||
getTelemetryEnabled: () => true,
|
||||
getTelemetryLogPromptsEnabled: () => false, // Disabled
|
||||
getTelemetryLogPromptsEnabled: () => false,
|
||||
getTelemetryTracesEnabled: () => false, // Disabled
|
||||
isInteractive: () => false,
|
||||
getExperiments: () => undefined,
|
||||
getExperimentsAsync: async () => undefined,
|
||||
@@ -978,6 +1083,7 @@ describe('loggers', () => {
|
||||
getSessionId: () => 'test-session-id',
|
||||
getTelemetryEnabled: () => true,
|
||||
getTelemetryLogPromptsEnabled: () => true,
|
||||
getTelemetryTracesEnabled: () => false,
|
||||
isInteractive: () => false,
|
||||
getExperiments: () => undefined,
|
||||
getExperimentsAsync: async () => undefined,
|
||||
@@ -1140,6 +1246,7 @@ describe('loggers', () => {
|
||||
getCoreTools: () => ['ls', 'read-file'],
|
||||
getApprovalMode: () => 'default',
|
||||
getTelemetryLogPromptsEnabled: () => true,
|
||||
getTelemetryTracesEnabled: () => false,
|
||||
getFileFilteringRespectGitIgnore: () => true,
|
||||
getFileFilteringAllowBuildArtifacts: () => false,
|
||||
getDebugMode: () => true,
|
||||
@@ -1170,6 +1277,7 @@ describe('loggers', () => {
|
||||
getUsageStatisticsEnabled: () => true,
|
||||
getTelemetryEnabled: () => true,
|
||||
getTelemetryLogPromptsEnabled: () => true,
|
||||
getTelemetryTracesEnabled: () => false,
|
||||
isInteractive: () => false,
|
||||
getExperiments: () => undefined,
|
||||
getExperimentsAsync: async () => undefined,
|
||||
@@ -1829,6 +1937,7 @@ describe('loggers', () => {
|
||||
getUsageStatisticsEnabled: () => true,
|
||||
getTelemetryEnabled: () => true,
|
||||
getTelemetryLogPromptsEnabled: () => true,
|
||||
getTelemetryTracesEnabled: () => false,
|
||||
isInteractive: () => false,
|
||||
getExperiments: () => undefined,
|
||||
getExperimentsAsync: async () => undefined,
|
||||
@@ -2423,6 +2532,7 @@ describe('loggers', () => {
|
||||
getExperiments: () => undefined,
|
||||
getExperimentsAsync: async () => undefined,
|
||||
getTelemetryLogPromptsEnabled: () => false,
|
||||
getTelemetryTracesEnabled: () => false,
|
||||
getContentGeneratorConfig: () => undefined,
|
||||
} as unknown as Config;
|
||||
|
||||
|
||||
@@ -115,7 +115,11 @@ describe('runInDevTraceSpan', () => {
|
||||
const fn = vi.fn(async () => 'result');
|
||||
|
||||
const result = await runInDevTraceSpan(
|
||||
{ operation: GeminiCliOperation.LLMCall, sessionId: 'test-session-id' },
|
||||
{
|
||||
operation: GeminiCliOperation.LLMCall,
|
||||
sessionId: 'test-session-id',
|
||||
tracesEnabled: true,
|
||||
},
|
||||
fn,
|
||||
);
|
||||
|
||||
@@ -123,14 +127,22 @@ describe('runInDevTraceSpan', () => {
|
||||
expect(trace.getTracer).toHaveBeenCalled();
|
||||
expect(mockTracer.startActiveSpan).toHaveBeenCalledWith(
|
||||
GeminiCliOperation.LLMCall,
|
||||
{},
|
||||
{
|
||||
attributes: {
|
||||
[GEN_AI_CONVERSATION_ID]: 'test-session-id',
|
||||
},
|
||||
},
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
it('should set default attributes on the span metadata', async () => {
|
||||
await runInDevTraceSpan(
|
||||
{ operation: GeminiCliOperation.LLMCall, sessionId: 'test-session-id' },
|
||||
{
|
||||
operation: GeminiCliOperation.LLMCall,
|
||||
sessionId: 'test-session-id',
|
||||
tracesEnabled: true,
|
||||
},
|
||||
async ({ metadata }) => {
|
||||
expect(metadata.attributes[GEN_AI_OPERATION_NAME]).toBe(
|
||||
GeminiCliOperation.LLMCall,
|
||||
@@ -148,7 +160,11 @@ describe('runInDevTraceSpan', () => {
|
||||
|
||||
it('should set span attributes from metadata on completion', async () => {
|
||||
await runInDevTraceSpan(
|
||||
{ operation: GeminiCliOperation.LLMCall, sessionId: 'test-session-id' },
|
||||
{
|
||||
operation: GeminiCliOperation.LLMCall,
|
||||
sessionId: 'test-session-id',
|
||||
tracesEnabled: true,
|
||||
},
|
||||
async ({ metadata }) => {
|
||||
metadata.input = { query: 'hello' };
|
||||
metadata.output = { response: 'world' };
|
||||
@@ -175,7 +191,11 @@ describe('runInDevTraceSpan', () => {
|
||||
const error = new Error('test error');
|
||||
await expect(
|
||||
runInDevTraceSpan(
|
||||
{ operation: GeminiCliOperation.LLMCall, sessionId: 'test-session-id' },
|
||||
{
|
||||
operation: GeminiCliOperation.LLMCall,
|
||||
sessionId: 'test-session-id',
|
||||
tracesEnabled: true,
|
||||
},
|
||||
async () => {
|
||||
throw error;
|
||||
},
|
||||
@@ -197,7 +217,11 @@ describe('runInDevTraceSpan', () => {
|
||||
}
|
||||
|
||||
const resultStream = await runInDevTraceSpan(
|
||||
{ operation: GeminiCliOperation.LLMCall, sessionId: 'test-session-id' },
|
||||
{
|
||||
operation: GeminiCliOperation.LLMCall,
|
||||
sessionId: 'test-session-id',
|
||||
tracesEnabled: true,
|
||||
},
|
||||
async () => testStream(),
|
||||
);
|
||||
|
||||
@@ -219,7 +243,11 @@ describe('runInDevTraceSpan', () => {
|
||||
}
|
||||
|
||||
const resultStream = await runInDevTraceSpan(
|
||||
{ operation: GeminiCliOperation.LLMCall, sessionId: 'test-session-id' },
|
||||
{
|
||||
operation: GeminiCliOperation.LLMCall,
|
||||
sessionId: 'test-session-id',
|
||||
tracesEnabled: true,
|
||||
},
|
||||
async () => testStream(),
|
||||
);
|
||||
|
||||
@@ -233,7 +261,11 @@ describe('runInDevTraceSpan', () => {
|
||||
}
|
||||
|
||||
const resultStream = await runInDevTraceSpan(
|
||||
{ operation: GeminiCliOperation.LLMCall, sessionId: 'test-session-id' },
|
||||
{
|
||||
operation: GeminiCliOperation.LLMCall,
|
||||
sessionId: 'test-session-id',
|
||||
tracesEnabled: true,
|
||||
},
|
||||
async () => testStream(),
|
||||
);
|
||||
|
||||
@@ -259,7 +291,11 @@ describe('runInDevTraceSpan', () => {
|
||||
}
|
||||
|
||||
const resultStream = await runInDevTraceSpan(
|
||||
{ operation: GeminiCliOperation.LLMCall, sessionId: 'test-session-id' },
|
||||
{
|
||||
operation: GeminiCliOperation.LLMCall,
|
||||
sessionId: 'test-session-id',
|
||||
tracesEnabled: true,
|
||||
},
|
||||
async () => errorStream(),
|
||||
);
|
||||
|
||||
@@ -278,7 +314,11 @@ describe('runInDevTraceSpan', () => {
|
||||
});
|
||||
|
||||
await runInDevTraceSpan(
|
||||
{ operation: GeminiCliOperation.LLMCall, sessionId: 'test-session-id' },
|
||||
{
|
||||
operation: GeminiCliOperation.LLMCall,
|
||||
sessionId: 'test-session-id',
|
||||
tracesEnabled: true,
|
||||
},
|
||||
async ({ metadata }) => {
|
||||
metadata.input = 'trigger error';
|
||||
},
|
||||
|
||||
@@ -125,10 +125,17 @@ export async function runInDevTraceSpan<R>(
|
||||
operation: GeminiCliOperation;
|
||||
logPrompts?: boolean;
|
||||
sessionId: string;
|
||||
tracesEnabled?: boolean;
|
||||
},
|
||||
fn: ({ metadata }: { metadata: SpanMetadata }) => Promise<R>,
|
||||
): Promise<R> {
|
||||
const { operation, logPrompts, sessionId, ...restOfSpanOpts } = opts;
|
||||
const { operation, logPrompts, sessionId, tracesEnabled, ...restOfSpanOpts } =
|
||||
opts;
|
||||
|
||||
restOfSpanOpts.attributes = {
|
||||
...restOfSpanOpts.attributes,
|
||||
[GEN_AI_CONVERSATION_ID]: sessionId,
|
||||
};
|
||||
|
||||
const tracer = trace.getTracer(TRACER_NAME, TRACER_VERSION);
|
||||
return tracer.startActiveSpan(operation, restOfSpanOpts, async (span) => {
|
||||
@@ -148,24 +155,41 @@ export async function runInDevTraceSpan<R>(
|
||||
}
|
||||
spanEnded = true;
|
||||
try {
|
||||
if (logPrompts !== false) {
|
||||
if (meta.input !== undefined) {
|
||||
const truncated = truncateForTelemetry(meta.input);
|
||||
if (truncated !== undefined) {
|
||||
span.setAttribute(GEN_AI_INPUT_MESSAGES, truncated);
|
||||
if (tracesEnabled) {
|
||||
if (logPrompts !== false) {
|
||||
if (meta.input !== undefined) {
|
||||
const truncated = truncateForTelemetry(meta.input);
|
||||
if (truncated !== undefined) {
|
||||
span.setAttribute(GEN_AI_INPUT_MESSAGES, truncated);
|
||||
}
|
||||
}
|
||||
if (meta.output !== undefined) {
|
||||
const truncated = truncateForTelemetry(meta.output);
|
||||
if (truncated !== undefined) {
|
||||
span.setAttribute(GEN_AI_OUTPUT_MESSAGES, truncated);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (meta.output !== undefined) {
|
||||
const truncated = truncateForTelemetry(meta.output);
|
||||
for (const [key, value] of Object.entries(meta.attributes)) {
|
||||
const truncated = truncateForTelemetry(value);
|
||||
if (truncated !== undefined) {
|
||||
span.setAttribute(GEN_AI_OUTPUT_MESSAGES, truncated);
|
||||
span.setAttribute(key, truncated);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const [key, value] of Object.entries(meta.attributes)) {
|
||||
const truncated = truncateForTelemetry(value);
|
||||
if (truncated !== undefined) {
|
||||
span.setAttribute(key, truncated);
|
||||
} else {
|
||||
// Add basic attributes even when traces are disabled
|
||||
for (const [key, value] of Object.entries(meta.attributes)) {
|
||||
if (
|
||||
key === GEN_AI_OPERATION_NAME ||
|
||||
key === GEN_AI_AGENT_NAME ||
|
||||
key === GEN_AI_AGENT_DESCRIPTION ||
|
||||
key === GEN_AI_CONVERSATION_ID
|
||||
) {
|
||||
const truncated = truncateForTelemetry(value);
|
||||
if (truncated !== undefined) {
|
||||
span.setAttribute(key, truncated);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (meta.error) {
|
||||
|
||||
@@ -387,6 +387,13 @@ export class ToolCallEvent implements BaseTelemetryEvent {
|
||||
}
|
||||
|
||||
export const EVENT_API_REQUEST = 'gemini_cli.api_request';
|
||||
|
||||
function shouldIncludePayloads(config: Config): boolean {
|
||||
return (
|
||||
config.getTelemetryTracesEnabled() && config.getTelemetryLogPromptsEnabled()
|
||||
);
|
||||
}
|
||||
|
||||
export class ApiRequestEvent implements BaseTelemetryEvent {
|
||||
'event.name': 'api_request';
|
||||
'event.timestamp': string;
|
||||
@@ -443,7 +450,7 @@ export class ApiRequestEvent implements BaseTelemetryEvent {
|
||||
attributes['server.port'] = this.prompt.server.port;
|
||||
}
|
||||
|
||||
if (config.getTelemetryLogPromptsEnabled() && this.prompt.contents) {
|
||||
if (shouldIncludePayloads(config) && this.prompt.contents) {
|
||||
attributes['gen_ai.input.messages'] = JSON.stringify(
|
||||
toInputMessages(this.prompt.contents),
|
||||
);
|
||||
@@ -540,7 +547,7 @@ export class ApiErrorEvent implements BaseTelemetryEvent {
|
||||
attributes['server.port'] = this.prompt.server.port;
|
||||
}
|
||||
|
||||
if (config.getTelemetryLogPromptsEnabled() && this.prompt.contents) {
|
||||
if (shouldIncludePayloads(config) && this.prompt.contents) {
|
||||
attributes['gen_ai.input.messages'] = JSON.stringify(
|
||||
toInputMessages(this.prompt.contents),
|
||||
);
|
||||
@@ -707,9 +714,13 @@ export class ApiResponseEvent implements BaseTelemetryEvent {
|
||||
'event.timestamp': this['event.timestamp'],
|
||||
'gen_ai.response.id': this.response.response_id,
|
||||
'gen_ai.response.finish_reasons': this.finish_reasons,
|
||||
'gen_ai.output.messages': JSON.stringify(
|
||||
toOutputMessages(this.response.candidates),
|
||||
),
|
||||
...(shouldIncludePayloads(config)
|
||||
? {
|
||||
'gen_ai.output.messages': JSON.stringify(
|
||||
toOutputMessages(this.response.candidates),
|
||||
),
|
||||
}
|
||||
: {}),
|
||||
...toGenerateContentConfigAttributes(this.prompt.generate_content_config),
|
||||
...getConventionAttributes(this),
|
||||
};
|
||||
@@ -719,7 +730,7 @@ export class ApiResponseEvent implements BaseTelemetryEvent {
|
||||
attributes['server.port'] = this.prompt.server.port;
|
||||
}
|
||||
|
||||
if (config.getTelemetryLogPromptsEnabled() && this.prompt.contents) {
|
||||
if (shouldIncludePayloads(config) && this.prompt.contents) {
|
||||
attributes['gen_ai.input.messages'] = JSON.stringify(
|
||||
toInputMessages(this.prompt.contents),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user