diff --git a/LTEMvcApp/Controllers/WebSocketController.cs b/LTEMvcApp/Controllers/WebSocketController.cs index 4e2a5cd..01d2a88 100644 --- a/LTEMvcApp/Controllers/WebSocketController.cs +++ b/LTEMvcApp/Controllers/WebSocketController.cs @@ -600,7 +600,7 @@ namespace LTEMvcApp.Controllers { var testLogs = new List { - + }; // 手动添加到日志缓存 @@ -634,6 +634,7 @@ namespace LTEMvcApp.Controllers int lastLogCount = 0; var lastLogs = new List(); + var lastLogHash = string.Empty; // 添加日志内容哈希值用于比较 // 首先,一次性推送所有已缓存的日志 try @@ -648,6 +649,7 @@ namespace LTEMvcApp.Controllers await Response.Body.FlushAsync(cancellationToken); lastLogCount = initialLogs.Count; lastLogs = initialLogs.ToList(); // 保存副本用于比较 + lastLogHash = CalculateLogsHash(initialLogs); // 计算初始日志哈希值 } else { @@ -666,27 +668,24 @@ namespace LTEMvcApp.Controllers try { var currentLogs = _webSocketManager.GetLogCache()?.ToList() ?? new List(); + var currentLogHash = CalculateLogsHash(currentLogs); + + // 检查是否有新日志(通过数量和内容哈希双重检查) + bool hasNewLogs = false; + List newLogs = new List(); - // 检查是否有新日志 if (currentLogs.Count > lastLogCount) { - // 计算新增的日志 - var newLogs = currentLogs.Skip(lastLogCount).ToList(); - - if (newLogs.Any()) - { - _logger.LogInformation("StreamLogs: 发送新日志事件,新增日志数量: {NewCount}, 总日志数量: {TotalCount}", newLogs.Count, currentLogs.Count); - await SendSseEvent("new_logs", new { - logs = newLogs, - totalCount = currentLogs.Count, - newCount = newLogs.Count - }); - await Response.Body.FlushAsync(cancellationToken); - - // 更新索引和缓存 - lastLogCount = currentLogs.Count; - lastLogs = currentLogs.ToList(); - } + // 数量增加,计算新增的日志 + newLogs = currentLogs.Skip(lastLogCount).ToList(); + hasNewLogs = newLogs.Any(); + } + else if (currentLogs.Count == lastLogCount && currentLogHash != lastLogHash) + { + // 数量相同但内容变化,可能是日志被替换或更新 + // 比较每个日志项,找出变化的日志 + newLogs = GetChangedLogs(lastLogs, currentLogs); + hasNewLogs = newLogs.Any(); } else if (currentLogs.Count < lastLogCount) { @@ -700,6 +699,24 @@ namespace LTEMvcApp.Controllers lastLogCount = currentLogs.Count; lastLogs = currentLogs.ToList(); + lastLogHash = currentLogHash; + continue; // 跳过本次循环,等待下次检查 + } + + if (hasNewLogs && newLogs.Any()) + { + _logger.LogInformation("StreamLogs: 发送新日志事件,新增日志数量: {NewCount}, 总日志数量: {TotalCount}", newLogs.Count, currentLogs.Count); + await SendSseEvent("new_logs", new { + logs = newLogs, + totalCount = currentLogs.Count, + newCount = newLogs.Count + }); + await Response.Body.FlushAsync(cancellationToken); + + // 更新索引和缓存 + lastLogCount = currentLogs.Count; + lastLogs = currentLogs.ToList(); + lastLogHash = currentLogHash; } await Task.Delay(250, cancellationToken); @@ -754,6 +771,78 @@ namespace LTEMvcApp.Controllers } } + /// + /// 计算日志列表的哈希值,用于检测内容变化 + /// + /// 日志列表 + /// 哈希值字符串 + private string CalculateLogsHash(List logs) + { + if (logs == null || !logs.Any()) + return string.Empty; + + try + { + // 使用日志的关键字段计算哈希值 + var hashInput = string.Join("|", logs.Select(log => + $"{log.Timestamp}_{log.Layer}_{log.Message}")); + + using (var sha256 = System.Security.Cryptography.SHA256.Create()) + { + var bytes = System.Text.Encoding.UTF8.GetBytes(hashInput); + var hash = sha256.ComputeHash(bytes); + return Convert.ToBase64String(hash); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "计算日志哈希值时发生错误"); + return string.Empty; + } + } + + /// + /// 获取变化的日志项 + /// + /// 旧日志列表 + /// 新日志列表 + /// 变化的日志列表 + private List GetChangedLogs(List oldLogs, List newLogs) + { + var changedLogs = new List(); + + if (oldLogs.Count != newLogs.Count) + { + // 数量不同,返回所有新日志 + return newLogs; + } + + // 比较每个日志项 + for (int i = 0; i < newLogs.Count; i++) + { + if (i < oldLogs.Count) + { + var oldLog = oldLogs[i]; + var newLog = newLogs[i]; + + // 比较关键字段 + if (oldLog.Timestamp != newLog.Timestamp || + oldLog.Layer != newLog.Layer || + oldLog.Message != newLog.Message) + { + changedLogs.Add(newLog); + } + } + else + { + // 新增的日志 + changedLogs.Add(newLogs[i]); + } + } + + return changedLogs; + } + /// /// 获取客户端消息日志文件列表 /// @@ -926,6 +1015,43 @@ namespace LTEMvcApp.Controllers return StatusCode(500, new { message = "删除日志文件失败", error = ex.Message }); } } + + /// + /// 获取日志缓存详细状态(调试用) + /// + /// 日志缓存详细状态 + [HttpGet("logs/debug")] + public ActionResult GetLogCacheDebugInfo() + { + try + { + var logs = _webSocketManager.GetLogCache()?.ToList() ?? new List(); + var logCount = _webSocketManager.GetLogCacheCount(); + + var debugInfo = new + { + totalLogs = logCount, + actualLogsCount = logs.Count, + cacheSize = 10000, // LogCacheSize + timestamp = DateTime.UtcNow, + sampleLogs = logs.TakeLast(5).Select(log => new + { + timestamp = log.Timestamp, + layer = log.Layer, + //level = log.Level, + message = log.Message?.Substring(0, Math.Min(100, log.Message?.Length ?? 0)) + "..." + }).ToList(), + logHash = CalculateLogsHash(logs) + }; + + return Ok(debugInfo); + } + catch (Exception ex) + { + _logger.LogError(ex, "获取日志缓存调试信息时发生错误"); + return StatusCode(500, new { message = "获取调试信息失败", error = ex.Message }); + } + } } /// diff --git a/LTEMvcApp/Services/WebSocketManagerService.cs b/LTEMvcApp/Services/WebSocketManagerService.cs index ad19a0e..fe2353c 100644 --- a/LTEMvcApp/Services/WebSocketManagerService.cs +++ b/LTEMvcApp/Services/WebSocketManagerService.cs @@ -489,7 +489,14 @@ namespace LTEMvcApp.Services /// /// 获取当前缓存的日志 /// - public IEnumerable GetLogCache() => _logCache; + public IEnumerable GetLogCache() + { + // 使用线程安全的方式获取日志列表 + lock (_logCache) + { + return _logCache.ToList(); + } + } /// /// 获取当前缓存的日志总数 @@ -502,9 +509,12 @@ namespace LTEMvcApp.Services public void ClearLogCache() { _logger.LogInformation("清空日志缓存"); - while (_logCache.TryDequeue(out _)) + lock (_logCache) { - // 清空所有日志 + while (_logCache.TryDequeue(out _)) + { + // 清空所有日志 + } } } @@ -526,13 +536,16 @@ namespace LTEMvcApp.Services { if (log != null) { - _logCache.Enqueue(log); - _logger.LogInformation("手动添加日志到缓存: {Layer} - {Message}", log.Layer, log.Message); - - // 维持缓存大小 - while (_logCache.Count > LogCacheSize) + lock (_logCache) { - _logCache.TryDequeue(out _); + _logCache.Enqueue(log); + _logger.LogDebug("手动添加日志到缓存: {Layer} - {Message}", log.Layer, log.Message); + + // 维持缓存大小 + while (_logCache.Count > LogCacheSize) + { + _logCache.TryDequeue(out _); + } } } } @@ -545,17 +558,20 @@ namespace LTEMvcApp.Services { if (logs != null && logs.Any()) { - foreach (var log in logs) - { - _logCache.Enqueue(log); - } - - _logger.LogInformation("手动添加 {Count} 条日志到缓存", logs.Count); - - // 维持缓存大小 - while (_logCache.Count > LogCacheSize) + lock (_logCache) { - _logCache.TryDequeue(out _); + foreach (var log in logs) + { + _logCache.Enqueue(log); + } + + _logger.LogInformation("手动添加 {Count} 条日志到缓存", logs.Count); + + // 维持缓存大小 + while (_logCache.Count > LogCacheSize) + { + _logCache.TryDequeue(out _); + } } } } @@ -589,16 +605,28 @@ namespace LTEMvcApp.Services { _logger.LogInformation($"客户端 {clientName} 收到日志: {logs.Count} 条"); - // 将新日志存入中央缓存 - foreach (var log in logs) - { - _logCache.Enqueue(log); - } - - // 维持缓存大小 - while (_logCache.Count > LogCacheSize) + if (logs != null && logs.Any()) { - _logCache.TryDequeue(out _); + lock (_logCache) + { + // 将新日志存入中央缓存 + foreach (var log in logs) + { + if (log != null) + { + _logCache.Enqueue(log); + _logger.LogDebug($"客户端 {clientName} 添加日志到缓存: {log.Layer} - {log.Message}"); + } + } + + // 维持缓存大小 + while (_logCache.Count > LogCacheSize) + { + _logCache.TryDequeue(out _); + } + + _logger.LogInformation($"客户端 {clientName} 日志已添加到缓存,当前缓存总数: {_logCache.Count}"); + } } LogsReceived?.Invoke(this, (clientName, logs)); diff --git a/LTEMvcApp/Views/Home/Logs.cshtml b/LTEMvcApp/Views/Home/Logs.cshtml index ab0a144..e4bca63 100644 --- a/LTEMvcApp/Views/Home/Logs.cshtml +++ b/LTEMvcApp/Views/Home/Logs.cshtml @@ -537,6 +537,7 @@ +
@@ -620,6 +621,7 @@ const resetLogsBtn = document.getElementById('reset-logs-btn'); const addTestDataBtn = document.getElementById('add-test-data-btn'); const reconnectBtn = document.getElementById('reconnect-btn'); + const debugBtn = document.getElementById('debug-btn'); const logListPanel = document.querySelector('.log-list-panel'); const resizer = document.getElementById('drag-resizer'); const layerFilterOptions = document.getElementById('layer-filter-options'); @@ -931,13 +933,30 @@ let note = ''; if (log && typeof log.Timestamp === 'number') { - timestamp = formatDuration(log.Timestamp); - title = `Duration from start: ${timestamp} (Raw: ${log.Timestamp}ms)`; + // 检查是否为 Unix 时间戳(13位数字,通常大于 1000000000000) + if (log.Timestamp > 1000000000000) { + // Unix 时间戳,转换为本地时间 + const date = new Date(log.Timestamp); + timestamp = date.toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + fractionalSecondDigits: 3 + }); + title = `Unix 时间戳: ${log.Timestamp} -> ${date.toISOString()}`; + } else { + // 持续时间(毫秒数) + timestamp = formatDuration(log.Timestamp); + title = `持续时间: ${timestamp} (${log.Timestamp}ms)`; + } } else { timestamp = 'Invalid Time'; - title = 'Timestamp from server was invalid or missing.'; + title = '时间戳无效或缺失'; note = `
${title}`; - console.warn('Timestamp missing or invalid, using fallback for log:', log); + console.warn('时间戳缺失或无效,使用默认值:', log); } return { timestamp, title, note }; @@ -960,7 +979,7 @@ } // 更新日志列表 - function updateLogList(logs, prepend = false) { + function updateLogList(logs, prepend = false, isHistory = false) { if (!logs || logs.length === 0) return; // 收集新的日志层 @@ -973,25 +992,35 @@ // 更新过滤器选项 updateLayerFilter(); - const newRows = logs.map((log, i) => formatLogItem(log, allLogsData.length + i)); - - if (prepend) { - clusterize.prepend(newRows); + if (isHistory) { + // 历史日志:直接替换所有数据 + allLogsData = [...logs]; + const rows = logs.map((log, i) => formatLogItem(log, i)); + clusterize.clear(); + clusterize.append(rows); + showInfo(`加载了 ${logs.length} 条历史日志`); } else { - clusterize.append(newRows); + // 新日志:添加到现有数据 + const newRows = logs.map((log, i) => formatLogItem(log, allLogsData.length + i)); + + if (prepend) { + clusterize.prepend(newRows); + } else { + clusterize.append(newRows); + } + allLogsData.push(...logs); + + // 显示新日志数量 + if (!prepend) { + newLogsCountEl.textContent = `+${logs.length}`; + setTimeout(() => { + newLogsCountEl.textContent = ''; + }, 2000); + } } - allLogsData.push(...logs); // 根据当前过滤器更新显示 refreshLogList(); - - // 显示新日志数量 - if (!prepend) { - newLogsCountEl.textContent = `+${logs.length}`; - setTimeout(() => { - newLogsCountEl.textContent = ''; - }, 2000); - } } // 显示日志详情 @@ -1039,24 +1068,25 @@ } updateConnectionStatus('connecting', '连接中...'); + console.log('开始建立SSE连接...'); eventSource = new EventSource('/api/websocket/logs/stream'); eventSource.addEventListener('connected', function(event) { - console.log("SSE连接已建立"); + console.log("SSE连接已建立", event); updateConnectionStatus('connected', '已连接'); showInfo('日志流连接已建立'); reconnectAttempts = 0; // 重置重连计数 }); eventSource.addEventListener('history', function(event) { - console.log("接收到历史日志...", event.data); + console.log("接收到历史日志事件", event); try { const data = JSON.parse(event.data); console.log("历史日志数据:", data); if (data.logs && Array.isArray(data.logs)) { - updateLogList(data.logs); - showInfo(`加载了 ${data.logs.length} 条历史日志`); + console.log(`处理 ${data.logs.length} 条历史日志`); + updateLogList(data.logs, false, true); // 标记为历史日志 } else { console.warn("历史日志数据格式不正确:", data); showError("历史日志数据格式不正确"); @@ -1068,12 +1098,13 @@ }); eventSource.addEventListener('new_logs', function(event) { - console.log("接收到新日志...", event.data); + console.log("接收到新日志事件", event); try { const data = JSON.parse(event.data); console.log("新日志数据:", data); if (data.logs && Array.isArray(data.logs)) { - updateLogList(data.logs); + console.log(`处理 ${data.logs.length} 条新日志`); + updateLogList(data.logs, false, false); // 标记为新日志 } else { console.warn("新日志数据格式不正确:", data); } @@ -1084,9 +1115,10 @@ }); eventSource.addEventListener('reset', function(event) { - console.log("日志缓存已重置"); + console.log("接收到重置事件", event); try { const data = JSON.parse(event.data); + console.log("重置事件数据:", data); clearLogsDisplay(); showInfo('日志缓存已重置,等待新日志以建立时间线'); } catch (error) { @@ -1095,9 +1127,10 @@ }); eventSource.addEventListener('error', function(event) { - console.log("接收到错误事件"); + console.log("接收到错误事件", event); try { const data = JSON.parse(event.data); + console.log("错误事件数据:", data); updateConnectionStatus('error', '连接错误'); showError(`连接错误: ${data.message}`); } catch (error) { @@ -1106,15 +1139,16 @@ }); eventSource.addEventListener('disconnected', function(event) { - console.log("连接已断开"); + console.log("接收到断开连接事件", event); updateConnectionStatus('disconnected', '已断开'); showInfo('日志流连接已断开'); }); eventSource.addEventListener('fatal_error', function(event) { - console.error("致命错误"); + console.error("接收到致命错误事件", event); try { const data = JSON.parse(event.data); + console.log("致命错误事件数据:", data); updateConnectionStatus('error', '服务器错误'); showError(`服务器错误: ${data.message}`); } catch (error) { @@ -1123,15 +1157,17 @@ }); eventSource.onerror = function (err) { - console.error("SSE 错误:", err); + console.error("SSE 连接错误:", err); updateConnectionStatus('error', '连接失败'); // 自动重连 if (reconnectAttempts < maxReconnectAttempts) { reconnectAttempts++; + console.log(`连接失败,${reconnectDelay/1000}秒后重试 (${reconnectAttempts}/${maxReconnectAttempts})`); showError(`连接失败,${reconnectDelay/1000}秒后重试 (${reconnectAttempts}/${maxReconnectAttempts})`); setTimeout(connectSSE, reconnectDelay); } else { + console.log('连接失败,已达到最大重试次数'); showError('连接失败,已达到最大重试次数'); updateConnectionStatus('disconnected', '连接失败'); } @@ -1225,6 +1261,35 @@ connectSSE(); }); + debugBtn.addEventListener('click', function() { + // 显示调试信息 + const debugInfo = { + totalLogs: allLogsData.length, + availableLayers: Array.from(availableLayers), + selectedLayers: Array.from(selectedLayers), + connectionStatus: statusText.textContent, + eventSourceReadyState: eventSource ? eventSource.readyState : 'null', + sortField: sortField, + sortDirection: sortDirection, + columnVisibility: columnVisibility, + clusterizeRows: clusterize.rows.length + }; + + console.log('调试信息:', debugInfo); + + // 检查服务器端日志缓存状态 + fetch('/api/websocket/logs/debug') + .then(response => response.json()) + .then(data => { + console.log('服务器端日志缓存状态:', data); + showInfo(`调试信息已输出到控制台。服务器缓存: ${data.totalLogs} 条日志`); + }) + .catch(error => { + console.error('获取服务器调试信息失败:', error); + showError('获取服务器调试信息失败: ' + error.message); + }); + }); + // --- Resizer Logic --- let isResizing = false;