/** * Evolution Logger — 结构化日志输出 * * 将进化过程的关键事件写入日志文件: * logs/evolution--.log * * 每行一个 JSON 事件,方便 grep/jq 分析。 * 同时仍然打印到 console(不影响终端体验)。 */ const fs = require('fs'); const path = require('path'); const LOGS_DIR = path.resolve(__dirname, '../../logs'); class EvolutionLogger { constructor(runId, skillName) { this.runId = runId || 'unknown'; this.skillName = skillName || '?'; this.startTime = Date.now(); this.events = []; // 确保日志目录存在 if (!fs.existsSync(LOGS_DIR)) { fs.mkdirSync(LOGS_DIR, { recursive: true }); } const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); this.filePath = path.join(LOGS_DIR, `evolution-${this.skillName}-${ts}.log`); this.stream = fs.createWriteStream(this.filePath, { flags: 'a' }); this._write('session_start', { runId: this.runId, skillName: this.skillName, startedAt: new Date().toISOString(), }); } /** * 记录一个事件 * @param {string} event - 事件类型 * @param {object} data - 事件数据 * @param {string} [message] - 人类可读消息 */ log(event, data = {}, message) { const elapsed = ((Date.now() - this.startTime) / 1000).toFixed(1); const entry = { t: new Date().toISOString(), elapsed: `${elapsed}s`, event, ...data, }; // 写文件 this._write(event, entry); // 打印到终端 const msg = message || this._formatMessage(event, data); if (msg) console.log(`[Evolution] ${msg}`); this.events.push(entry); return entry; } /** Phase 级别事件 */ phase(name, data = {}) { return this.log('phase', { phase: name, ...data }, `\n═══ ${name} ═══`); } /** Step 级别事件 */ step(name, data = {}) { return this.log('step', { step: name, ...data }); } /** LLM 调用 */ llmCall(purpose, data = {}) { return this.log('llm_call', { purpose, ...data }, ` 🤖 LLM: ${purpose}${data.backend ? ` [${data.backend}]` : ''}`); } /** LLM 响应 */ llmResult(purpose, data = {}) { const status = data.success ? '✅' : '❌'; return this.log('llm_result', { purpose, ...data }, ` ${status} LLM 结果: ${purpose}${data.outputLength ? ` (${data.outputLength} chars)` : ''}`); } /** 基因组事件 */ genome(action, data = {}) { return this.log('genome', { action, ...data }); } /** 错误 */ error(context, err) { return this.log('error', { context, message: err.message || String(err), stack: err.stack?.split('\n').slice(0, 3).join(' | '), }, ` ❌ ${context}: ${err.message || err}`); } /** 结束 */ end(summary = {}) { const elapsed = ((Date.now() - this.startTime) / 1000).toFixed(1); this._write('session_end', { runId: this.runId, skillName: this.skillName, elapsed: `${elapsed}s`, totalEvents: this.events.length, ...summary, }); this.stream.end(); console.log(`\n📋 进化日志已保存: ${this.filePath}`); return this.filePath; } /** 生成人类可读的摘要报告 */ writeSummary() { const summaryPath = this.filePath.replace('.log', '.summary.md'); const lines = ['# 进化运行摘要\n']; lines.push(`- **Skill**: ${this.skillName}`); lines.push(`- **Run ID**: ${this.runId}`); lines.push(`- **开始**: ${new Date(this.startTime).toISOString()}`); lines.push(`- **耗时**: ${((Date.now() - this.startTime) / 1000).toFixed(1)}s`); lines.push(`- **总事件数**: ${this.events.length}\n`); // 按阶段分组 lines.push('## 事件时间线\n'); lines.push('| 时间 | 事件 | 详情 |'); lines.push('|------|------|------|'); for (const e of this.events) { const detail = this._summarizeEvent(e); if (detail) { lines.push(`| ${e.elapsed} | ${e.event} | ${detail} |`); } } // LLM 调用统计 const llmCalls = this.events.filter(e => e.event === 'llm_call'); const llmResults = this.events.filter(e => e.event === 'llm_result'); const llmErrors = llmResults.filter(e => !e.success); lines.push('\n## LLM 调用统计\n'); lines.push(`- 总调用: ${llmCalls.length}`); lines.push(`- 成功: ${llmResults.length - llmErrors.length}`); lines.push(`- 失败: ${llmErrors.length}`); if (llmErrors.length > 0) { lines.push('\n### 失败详情\n'); for (const e of llmErrors) { lines.push(`- **${e.purpose}**: ${e.message || '未知错误'}`); } } // 基因组统计 const genomeEvents = this.events.filter(e => e.event === 'genome'); if (genomeEvents.length > 0) { lines.push('\n## 基因组操作\n'); const actions = {}; for (const e of genomeEvents) { actions[e.action] = (actions[e.action] || 0) + 1; } for (const [action, count] of Object.entries(actions)) { lines.push(`- ${action}: ${count} 次`); } } fs.writeFileSync(summaryPath, lines.join('\n'), 'utf-8'); console.log(`📊 摘要报告: ${summaryPath}`); return summaryPath; } // ── 内部方法 ── _write(event, data) { try { this.stream.write(JSON.stringify(data) + '\n'); } catch { /* 静默 */ } } _formatMessage(event, data) { switch (event) { case 'step': return ` 📌 ${data.step}${data.count !== undefined ? ` (${data.count})` : ''}`; case 'genome': return ` 🧬 ${data.action}: ${data.id?.slice(0, 8) || ''}${data.fitness ? ` fitness=${(data.fitness * 100).toFixed(1)}%` : ''}`; case 'llm_request': return ` 📤 → LLM [${data.backend}] (${data.promptLength} chars)`; case 'llm_response': return ` 📥 ← LLM [${data.backend}] (${data.outputLength} chars, ${(data.durationMs / 1000).toFixed(1)}s)`; case 'llm_parsed': return ` ✅ JSON 解析成功`; default: return null; } } } module.exports = { EvolutionLogger };