LogSession.js 7.96 KB
/**
 * LogSession - 会话级日志管理
 *
 * 为单个会话(任务)提供结构化的日志记录接口
 */

const { EventEmitter } = require('events');

class LogSession extends EventEmitter {
  constructor(sessionId, backend, options = {}) {
    super();
    this.sessionId = sessionId;
    this.backend = backend;
    this.options = options;
    this.sequence = 0;
    this.agentContexts = new Map(); // agentName -> context
    this.startTime = new Date().toISOString();
  }

  /**
   * 生成下一个序列号
   * @private
   */
  _nextSeq() {
    return ++this.sequence;
  }

  /**
   * 构建基础日志条目
   * @private
   */
  _buildEntry(category, eventType, level, payload, agentName = null, metadata = {}) {
    return {
      sessionId: this.sessionId,
      sequence: this._nextSeq(),
      timestamp: new Date().toISOString(),
      level,
      category,
      eventType,
      agentName,
      payload,
      metadata: {
        ...this.options.metadata,
        ...metadata,
      },
    };
  }

  /**
   * 写入日志
   * @private
   */
  async _write(entry) {
    try {
      await this.backend.write(entry);
      this.emit('log', entry);
    } catch (err) {
      console.error('[LogSession] Failed to write log:', err);
    }
  }

  // ============ Agent 生命周期 ============

  /**
   * 记录 Agent 开始
   */
  logAgentStart(agentName, task, metadata = {}) {
    // 设置 Agent 上下文
    this.agentContexts.set(agentName, {
      startTime: Date.now(),
      task,
      ...metadata,
    });

    const entry = this._buildEntry(
      'agent',
      'agent_start',
      'info',
      { task },
      agentName,
      metadata
    );

    return this._write(entry);
  }

  /**
   * 记录 Agent 结束
   */
  logAgentEnd(agentName, output, status, metadata = {}) {
    const context = this.agentContexts.get(agentName) || {};
    const duration = context.startTime ? Date.now() - context.startTime : null;

    const entry = this._buildEntry(
      'agent',
      'agent_end',
      status === 'success' ? 'info' : 'warn',
      { output: this._truncate(output), status, duration },
      agentName,
      metadata
    );

    this.agentContexts.delete(agentName);
    return this._write(entry);
  }

  /**
   * 记录 Agent 错误
   */
  logAgentError(agentName, error, metadata = {}) {
    const entry = this._buildEntry(
      'agent',
      'agent_error',
      'error',
      {
        message: error.message,
        stack: error.stack,
      },
      agentName,
      metadata
    );

    return this._write(entry);
  }

  // ============ 流式输出 ============

  /**
   * 记录流式输出块
   * @param {string} agentName - Agent 名称
   * @param {string} chunk - 输出内容
   * @param {string} type - 类型: 'stdout'|'stderr'|'thought'|'message'
   */
  logStreamChunk(agentName, chunk, type = 'stdout') {
    const entry = this._buildEntry(
      'agent',
      'stream_chunk',
      'debug',
      { chunk, chunkType: type },
      agentName
    );

    // 流式输出通常是高频的,使用 debug 级别
    return this._write(entry);
  }

  // ============ 协议事件 ============

  /**
   * 记录协议事件(ACP/MCP)
   * @param {string} agentName - Agent 名称
   * @param {string} direction - 方向: 'in'|'out'
   * @param {Object} event - 事件对象
   */
  logProtocolEvent(agentName, direction, event) {
    const entry = this._buildEntry(
      'protocol',
      'protocol_event',
      'debug',
      { direction, event },
      agentName
    );

    return this._write(entry);
  }

  // ============ 工具调用 ============

  /**
   * 记录工具调用
   */
  logToolCall(agentName, toolName, args, callId) {
    const entry = this._buildEntry(
      'tool',
      'tool_call',
      'info',
      { toolName, args, callId },
      agentName
    );

    return this._write(entry);
  }

