Browse Source

44444444444

feature/LteClientLogFun
root 1 month ago
parent
commit
67775dce29
  1. 164
      LTEMvcApp/Controllers/WebSocketController.cs
  2. 84
      LTEMvcApp/Services/WebSocketManagerService.cs
  3. 125
      LTEMvcApp/Views/Home/Logs.cshtml

164
LTEMvcApp/Controllers/WebSocketController.cs

@ -600,7 +600,7 @@ namespace LTEMvcApp.Controllers
{
var testLogs = new List<LTELog>
{
};
// 手动添加到日志缓存
@ -634,6 +634,7 @@ namespace LTEMvcApp.Controllers
int lastLogCount = 0;
var lastLogs = new List<LTELog>();
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<LTELog>();
var currentLogHash = CalculateLogsHash(currentLogs);
// 检查是否有新日志(通过数量和内容哈希双重检查)
bool hasNewLogs = false;
List<LTELog> newLogs = new List<LTELog>();
// 检查是否有新日志
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
}
}
/// <summary>
/// 计算日志列表的哈希值,用于检测内容变化
/// </summary>
/// <param name="logs">日志列表</param>
/// <returns>哈希值字符串</returns>
private string CalculateLogsHash(List<LTELog> 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;
}
}
/// <summary>
/// 获取变化的日志项
/// </summary>
/// <param name="oldLogs">旧日志列表</param>
/// <param name="newLogs">新日志列表</param>
/// <returns>变化的日志列表</returns>
private List<LTELog> GetChangedLogs(List<LTELog> oldLogs, List<LTELog> newLogs)
{
var changedLogs = new List<LTELog>();
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;
}
/// <summary>
/// 获取客户端消息日志文件列表
/// </summary>
@ -926,6 +1015,43 @@ namespace LTEMvcApp.Controllers
return StatusCode(500, new { message = "删除日志文件失败", error = ex.Message });
}
}
/// <summary>
/// 获取日志缓存详细状态(调试用)
/// </summary>
/// <returns>日志缓存详细状态</returns>
[HttpGet("logs/debug")]
public ActionResult<object> GetLogCacheDebugInfo()
{
try
{
var logs = _webSocketManager.GetLogCache()?.ToList() ?? new List<LTELog>();
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 });
}
}
}
/// <summary>

84
LTEMvcApp/Services/WebSocketManagerService.cs

@ -489,7 +489,14 @@ namespace LTEMvcApp.Services
/// <summary>
/// 获取当前缓存的日志
/// </summary>
public IEnumerable<LTELog> GetLogCache() => _logCache;
public IEnumerable<LTELog> GetLogCache()
{
// 使用线程安全的方式获取日志列表
lock (_logCache)
{
return _logCache.ToList();
}
}
/// <summary>
/// 获取当前缓存的日志总数
@ -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));

125
LTEMvcApp/Views/Home/Logs.cshtml

@ -537,6 +537,7 @@
<button class="btn-small" id="reset-logs-btn" title="重置日志">重置</button>
<button class="btn-small" id="add-test-data-btn" title="添加测试数据">测试</button>
<button class="btn-small" id="reconnect-btn" title="重新连接">重连</button>
<button class="btn-small" id="debug-btn" title="调试信息">调试</button>
<div class="column-settings">
<button class="btn-small" id="column-settings-btn" title="列设置">列设置</button>
<div class="column-settings-dropdown" id="column-settings-dropdown">
@ -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 = `<br><small class="text-muted">${title}</small>`;
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;

Loading…
Cancel
Save