diff options
| author | owenofbrien <[email protected]> | 2025-06-22 09:26:48 -0500 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-06-22 14:26:48 +0000 |
| commit | 4cfab0a8931decca8c953de1e5715e40ee31ee9a (patch) | |
| tree | dd45db52d57060058213d3fb0b7a126ab043ce4d /packages/core/src/telemetry/loggers.ts | |
| parent | c9950b3cb273246d801a5cbb04cf421d4c5e39c4 (diff) | |
Clearcut logging - initial implementation (#1274)
Flag-guarded initial implementation of a clearcut logger to collect telemetry data and send it to Concord for dashboards, etc.
Diffstat (limited to 'packages/core/src/telemetry/loggers.ts')
| -rw-r--r-- | packages/core/src/telemetry/loggers.ts | 131 |
1 files changed, 38 insertions, 93 deletions
diff --git a/packages/core/src/telemetry/loggers.ts b/packages/core/src/telemetry/loggers.ts index 0ecf130f..054386b8 100644 --- a/packages/core/src/telemetry/loggers.ts +++ b/packages/core/src/telemetry/loggers.ts @@ -20,6 +20,7 @@ import { ApiErrorEvent, ApiRequestEvent, ApiResponseEvent, + StartSessionEvent, ToolCallEvent, UserPromptEvent, } from './types.js'; @@ -30,15 +31,10 @@ import { recordToolCallMetrics, } from './metrics.js'; import { isTelemetrySdkInitialized } from './sdk.js'; -import { ToolConfirmationOutcome } from '../tools/tools.js'; -import { - GenerateContentResponse, - GenerateContentResponseUsageMetadata, -} from '@google/genai'; -import { AuthType } from '../core/contentGenerator.js'; +import { ClearcutLogger } from './clearcut-logger/clearcut-logger.js'; const shouldLogUserPrompts = (config: Config): boolean => - config.getTelemetryLogPromptsEnabled() ?? false; + config.getTelemetryLogPromptsEnabled(); function getCommonAttributes(config: Config): LogAttributes { return { @@ -46,59 +42,30 @@ function getCommonAttributes(config: Config): LogAttributes { }; } -export enum ToolCallDecision { - ACCEPT = 'accept', - REJECT = 'reject', - MODIFY = 'modify', -} - -export function getDecisionFromOutcome( - outcome: ToolConfirmationOutcome, -): ToolCallDecision { - switch (outcome) { - case ToolConfirmationOutcome.ProceedOnce: - case ToolConfirmationOutcome.ProceedAlways: - case ToolConfirmationOutcome.ProceedAlwaysServer: - case ToolConfirmationOutcome.ProceedAlwaysTool: - return ToolCallDecision.ACCEPT; - case ToolConfirmationOutcome.ModifyWithEditor: - return ToolCallDecision.MODIFY; - case ToolConfirmationOutcome.Cancel: - default: - return ToolCallDecision.REJECT; - } -} - -export function logCliConfiguration(config: Config): void { +export function logCliConfiguration( + config: Config, + event: StartSessionEvent, +): void { + ClearcutLogger.getInstance(config)?.logStartSessionEvent(event); if (!isTelemetrySdkInitialized()) return; - const generatorConfig = config.getContentGeneratorConfig(); - let useGemini = false; - let useVertex = false; - - if (generatorConfig && generatorConfig.authType) { - useGemini = generatorConfig.authType === AuthType.USE_GEMINI; - useVertex = generatorConfig.authType === AuthType.USE_VERTEX_AI; - } - - const mcpServers = config.getMcpServers(); const attributes: LogAttributes = { ...getCommonAttributes(config), 'event.name': EVENT_CLI_CONFIG, 'event.timestamp': new Date().toISOString(), - model: config.getModel(), - embedding_model: config.getEmbeddingModel(), - sandbox_enabled: !!config.getSandbox(), - core_tools_enabled: (config.getCoreTools() ?? []).join(','), - approval_mode: config.getApprovalMode(), - api_key_enabled: useGemini || useVertex, - vertex_ai_enabled: useVertex, - log_user_prompts_enabled: config.getTelemetryLogPromptsEnabled(), - file_filtering_respect_git_ignore: - config.getFileFilteringRespectGitIgnore(), - debug_mode: config.getDebugMode(), - mcp_servers: mcpServers ? Object.keys(mcpServers).join(',') : '', + model: event.model, + embedding_model: event.embedding_model, + sandbox_enabled: event.sandbox_enabled, + core_tools_enabled: event.core_tools_enabled, + approval_mode: event.approval_mode, + api_key_enabled: event.api_key_enabled, + vertex_ai_enabled: event.vertex_ai_enabled, + log_user_prompts_enabled: event.telemetry_log_user_prompts_enabled, + file_filtering_respect_git_ignore: event.file_filtering_respect_git_ignore, + debug_mode: event.debug_enabled, + mcp_servers: event.mcp_servers, }; + const logger = logs.getLogger(SERVICE_NAME); const logRecord: LogRecord = { body: 'CLI configuration loaded.', @@ -107,12 +74,8 @@ export function logCliConfiguration(config: Config): void { logger.emit(logRecord); } -export function logUserPrompt( - config: Config, - event: Omit<UserPromptEvent, 'event.name' | 'event.timestamp' | 'prompt'> & { - prompt: string; - }, -): void { +export function logUserPrompt(config: Config, event: UserPromptEvent): void { + ClearcutLogger.getInstance(config)?.logNewPromptEvent(event); if (!isTelemetrySdkInitialized()) return; const attributes: LogAttributes = { @@ -134,22 +97,16 @@ export function logUserPrompt( logger.emit(logRecord); } -export function logToolCall( - config: Config, - event: Omit<ToolCallEvent, 'event.name' | 'event.timestamp' | 'decision'>, - outcome?: ToolConfirmationOutcome, -): void { +export function logToolCall(config: Config, event: ToolCallEvent): void { + ClearcutLogger.getInstance(config)?.logToolCallEvent(event); if (!isTelemetrySdkInitialized()) return; - const decision = outcome ? getDecisionFromOutcome(outcome) : undefined; - const attributes: LogAttributes = { ...getCommonAttributes(config), ...event, 'event.name': EVENT_TOOL_CALL, 'event.timestamp': new Date().toISOString(), function_args: JSON.stringify(event.function_args, null, 2), - decision, }; if (event.error) { attributes['error.message'] = event.error; @@ -157,9 +114,10 @@ export function logToolCall( attributes['error.type'] = event.error_type; } } + const logger = logs.getLogger(SERVICE_NAME); const logRecord: LogRecord = { - body: `Tool call: ${event.function_name}${decision ? `. Decision: ${decision}` : ''}. Success: ${event.success}. Duration: ${event.duration_ms}ms.`, + body: `Tool call: ${event.function_name}${event.decision ? `. Decision: ${event.decision}` : ''}. Success: ${event.success}. Duration: ${event.duration_ms}ms.`, attributes, }; logger.emit(logRecord); @@ -168,21 +126,21 @@ export function logToolCall( event.function_name, event.duration_ms, event.success, - decision, + event.decision, ); } -export function logApiRequest( - config: Config, - event: Omit<ApiRequestEvent, 'event.name' | 'event.timestamp'>, -): void { +export function logApiRequest(config: Config, event: ApiRequestEvent): void { + ClearcutLogger.getInstance(config)?.logApiRequestEvent(event); if (!isTelemetrySdkInitialized()) return; + const attributes: LogAttributes = { ...getCommonAttributes(config), ...event, 'event.name': EVENT_API_REQUEST, 'event.timestamp': new Date().toISOString(), }; + const logger = logs.getLogger(SERVICE_NAME); const logRecord: LogRecord = { body: `API request to ${event.model}.`, @@ -191,17 +149,18 @@ export function logApiRequest( logger.emit(logRecord); } -export function logApiError( - config: Config, - event: Omit<ApiErrorEvent, 'event.name' | 'event.timestamp'>, -): void { +export function logApiError(config: Config, event: ApiErrorEvent): void { + ClearcutLogger.getInstance(config)?.logApiErrorEvent(event); if (!isTelemetrySdkInitialized()) return; + const attributes: LogAttributes = { ...getCommonAttributes(config), ...event, 'event.name': EVENT_API_ERROR, 'event.timestamp': new Date().toISOString(), ['error.message']: event.error, + model_name: event.model, + duration: event.duration_ms, }; if (event.error_type) { @@ -226,10 +185,8 @@ export function logApiError( ); } -export function logApiResponse( - config: Config, - event: Omit<ApiResponseEvent, 'event.name' | 'event.timestamp'>, -): void { +export function logApiResponse(config: Config, event: ApiResponseEvent): void { + ClearcutLogger.getInstance(config)?.logApiResponseEvent(event); if (!isTelemetrySdkInitialized()) return; const attributes: LogAttributes = { ...getCommonAttributes(config), @@ -287,15 +244,3 @@ export function logApiResponse( ); recordTokenUsageMetrics(config, event.model, event.tool_token_count, 'tool'); } - -export function getFinalUsageMetadata( - chunks: GenerateContentResponse[], -): GenerateContentResponseUsageMetadata | undefined { - // Only the last streamed item has the final token count. - const lastChunkWithMetadata = chunks - .slice() - .reverse() - .find((chunk) => chunk.usageMetadata); - - return lastChunkWithMetadata?.usageMetadata; -} |
