# LogViewer 日志查看器组件

基于Monaco Editor实现的高性能只读日志查看器组件，支持WebSocket实时日志流和HTTP轮询两种数据源。

## 特性

- ✅ **高性能虚拟滚动** - 支持百万行日志流畅显示
- ✅ **内置搜索功能** - 快速查找日志内容
- ✅ **只读模式** - 禁用用户输入，专注日志查看
- ✅ **自动滚动** - 新日志自动滚动到底部
- ✅ **批量追加优化** - 支持批量追加日志，减少渲染次数
- ✅ **主题切换** - 支持深色/浅色/高对比度主题
- ✅ **响应式布局** - 自动适应容器大小
- ✅ **行号显示** - 清晰的行号显示
- ✅ **策略模式** - 支持WebSocket和HTTP轮询数据源

## 架构设计

```
LogStreamViewer (通用组件)
├── LogDataSource (策略接口)
│   ├── WebSocketLogSource (WebSocket策略实现)
│   └── HttpPollingLogSource (HTTP轮询策略实现)
└── MonacoLogViewer (显示层)
```

## 安装依赖

组件依赖以下包（项目已安装）：
- `@monaco-editor/react`
- `monaco-editor`

## 基础用法

### 方式1：使用WebSocket日志流（推荐）

```tsx
import { LogStreamViewer, WebSocketLogSource } from '@/components/LogViewer';

function DashboardLogs() {
  const dataSource = new WebSocketLogSource({
    url: 'ws://localhost:8080/logs',
    messageParser: (message) => {
      const data = JSON.parse(message);
      return data.type === 'LOG' ? data.content : null;
    },
    autoConnect: true,
  });

  return (
    <div style={{ height: '600px' }}>
      <LogStreamViewer dataSource={dataSource} />
    </div>
  );
}
```

### 方式2：使用HTTP轮询

```tsx
import { LogStreamViewer, HttpPollingLogSource } from '@/components/LogViewer';
import { getPodLogs } from '@/services/api';

function PodLogs({ namespace, podName }: { namespace: string; podName: string }) {
  const dataSource = new HttpPollingLogSource({
    fetchLogs: (params) => getPodLogs(namespace, podName, params),
    initialParams: { lines: 100 },
    autoRefresh: true,
    refreshInterval: 5,
    formatLog: (log) => `[${log.timestamp}] ${log.content}`,
  });

  return (
    <div style={{ height: '600px' }}>
      <LogStreamViewer dataSource={dataSource} />
    </div>
  );
}
```

### 方式3：直接使用MonacoLogViewer

```tsx
import { MonacoLogViewer, type LogViewerAPI } from '@/components/LogViewer';
import { useState } from 'react';

function SimpleLogViewer() {
  const [api, setApi] = useState<LogViewerAPI | null>(null);

  const handleReady = (logApi: LogViewerAPI) => {
    setApi(logApi);
    logApi.appendLog('Component ready!');
  };

  const handleAddLog = () => {
    api?.appendLog(`[${new Date().toISOString()}] New log message`);
  };

  return (
    <div>
      <button onClick={handleAddLog}>添加日志</button>
      <div style={{ height: '500px' }}>
        <MonacoLogViewer
          onReady={handleReady}
          autoScroll={true}
          theme="vs-dark"
        />
      </div>
    </div>
  );
}
```

## MonacoLogViewer Props

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| autoScroll | `boolean` | `true` | 是否自动滚动到底部 |
| fontSize | `number` | `12` | 字体大小 |
| theme | `'vs-dark' \| 'vs-light' \| 'hc-black'` | `'vs-dark'` | 主题 |
| height | `string \| number` | `'100%'` | 高度 |
| onReady | `(api: LogViewerAPI) => void` | - | 组件就绪回调，暴露API |
| className | `string` | `''` | 自定义类名 |
| style | `React.CSSProperties` | `{}` | 自定义样式 |

## LogStreamViewer Props

| 属性 | 类型 | 说明 |
|------|------|------|
| dataSource | `LogDataSource` | 日志数据源策略（必需） |
| customToolbar | `React.ReactNode` | 自定义控制栏 |
| monacoConfig | `Partial<MonacoLogViewerProps>` | Monaco配置 |
| onReady | `(controlApi, logApi) => void` | 就绪回调 |
| className | `string` | 自定义类名 |

## API 方法

通过 `onReady` 回调获取的 API 对象提供以下方法：

### LogViewerAPI

