|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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, |
|
|
StartSessionEvent, |
|
|
ToolCallEvent, |
|
|
UserPromptEvent, |
|
|
} from './types.js'; |
|
|
import { |
|
|
recordApiErrorMetrics, |
|
|
recordTokenUsageMetrics, |
|
|
recordApiResponseMetrics, |
|
|
recordToolCallMetrics, |
|
|
} from './metrics.js'; |
|
|
import { isTelemetrySdkInitialized } from './sdk.js'; |
|
|
import { ClearcutLogger } from './clearcut-logger/clearcut-logger.js'; |
|
|
|
|
|
const shouldLogUserPrompts = (config: Config): boolean => |
|
|
config.getTelemetryLogPromptsEnabled(); |
|
|
|
|
|
function getCommonAttributes(config: Config): LogAttributes { |
|
|
return { |
|
|
'session.id': config.getSessionId(), |
|
|
}; |
|
|
} |
|
|
|
|
|
export function logCliConfiguration( |
|
|
config: Config, |
|
|
event: StartSessionEvent, |
|
|
): void { |
|
|
ClearcutLogger.getInstance(config)?.logStartSessionEvent(event); |
|
|
if (!isTelemetrySdkInitialized()) return; |
|
|
|
|
|
const attributes: LogAttributes = { |
|
|
...getCommonAttributes(config), |
|
|
'event.name': EVENT_CLI_CONFIG, |
|
|
'event.timestamp': new Date().toISOString(), |
|
|
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.', |
|
|
attributes, |
|
|
}; |
|
|
logger.emit(logRecord); |
|
|
} |
|
|
|
|
|
export function logUserPrompt(config: Config, event: UserPromptEvent): void { |
|
|
ClearcutLogger.getInstance(config)?.logNewPromptEvent(event); |
|
|
if (!isTelemetrySdkInitialized()) return; |
|
|
|
|
|
const attributes: LogAttributes = { |
|
|
...getCommonAttributes(config), |
|
|
'event.name': EVENT_USER_PROMPT, |
|
|
'event.timestamp': new Date().toISOString(), |
|
|
prompt_length: event.prompt_length, |
|
|
}; |
|
|
|
|
|
if (shouldLogUserPrompts(config)) { |
|
|
attributes.prompt = event.prompt; |
|
|
} |
|
|
|
|
|
const logger = logs.getLogger(SERVICE_NAME); |
|
|
const logRecord: LogRecord = { |
|
|
body: `User prompt. Length: ${event.prompt_length}.`, |
|
|
attributes, |
|
|
}; |
|
|
logger.emit(logRecord); |
|
|
} |
|
|
|
|
|
export function logToolCall(config: Config, event: ToolCallEvent): void { |
|
|
ClearcutLogger.getInstance(config)?.logToolCallEvent(event); |
|
|
if (!isTelemetrySdkInitialized()) return; |
|
|
|
|
|
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), |
|
|
}; |
|
|
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}${event.decision ? `. Decision: ${event.decision}` : ''}. Success: ${event.success}. Duration: ${event.duration_ms}ms.`, |
|
|
attributes, |
|
|
}; |
|
|
logger.emit(logRecord); |
|
|
recordToolCallMetrics( |
|
|
config, |
|
|
event.function_name, |
|
|
event.duration_ms, |
|
|
event.success, |
|
|
event.decision, |
|
|
); |
|
|
} |
|
|
|
|
|
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}.`, |
|
|
attributes, |
|
|
}; |
|
|
logger.emit(logRecord); |
|
|
} |
|
|
|
|
|
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) { |
|
|
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( |
|
|
config, |
|
|
event.model, |
|
|
event.duration_ms, |
|
|
event.status_code, |
|
|
event.error_type, |
|
|
); |
|
|
} |
|
|
|
|
|
export function logApiResponse(config: Config, event: ApiResponseEvent): void { |
|
|
ClearcutLogger.getInstance(config)?.logApiResponseEvent(event); |
|
|
if (!isTelemetrySdkInitialized()) return; |
|
|
const attributes: LogAttributes = { |
|
|
...getCommonAttributes(config), |
|
|
...event, |
|
|
'event.name': EVENT_API_RESPONSE, |
|
|
'event.timestamp': new Date().toISOString(), |
|
|
}; |
|
|
if (event.response_text) { |
|
|
attributes.response_text = event.response_text; |
|
|
} |
|
|
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( |
|
|
config, |
|
|
event.model, |
|
|
event.duration_ms, |
|
|
event.status_code, |
|
|
event.error, |
|
|
); |
|
|
recordTokenUsageMetrics( |
|
|
config, |
|
|
event.model, |
|
|
event.input_token_count, |
|
|
'input', |
|
|
); |
|
|
recordTokenUsageMetrics( |
|
|
config, |
|
|
event.model, |
|
|
event.output_token_count, |
|
|
'output', |
|
|
); |
|
|
recordTokenUsageMetrics( |
|
|
config, |
|
|
event.model, |
|
|
event.cached_content_token_count, |
|
|
'cache', |
|
|
); |
|
|
recordTokenUsageMetrics( |
|
|
config, |
|
|
event.model, |
|
|
event.thoughts_token_count, |
|
|
'thought', |
|
|
); |
|
|
recordTokenUsageMetrics(config, event.model, event.tool_token_count, 'tool'); |
|
|
} |
|
|
|