Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
6.6 kB · 235 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236/** * Error log sink implementation * * This module contains the heavy implementation for error logging and should be * initialized during app startup. It handles file-based error logging to disk. * * Usage: Call initializeErrorLogSink() during app startup to attach the sink. * * DESIGN: This module is separate from log.ts to avoid import cycles. * log.ts has NO heavy dependencies - events are queued until this sink is attached. */
import axios from 'axios'import { dirname, join } from 'path'import { getSessionId } from '../bootstrap/state.js'import { createBufferedWriter } from './bufferedWriter.js'import { CACHE_PATHS } from './cachePaths.js'import { registerCleanup } from './cleanupRegistry.js'import { logForDebugging } from './debug.js'import { getFsImplementation } from './fsOperations.js'import { attachErrorLogSink, dateToFilename } from './log.js'import { jsonStringify } from './slowOperations.js'
const DATE = dateToFilename(new Date())
/** * Gets the path to the errors log file. */export function getErrorsPath(): string { return join(CACHE_PATHS.errors(), DATE + '.jsonl')}
/** * Gets the path to MCP logs for a server. */export function getMCPLogsPath(serverName: string): string { return join(CACHE_PATHS.mcpLogs(serverName), DATE + '.jsonl')}
type JsonlWriter = { write: (obj: object) => void flush: () => void dispose: () => void}
function createJsonlWriter(options: { writeFn: (content: string) => void flushIntervalMs?: number maxBufferSize?: number}): JsonlWriter { const writer = createBufferedWriter(options) return { write(obj: object): void { writer.write(jsonStringify(obj) + '\n') }, flush: writer.flush, dispose: writer.dispose, }}
// Buffered writers for JSONL log files, keyed by pathconst logWriters = new Map<string, JsonlWriter>()
/** * Flush all buffered log writers. Used for testing. * @internal */export function _flushLogWritersForTesting(): void { for (const writer of logWriters.values()) { writer.flush() }}
/** * Clear all buffered log writers. Used for testing. * @internal */export function _clearLogWritersForTesting(): void { for (const writer of logWriters.values()) { writer.dispose() } logWriters.clear()}
function getLogWriter(path: string): JsonlWriter { let writer = logWriters.get(path) if (!writer) { const dir = dirname(path) writer = createJsonlWriter({ // sync IO: called from sync context writeFn: (content: string) => { try { // Happy-path: directory already exists getFsImplementation().appendFileSync(path, content) } catch { // If any error occurs, assume it was due to missing directory getFsImplementation().mkdirSync(dir) // Retry appending getFsImplementation().appendFileSync(path, content) } }, flushIntervalMs: 1000, maxBufferSize: 50, }) logWriters.set(path, writer) registerCleanup(async () => writer?.dispose()) } return writer}
function appendToLog(path: string, message: object): void { if (process.env.USER_TYPE !== 'ant') { return }
const messageWithTimestamp = { timestamp: new Date().toISOString(), ...message, cwd: getFsImplementation().cwd(), userType: process.env.USER_TYPE, sessionId: getSessionId(), version: MACRO.VERSION, }
getLogWriter(path).write(messageWithTimestamp)}
function extractServerMessage(data: unknown): string | undefined { if (typeof data === 'string') { return data } if (data && typeof data === 'object') { const obj = data as Record<string, unknown> if (typeof obj.message === 'string') { return obj.message } if ( typeof obj.error === 'object' && obj.error && 'message' in obj.error && typeof (obj.error as Record<string, unknown>).message === 'string' ) { return (obj.error as Record<string, unknown>).message as string } } return undefined}
/** * Implementation for logError - writes error to debug log and file. */function logErrorImpl(error: Error): void { const errorStr = error.stack || error.message
// Enrich axios errors with request URL, status, and server message for debugging let context = '' if (axios.isAxiosError(error) && error.config?.url) { const parts = [`url=${error.config.url}`] if (error.response?.status !== undefined) { parts.push(`status=${error.response.status}`) } const serverMessage = extractServerMessage(error.response?.data) if (serverMessage) { parts.push(`body=${serverMessage}`) } context = `[${parts.join(',')}] ` }
logForDebugging(`${error.name}: ${context}${errorStr}`, { level: 'error' })
appendToLog(getErrorsPath(), { error: `${context}${errorStr}`, })}
/** * Implementation for logMCPError - writes MCP error to debug log and file. */function logMCPErrorImpl(serverName: string, error: unknown): void { // Not themed, to avoid having to pipe theme all the way down logForDebugging(`MCP server "${serverName}" ${error}`, { level: 'error' })
const logFile = getMCPLogsPath(serverName) const errorStr = error instanceof Error ? error.stack || error.message : String(error)
const errorInfo = { error: errorStr, timestamp: new Date().toISOString(), sessionId: getSessionId(), cwd: getFsImplementation().cwd(), }
getLogWriter(logFile).write(errorInfo)}
/** * Implementation for logMCPDebug - writes MCP debug message to log file. */function logMCPDebugImpl(serverName: string, message: string): void { logForDebugging(`MCP server "${serverName}": ${message}`)
const logFile = getMCPLogsPath(serverName)
const debugInfo = { debug: message, timestamp: new Date().toISOString(), sessionId: getSessionId(), cwd: getFsImplementation().cwd(), }
getLogWriter(logFile).write(debugInfo)}
/** * Initialize the error log sink. * * Call this during app startup to attach the error logging backend. * Any errors logged before this is called will be queued and drained. * * Should be called BEFORE initializeAnalyticsSink() in the startup sequence. * * Idempotent: safe to call multiple times (subsequent calls are no-ops). */export function initializeErrorLogSink(): void { attachErrorLogSink({ logError: logErrorImpl, logMCPError: logMCPErrorImpl, logMCPDebug: logMCPDebugImpl, getErrorsPath, getMCPLogsPath, })
logForDebugging('Error log sink initialized')}