  /**
   * 记录工具结果
   */
  logToolResult(agentName, callId, result, error = null) {
    const level = error ? 'error' : 'info';
    const payload = error
      ? { callId, error: { message: error.message, stack: error.stack } }
      : { callId, result: this._truncate(result) };

    const entry = this._buildEntry('tool', 'tool_result', level, payload, agentName);

    return this._write(entry);
  }

  // ============ 子代理调用 ============

  /**
   * 记录子代理调用
   */
  logSubagentCall(parentAgent, subagentName, task) {
    const entry = this._buildEntry(
      'agent',
      'subagent_call',
      'info',
      { subagentName, task: this._truncate(task) },
      parentAgent
    );

    return this._write(entry);
  }

  /**
   * 记录子代理返回
   */
  logSubagentReturn(parentAgent, subagentName, result) {
    const entry = this._buildEntry(
      'agent',
      'subagent_return',
      'info',
      { subagentName, result: this._truncate(result) },
      parentAgent
    );

    return this._write(entry);
  }

  // ============ 文件变更 ============

  /**
   * 记录文件变更
   * @param {string} agentName - Agent 名称
   * @param {string} operation - 操作: 'create'|'modify'|'delete'|'read'
   * @param {string} filePath - 文件路径
   * @param {Object} metadata - 额外信息
   */
  logFileChange(agentName, operation, filePath, metadata = {}) {
    const entry = this._buildEntry(
      'file',
      'file_change',
      'info',
      { operation, filePath },
      agentName,
      metadata
    );

    return this._write(entry);
  }

  // ============ 自定义事件 ============

  /**
   * 记录自定义事件
   * @param {string} agentName - Agent 名称(可选,null 表示系统级)
   * @param {string} eventType - 事件类型
   * @param {Object} payload - 事件数据
   * @param {string} level - 日志级别
   */
  logEvent(agentName, eventType, payload, level = 'info') {
    const category = agentName ? 'agent' : 'system';

    const entry = this._buildEntry(category, eventType, level, payload, agentName);

    return this._write(entry);
  }

  // ============ 系统级快捷方法 ============

  logInfo(eventType, payload) {
    return this.logEvent(null, eventType, payload, 'info');
  }

  logWarn(eventType, payload) {
    return this.logEvent(null, eventType, payload, 'warn');
  }

  logError(eventType, payload) {
    return this.logEvent(null, eventType, payload, 'error');
  }

  logDebug(eventType, payload) {
    return this.logEvent(null, eventType, payload, 'debug');
  }

  // ============ 会话管理 ============

  /**
   * 生成会话报告
   */
  async generateReport() {
    // 查询所有日志
    const entries = await this.backend.query({ sessionId: this.sessionId, limit: 10000 });

    // 统计信息
    const stats = {
      totalEvents: entries.length,
      agents: new Set(),
      tools: new Set(),
      files: new Set(),
      errors: 0,
    };

    for (const entry of entries) {
      if (entry.agentName) stats.agents.add(entry.agentName);
      if (entry.category === 'tool' && entry.payload?.toolName) {
        stats.tools.add(entry.payload.toolName);
      }
      if (entry.category === 'file' && entry.payload?.filePath) {
        stats.files.add(entry.payload.filePath);
      }
      if (entry.level === 'error' || entry.level === 'fatal') {
        stats.errors++;
      }
    }

    return {
      sessionId: this.sessionId,
      startTime: this.startTime,
      endTime: new Date().toISOString(),
      stats: {
        ...stats,
        agents: Array.from(stats.agents),
        tools: Array.from(stats.tools),
        files: Array.from(stats.files),
      },
    };
  }

  /**
   * 关闭会话
   */
  async close() {
    this.emit('close');
    this.removeAllListeners();
  }

  // ============ 辅助方法 ============

  /**
   * 截断长文本
   * @private
   */
  _truncate(text, maxLength = 10000) {
    if (!text) return text;
    const str = typeof text === 'string' ? text : JSON.stringify(text);
    if (str.length <= maxLength) return str;
    return str.substring(0, maxLength) + `... [truncated ${str.length - maxLength} chars]`;
  }
}

module.exports = { LogSession };