source dump of claude code
at main 75 lines 2.3 kB view raw
1import type { Attributes } from '@opentelemetry/api' 2import { getEventLogger, getPromptId } from 'src/bootstrap/state.js' 3import { logForDebugging } from '../debug.js' 4import { isEnvTruthy } from '../envUtils.js' 5import { getTelemetryAttributes } from '../telemetryAttributes.js' 6 7// Monotonically increasing counter for ordering events within a session 8let eventSequence = 0 9 10// Track whether we've already warned about a null event logger to avoid spamming 11let hasWarnedNoEventLogger = false 12 13function isUserPromptLoggingEnabled() { 14 return isEnvTruthy(process.env.OTEL_LOG_USER_PROMPTS) 15} 16 17export function redactIfDisabled(content: string): string { 18 return isUserPromptLoggingEnabled() ? content : '<REDACTED>' 19} 20 21export async function logOTelEvent( 22 eventName: string, 23 metadata: { [key: string]: string | undefined } = {}, 24): Promise<void> { 25 const eventLogger = getEventLogger() 26 if (!eventLogger) { 27 if (!hasWarnedNoEventLogger) { 28 hasWarnedNoEventLogger = true 29 logForDebugging( 30 `[3P telemetry] Event dropped (no event logger initialized): ${eventName}`, 31 { level: 'warn' }, 32 ) 33 } 34 return 35 } 36 37 // Skip logging in test environment 38 if (process.env.NODE_ENV === 'test') { 39 return 40 } 41 42 const attributes: Attributes = { 43 ...getTelemetryAttributes(), 44 'event.name': eventName, 45 'event.timestamp': new Date().toISOString(), 46 'event.sequence': eventSequence++, 47 } 48 49 // Add prompt ID to events (but not metrics, where it would cause unbounded cardinality) 50 const promptId = getPromptId() 51 if (promptId) { 52 attributes['prompt.id'] = promptId 53 } 54 55 // Workspace directory from the desktop app (host path). Events only — 56 // filesystem paths are too high-cardinality for metric dimensions, and 57 // the BQ metrics pipeline must never see them. 58 const workspaceDir = process.env.CLAUDE_CODE_WORKSPACE_HOST_PATHS 59 if (workspaceDir) { 60 attributes['workspace.host_paths'] = workspaceDir.split('|') 61 } 62 63 // Add metadata as attributes - all values are already strings 64 for (const [key, value] of Object.entries(metadata)) { 65 if (value !== undefined) { 66 attributes[key] = value 67 } 68 } 69 70 // Emit log record as an event 71 eventLogger.emit({ 72 body: `claude_code.${eventName}`, 73 attributes, 74 }) 75}