| 方法 | 参数 | 说明 |
|------|------|------|
| appendLog | `(log: string) => void` | 追加单条日志 |
| appendLogs | `(logs: string[]) => void` | 批量追加日志（性能优化） |
| clear | `() => void` | 清空所有日志 |
| scrollToBottom | `() => void` | 滚动到底部 |
| search | `(text: string) => void` | 搜索文本 |
| findNext | `() => void` | 查找下一个 |
| findPrevious | `() => void` | 查找上一个 |
| getLineCount | `() => number` | 获取当前日志行数 |

### LogStreamControlAPI

| 方法 | 说明 |
|------|------|
| connect | 连接数据源 |
| disconnect | 断开数据源 |
| send | 发送消息（WebSocket） |
| getStatus | 获取连接状态 |
| getError | 获取错误信息 |

## 完整示例：带控制栏的日志流

```tsx
import { 
  LogStreamViewer, 
  WebSocketLogSource,
  LogStreamStatus,
  type LogStreamControlAPI,
  type LogViewerAPI 
} from '@/components/LogViewer';
import { useState } from 'react';

function AdvancedLogViewer() {
  const [controlApi, setControlApi] = useState<LogStreamControlAPI | null>(null);
  const [logApi, setLogApi] = useState<LogViewerAPI | null>(null);
  const [status, setStatus] = useState<LogStreamStatus>(LogStreamStatus.DISCONNECTED);

  const dataSource = new WebSocketLogSource({
    url: 'ws://localhost:8080/logs',
    messageParser: (msg) => JSON.parse(msg).content,
    autoConnect: true,
  });

  dataSource.onStatusChange(setStatus);

  const handleReady = (ctrl: LogStreamControlAPI, log: LogViewerAPI) => {
    setControlApi(ctrl);
    setLogApi(log);
  };

  const customToolbar = (
    <div className="flex gap-2 p-2 border-b bg-gray-900">
      <button onClick={() => controlApi?.connect()}>连接</button>
      <button onClick={() => controlApi?.disconnect()}>断开</button>
      <button onClick={() => logApi?.clear()}>清空</button>
      <span className="ml-auto text-sm text-gray-400">
        状态: {status}
      </span>
    </div>
  );

  return (
    <div style={{ height: '600px' }}>
      <LogStreamViewer
        dataSource={dataSource}
        customToolbar={customToolbar}
        onReady={handleReady}
      />
    </div>
  );
}
```

## 创建自定义数据源

实现 `LogDataSource` 接口：

```tsx
import { LogDataSource, LogStreamStatus } from '@/components/LogViewer';

class CustomLogSource implements LogDataSource {
  private status = LogStreamStatus.DISCONNECTED;
  private error: string | null = null;
  private logCallback?: (log: string) => void;
  private statusCallback?: (status: LogStreamStatus) => void;
  private errorCallback?: (error: string) => void;

  connect() {
    this.status = LogStreamStatus.CONNECTING;
    this.statusCallback?.(this.status);
    
    // 实现连接逻辑
    // ...
    
    this.status = LogStreamStatus.CONNECTED;
    this.statusCallback?.(this.status);
  }

  disconnect() {
    // 实现断开逻辑
    this.status = LogStreamStatus.DISCONNECTED;
    this.statusCallback?.(this.status);
  }

  onLog(callback: (log: string) => void) {
    this.logCallback = callback;
  }

  onStatusChange(callback: (status: LogStreamStatus) => void) {
    this.statusCallback = callback;
  }

  onError(callback: (error: string) => void) {
    this.errorCallback = callback;
  }

  getStatus() {
    return this.status;
  }

  getError() {
    return this.error;
  }

  cleanup() {
    this.disconnect();
  }
}
```

## 性能优化建议

1. **批量追加日志**：使用 `appendLogs` 而不是多次调用 `appendLog`
   ```tsx
   // ❌ 不推荐
   logs.forEach(log => api.appendLog(log));
   
   // ✅ 推荐
   api.appendLogs(logs);
   ```

2. **限制日志行数**：定期清理旧日志
   ```tsx
   if (api.getLineCount() > 10000) {
     api.clear();
   }
   ```

3. **暂停自动滚动**：大量日志时可暂时关闭自动滚动
   ```tsx
   <MonacoLogViewer autoScroll={false} />
   ```

## 注意事项

1. 组件必须有明确的高度（通过父容器或 style 设置）
2. 组件是只读的，不支持用户输入
3. Monaco Editor会自动处理虚拟滚动，性能优秀
4. 搜索功能会打开Monaco的查找面板

## 故障排查

### 日志不显示
- 检查容器是否有高度
- 检查数据源是否正确连接
- 查看浏览器控制台是否有错误

### 性能问题
- 使用 `appendLogs` 批量追加
- 限制最大日志行数
- 考虑关闭自动滚动

### WebSocket连接失败
- 检查URL是否正确
- 检查messageParser是否正确解析消息
- 查看网络面板的WebSocket连接状态
