diff options
| author | Jerop Kipruto <[email protected]> | 2025-06-05 16:04:25 -0400 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-06-05 13:04:25 -0700 |
| commit | 2ebf2fbc82ba998f0369f17b56f99d0b25680cb4 (patch) | |
| tree | f2cfb0991e4781f99618d5b2fbff607f52aead81 /packages/core/src/telemetry/loggers.ts | |
| parent | d3e43437a00cfe64790cc60c9c8aa82c85f520c3 (diff) | |
OpenTelemetry Integration & Telemetry Control Flag (#762)
Diffstat (limited to 'packages/core/src/telemetry/loggers.ts')
| -rw-r--r-- | packages/core/src/telemetry/loggers.ts | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/packages/core/src/telemetry/loggers.ts b/packages/core/src/telemetry/loggers.ts new file mode 100644 index 00000000..ccab12ff --- /dev/null +++ b/packages/core/src/telemetry/loggers.ts @@ -0,0 +1,191 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { logs, LogRecord, LogAttributes } from '@opentelemetry/api-logs'; +import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; +import { Config } from '../config/config.js'; +import { + EVENT_API_ERROR, + EVENT_API_REQUEST, + EVENT_API_RESPONSE, + EVENT_CLI_CONFIG, + EVENT_TOOL_CALL, + EVENT_USER_PROMPT, + SERVICE_NAME, +} from './constants.js'; +import { + ApiErrorEvent, + ApiRequestEvent, + ApiResponseEvent, + ToolCallEvent, + UserPromptEvent, +} from './types.js'; +import { + recordApiErrorMetrics, + recordApiRequestMetrics, + recordApiResponseMetrics, + recordToolCallMetrics, +} from './metrics.js'; +import { isTelemetrySdkInitialized } from './sdk.js'; + +const shouldLogUserPrompts = (config: Config): boolean => + config.getTelemetryLogUserPromptsEnabled() ?? false; + +export function logCliConfiguration(config: Config): void { + if (!isTelemetrySdkInitialized()) return; + + const attributes: LogAttributes = { + 'event.name': EVENT_CLI_CONFIG, + 'event.timestamp': new Date().toISOString(), + model: config.getModel(), + sandbox_enabled: + typeof config.getSandbox() === 'string' ? true : config.getSandbox(), + core_tools_enabled: (config.getCoreTools() ?? []).join(','), + approval_mode: config.getApprovalMode(), + vertex_ai_enabled: config.getVertexAI() ?? false, + log_user_prompts_enabled: config.getTelemetryLogUserPromptsEnabled(), + file_filtering_respect_git_ignore: + config.getFileFilteringRespectGitIgnore(), + file_filtering_allow_build_artifacts: + config.getFileFilteringAllowBuildArtifacts(), + }; + const logger = logs.getLogger(SERVICE_NAME); + const logRecord: LogRecord = { + body: 'CLI configuration loaded.', + attributes, + }; + logger.emit(logRecord); +} + +export function logUserPrompt( + config: Config, + event: Omit<UserPromptEvent, 'event.name' | 'event.timestamp' | 'prompt'> & { + prompt: string; + }, +): void { + if (!isTelemetrySdkInitialized()) return; + const { prompt, ...restOfEventArgs } = event; + const attributes: LogAttributes = { + ...restOfEventArgs, + 'event.name': EVENT_USER_PROMPT, + 'event.timestamp': new Date().toISOString(), + }; + if (shouldLogUserPrompts(config)) { + attributes.prompt = prompt; + } + const logger = logs.getLogger(SERVICE_NAME); + const logRecord: LogRecord = { + body: `User prompt. Length: ${event.prompt_char_count}`, + attributes, + }; + logger.emit(logRecord); +} + +export function logToolCall( + event: Omit<ToolCallEvent, 'event.name' | 'event.timestamp'>, +): void { + if (!isTelemetrySdkInitialized()) return; + const attributes: LogAttributes = { + ...event, + 'event.name': EVENT_TOOL_CALL, + 'event.timestamp': new Date().toISOString(), + function_args: JSON.stringify(event.function_args), + }; + if (event.error) { + attributes['error.message'] = event.error; + if (event.error_type) { + attributes['error.type'] = event.error_type; + } + } + const logger = logs.getLogger(SERVICE_NAME); + const logRecord: LogRecord = { + body: `Tool call: ${event.function_name}. Success: ${event.success}. Duration: ${event.duration_ms}ms.`, + attributes, + }; + logger.emit(logRecord); + recordToolCallMetrics(event.function_name, event.duration_ms, event.success); +} + +export function logApiRequest( + event: Omit<ApiRequestEvent, 'event.name' | 'event.timestamp'>, +): void { + if (!isTelemetrySdkInitialized()) return; + const attributes: LogAttributes = { + ...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}. Tokens: ${event.prompt_token_count}.`, + attributes, + }; + logger.emit(logRecord); + recordApiRequestMetrics(event.model, event.prompt_token_count); +} + +export function logApiError( + event: Omit<ApiErrorEvent, 'event.name' | 'event.timestamp'>, +): void { + if (!isTelemetrySdkInitialized()) return; + const attributes: LogAttributes = { + ...event, + 'event.name': EVENT_API_ERROR, + 'event.timestamp': new Date().toISOString(), + ['error.message']: event.error, + }; + + if (event.error_type) { + attributes['error.type'] = event.error_type; + } + if (typeof event.status_code === 'number') { + attributes[SemanticAttributes.HTTP_STATUS_CODE] = event.status_code; + } + + const logger = logs.getLogger(SERVICE_NAME); + const logRecord: LogRecord = { + body: `API error for ${event.model}. Error: ${event.error}. Duration: ${event.duration_ms}ms.`, + attributes, + }; + logger.emit(logRecord); + recordApiErrorMetrics( + event.model, + event.duration_ms, + event.status_code, + event.error_type, + ); +} + +export function logApiResponse( + event: Omit<ApiResponseEvent, 'event.name' | 'event.timestamp'>, +): void { + if (!isTelemetrySdkInitialized()) return; + const attributes: LogAttributes = { + ...event, + 'event.name': EVENT_API_RESPONSE, + 'event.timestamp': new Date().toISOString(), + }; + if (event.error) { + attributes['error.message'] = event.error; + } else if (event.status_code) { + if (typeof event.status_code === 'number') { + attributes[SemanticAttributes.HTTP_STATUS_CODE] = event.status_code; + } + } + + const logger = logs.getLogger(SERVICE_NAME); + const logRecord: LogRecord = { + body: `API response from ${event.model}. Status: ${event.status_code || 'N/A'}. Duration: ${event.duration_ms}ms.`, + attributes, + }; + logger.emit(logRecord); + recordApiResponseMetrics( + event.model, + event.duration_ms, + event.status_code, + event.error, + ); +} |
