// 实时日志查看页面JavaScript class LogViewer { constructor() { this.eventSource = null; this.logs = []; this.maxLogs = 1000; // 最多保留1000条日志 this.autoScroll = true; this.currentFilter = { level: '', keyword: '' }; this.init(); } async init() { console.log('初始化日志查看器...'); // 绑定事件 this.bindEvents(); // 加载日志文件列表 await this.loadLogFiles(); // 加载最近日志 await this.loadRecentLogs(); console.log('日志查看器初始化完成'); } bindEvents() { // 开始/停止实时日志流 document.getElementById('startStreamBtn').addEventListener('click', () => { this.startStream(); }); document.getElementById('stopStreamBtn').addEventListener('click', () => { this.stopStream(); }); // 清空日志 document.getElementById('clearLogsBtn').addEventListener('click', () => { this.clearLogs(); }); // 自动滚动切换 document.getElementById('autoScrollCheck').addEventListener('change', (e) => { this.autoScroll = e.target.checked; if (this.autoScroll) { this.scrollToBottom(); } }); // 日志级别过滤 document.getElementById('logLevelFilter').addEventListener('change', (e) => { this.currentFilter.level = e.target.value; this.renderLogs(); }); // 搜索 document.getElementById('searchBtn').addEventListener('click', () => { this.searchLogs(); }); document.getElementById('searchKeyword').addEventListener('keypress', (e) => { if (e.key === 'Enter') { this.searchLogs(); } }); // 日志文件选择 document.getElementById('logFileSelect').addEventListener('change', async (e) => { const filename = e.target.value; if (filename) { await this.loadLogFile(filename); } else { await this.loadRecentLogs(); } }); } async loadLogFiles() { try { const response = await fetch('/api/history/logs/files?days=7'); const result = await response.json(); if (result.success) { const select = document.getElementById('logFileSelect'); // 保留"今日日志"选项 select.innerHTML = ''; result.data.forEach(file => { const option = document.createElement('option'); option.value = file.filename; option.textContent = `${file.date} (${this.formatFileSize(file.size)})`; select.appendChild(option); }); } } catch (error) { console.error('加载日志文件列表失败:', error); this.showAlert('加载日志文件列表失败', 'danger'); } } async loadRecentLogs() { try { const response = await fetch('/api/history/logs/recent?limit=100'); const result = await response.json(); if (result.success) { this.logs = result.data.map(log => ({ timestamp: log.timestamp, level: log.level.toUpperCase(), message: log.message })); this.renderLogs(); this.updateLogCount(); } } catch (error) { console.error('加载最近日志失败:', error); this.showAlert('加载日志失败', 'danger'); } } async loadLogFile(filename) { try { const response = await fetch(`/api/history/logs/file/${filename}?limit=500`); const result = await response.json(); if (result.success) { this.logs = result.data.map(log => ({ timestamp: log.timestamp, level: log.level.toUpperCase(), message: log.message })); this.renderLogs(); this.updateLogCount(); } } catch (error) { console.error('加载日志文件失败:', error); this.showAlert('加载日志文件失败', 'danger'); } } startStream() { if (this.eventSource) { return; // 已经在运行 } try { this.eventSource = new EventSource('/api/history/logs/stream'); this.eventSource.onopen = () => { this.updateConnectionStatus(true); document.getElementById('startStreamBtn').disabled = true; document.getElementById('stopStreamBtn').disabled = false; this.showAlert('实时日志流已连接', 'success'); }; this.eventSource.onmessage = (event) => { try { const data = JSON.parse(event.data); if (data.type === 'connected') { console.log('实时日志流连接成功'); } else if (data.type === 'log') { this.addLog(data.data); } else if (data.type === 'heartbeat') { // 心跳包,保持连接 } } catch (error) { console.error('解析日志数据失败:', error); } }; this.eventSource.onerror = (error) => { console.error('实时日志流错误:', error); this.updateConnectionStatus(false); this.stopStream(); this.showAlert('实时日志流连接断开', 'warning'); }; } catch (error) { console.error('启动实时日志流失败:', error); this.showAlert('启动实时日志流失败: ' + error.message, 'danger'); } } stopStream() { if (this.eventSource) { this.eventSource.close(); this.eventSource = null; this.updateConnectionStatus(false); document.getElementById('startStreamBtn').disabled = false; document.getElementById('stopStreamBtn').disabled = true; this.showAlert('实时日志流已停止', 'info'); } } addLog(logEntry) { // 添加新日志 this.logs.push({ timestamp: logEntry.timestamp, level: logEntry.level ? logEntry.level.toUpperCase() : 'INFO', message: logEntry.message }); // 限制日志数量 if (this.logs.length > this.maxLogs) { this.logs.shift(); } // 更新显示 this.renderLogs(); this.updateLogCount(); // 自动滚动 if (this.autoScroll) { setTimeout(() => this.scrollToBottom(), 10); } } renderLogs() { const container = document.getElementById('logContainer'); // 过滤日志 let filteredLogs = this.logs; if (this.currentFilter.level) { filteredLogs = filteredLogs.filter(log => log.level === this.currentFilter.level); } if (this.currentFilter.keyword) { const keyword = this.currentFilter.keyword.toLowerCase(); filteredLogs = filteredLogs.filter(log => log.message.toLowerCase().includes(keyword) || log.timestamp.toLowerCase().includes(keyword) ); } // 渲染日志 container.innerHTML = filteredLogs.map(log => { const levelClass = log.level || 'INFO'; const messageClass = log.level ? log.level.toLowerCase() : 'info'; return `
${log.timestamp || ''} ${levelClass} ${this.escapeHtml(log.message)}
`; }).join(''); } clearLogs() { if (confirm('确定要清空所有日志吗?')) { this.logs = []; this.renderLogs(); this.updateLogCount(); this.showAlert('日志已清空', 'info'); } } searchLogs() { const keyword = document.getElementById('searchKeyword').value.trim(); this.currentFilter.keyword = keyword; this.renderLogs(); } scrollToBottom() { const container = document.getElementById('logContainer'); container.scrollTop = container.scrollHeight; // 显示自动滚动指示器 const indicator = document.getElementById('autoScrollIndicator'); if (this.autoScroll) { indicator.classList.add('active'); setTimeout(() => { indicator.classList.remove('active'); }, 2000); } } updateLogCount() { const count = this.logs.length; document.getElementById('logCount').textContent = count; } updateConnectionStatus(connected) { const indicator = document.getElementById('connectionStatus'); const text = document.getElementById('connectionStatusText'); if (connected) { indicator.className = 'status-indicator online'; text.textContent = '已连接'; } else { indicator.className = 'status-indicator offline'; text.textContent = '未连接'; } } formatFileSize(bytes) { if (bytes < 1024) return bytes + ' B'; if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB'; return (bytes / (1024 * 1024)).toFixed(2) + ' MB'; } escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } showAlert(message, type = 'info') { const alertContainer = document.getElementById('alertContainer'); if (!alertContainer) return; const alertId = 'alert-' + Date.now(); const alertHtml = ` `; alertContainer.insertAdjacentHTML('beforeend', alertHtml); // 自动移除提示 setTimeout(() => { const alertElement = document.getElementById(alertId); if (alertElement) { alertElement.remove(); } }, 3000); } getAlertIcon(type) { const icons = { 'success': 'check-circle-fill', 'danger': 'exclamation-triangle-fill', 'warning': 'exclamation-triangle-fill', 'info': 'info-circle-fill' }; return icons[type] || 'info-circle-fill'; } } // 全局变量 let logViewer; // 页面加载完成后初始化 document.addEventListener('DOMContentLoaded', () => { logViewer = new LogViewer(); }); // 页面卸载时清理 window.addEventListener('beforeunload', () => { if (logViewer) { logViewer.stopStream(); } });