Browse Source

优化 StreamLogs 方法和前端页面 - 修复线程安全问题、性能问题和错误处理 - 添加连接状态指示器、自动重连机制和控制按钮 - 新增日志缓存管理API端点 - 完善SSE事件处理和用户体验

master
root 1 month ago
parent
commit
ef1168195c
  1. 383
      LTEMvcApp/Controllers/WebSocketController.cs
  2. 175
      LTEMvcApp/README_StreamLogs_Optimization.md
  3. 22
      LTEMvcApp/Services/WebSocketManagerService.cs
  4. 295
      LTEMvcApp/Views/Home/Logs.cshtml

383
LTEMvcApp/Controllers/WebSocketController.cs

@ -253,67 +253,169 @@ namespace LTEMvcApp.Controllers
[HttpGet("clients/{clientName}/messages/stream")]
public async Task StreamClientMessages(string clientName)
{
Response.ContentType = "text/event-stream";
Response.Headers.Add("Cache-Control", "no-cache");
Response.Headers.Add("Connection", "keep-alive");
var client = _webSocketManager.GetClientInstance(clientName);
if (client == null)
try
{
// 发送一个错误事件然后关闭
await SendSseEvent("error", new { message = "客户端未连接或不存在" });
return;
}
Response.ContentType = "text/event-stream";
Response.Headers.Append("Cache-Control", "no-cache");
Response.Headers.Append("Connection", "keep-alive");
Response.Headers.Append("Access-Control-Allow-Origin", "*");
// 发送一个连接成功事件
await SendSseEvent("open", new { message = "成功连接到服务器事件流" });
var client = _webSocketManager.GetClientInstance(clientName);
if (client == null)
{
// 发送一个错误事件然后关闭
await SendSseEvent("error", new { message = "客户端未连接或不存在", clientName });
return;
}
int sentIndex = 0;
int receivedIndex = 0;
var cancellationToken = HttpContext.RequestAborted;
// 发送一个连接成功事件
await SendSseEvent("open", new {
message = "成功连接到服务器事件流",
clientName,
timestamp = DateTime.UtcNow
});
await Response.Body.FlushAsync(HttpContext.RequestAborted);
while (!cancellationToken.IsCancellationRequested)
{
bool hasNewMessages = false;
int lastSentCount = 0;
int lastReceivedCount = 0;
var cancellationToken = HttpContext.RequestAborted;
// 检查并高效地发送新的"已发送"消息
if (client.SentMessagesCount > sentIndex)
while (!cancellationToken.IsCancellationRequested)
{
var newMessages = client.SentMessages.Skip(sentIndex).ToList();
if (newMessages.Any())
try
{
await SendSseEvent("update", new { type = "sent", messages = newMessages, totalCount = client.SentMessagesCount });
sentIndex = client.SentMessagesCount;
hasNewMessages = true;
bool hasNewMessages = false;
// 检查并高效地发送新的"已发送"消息
var currentSentCount = client.SentMessagesCount;
if (currentSentCount > lastSentCount)
{
var sentMessages = client.SentMessages?.ToList() ?? new List<string>();
if (sentMessages.Count > lastSentCount)
{
var newMessages = sentMessages.Skip(lastSentCount).ToList();
if (newMessages.Any())
{
await SendSseEvent("update", new {
type = "sent",
messages = newMessages,
totalCount = currentSentCount,
newCount = newMessages.Count
});
lastSentCount = currentSentCount;
hasNewMessages = true;
}
}
}
// 检查并高效地发送新的"已接收"消息
var currentReceivedCount = client.ReceivedMessagesCount;
if (currentReceivedCount > lastReceivedCount)
{
var receivedMessages = client.ReceivedMessages?.ToList() ?? new List<string>();
if (receivedMessages.Count > lastReceivedCount)
{
var newMessages = receivedMessages.Skip(lastReceivedCount).ToList();
if (newMessages.Any())
{
await SendSseEvent("update", new {
type = "received",
messages = newMessages,
totalCount = currentReceivedCount,
newCount = newMessages.Count
});
lastReceivedCount = currentReceivedCount;
hasNewMessages = true;
}
}
}
if (hasNewMessages)
{
await Response.Body.FlushAsync(cancellationToken);
}
await Task.Delay(250, cancellationToken); // 每250毫秒检查一次新消息
}
}
// 检查并高效地发送新的"已接收"消息
if (client.ReceivedMessagesCount > receivedIndex)
{
var newMessages = client.ReceivedMessages.Skip(receivedIndex).ToList();
if (newMessages.Any())
catch (OperationCanceledException)
{
// 正常的取消操作,退出循环
break;
}
catch (Exception ex)
{
await SendSseEvent("update", new { type = "received", messages = newMessages, totalCount = client.ReceivedMessagesCount });
receivedIndex = client.ReceivedMessagesCount;
hasNewMessages = true;
_logger.LogError(ex, "StreamClientMessages 循环中发生错误,客户端: {ClientName}", clientName);
await SendSseEvent("error", new {
message = "处理消息流时发生错误",
error = ex.Message,
clientName,
timestamp = DateTime.UtcNow
});
await Response.Body.FlushAsync(cancellationToken);
// 等待一段时间后继续,避免频繁错误
await Task.Delay(1000, cancellationToken);
}
}
if (hasNewMessages)
// 发送断开连接事件
await SendSseEvent("disconnected", new {
message = "客户端消息流连接已断开",
clientName,
timestamp = DateTime.UtcNow
});
await Response.Body.FlushAsync(cancellationToken);
}
catch (OperationCanceledException)
{
_logger.LogInformation("StreamClientMessages 连接被客户端取消,客户端: {ClientName}", clientName);
}
catch (Exception ex)
{
_logger.LogError(ex, "StreamClientMessages 方法执行时发生未处理的异常,客户端: {ClientName}", clientName);
try
{
await Response.Body.FlushAsync(cancellationToken);
await SendSseEvent("fatal_error", new {
message = "服务器内部错误",
error = ex.Message,
clientName,
timestamp = DateTime.UtcNow
});
await Response.Body.FlushAsync();
}
catch
{
// 忽略发送错误事件时的异常
}
await Task.Delay(250, cancellationToken); // 每250毫秒检查一次新消息
}
}
private async Task SendSseEvent(string eventName, object data)
{
var json = Newtonsoft.Json.JsonConvert.SerializeObject(data);
await Response.WriteAsync($"event: {eventName}\n");
await Response.WriteAsync($"data: {json}\n\n");
try
{
if (string.IsNullOrEmpty(eventName))
{
_logger.LogWarning("尝试发送空事件名称的SSE事件");
return;
}
if (data == null)
{
_logger.LogWarning("尝试发送空数据的SSE事件: {EventName}", eventName);
return;
}
var json = Newtonsoft.Json.JsonConvert.SerializeObject(data);
var eventData = $"event: {eventName}\ndata: {json}\n\n";
await Response.WriteAsync(eventData);
}
catch (Exception ex)
{
_logger.LogError(ex, "发送SSE事件时发生错误: {EventName}", eventName);
// 不重新抛出异常,避免影响整个流
}
}
/// <summary>
@ -375,40 +477,195 @@ namespace LTEMvcApp.Controllers
}
/// <summary>
/// 使用 Server-Sent Events (SSE) 实时推送全局日志
/// 获取日志缓存统计信息
/// </summary>
[HttpGet("logs/stream")]
public async Task StreamLogs(CancellationToken cancellationToken)
/// <returns>统计信息</returns>
[HttpGet("logs/stats")]
public ActionResult<object> GetLogCacheStats()
{
Response.ContentType = "text/event-stream";
Response.Headers.Append("Cache-Control", "no-cache");
Response.Headers.Append("Connection", "keep-alive");
try
{
var stats = new
{
totalLogs = _webSocketManager.GetLogCacheCount(),
cacheSize = 10000, // LogCacheSize
timestamp = DateTime.UtcNow
};
return Ok(stats);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取日志缓存统计信息时发生错误");
return StatusCode(500, new { message = "获取统计信息失败", error = ex.Message });
}
}
int logIndex = 0;
/// <summary>
/// 清空全局日志缓存
/// </summary>
/// <returns>操作结果</returns>
[HttpPost("logs/clear")]
public ActionResult ClearLogCache()
{
try
{
_webSocketManager.ClearLogCache();
return Ok(new { message = "日志缓存已清空" });
}
catch (Exception ex)
{
_logger.LogError(ex, "清空日志缓存时发生错误");
return StatusCode(500, new { message = "清空日志缓存失败", error = ex.Message });
}
}
// 首先,一次性推送所有已缓存的日志
var initialLogs = _webSocketManager.GetLogCache().ToList();
if (initialLogs.Any())
/// <summary>
/// 重置全局日志缓存
/// </summary>
/// <returns>操作结果</returns>
[HttpPost("logs/reset")]
public ActionResult ResetLogCache()
{
try
{
await SendSseEvent("history", new { logs = initialLogs });
await Response.Body.FlushAsync(cancellationToken);
logIndex = initialLogs.Count;
_webSocketManager.ResetLogCache();
return Ok(new { message = "日志缓存已重置" });
}
catch (Exception ex)
{
_logger.LogError(ex, "重置日志缓存时发生错误");
return StatusCode(500, new { message = "重置日志缓存失败", error = ex.Message });
}
}
while (!cancellationToken.IsCancellationRequested)
/// <summary>
/// 使用 Server-Sent Events (SSE) 实时推送全局日志
/// </summary>
[HttpGet("logs/stream")]
public async Task StreamLogs(CancellationToken cancellationToken)
{
try
{
var logCache = _webSocketManager.GetLogCache();
if (logCache.Count() > logIndex)
Response.ContentType = "text/event-stream";
Response.Headers.Append("Cache-Control", "no-cache");
Response.Headers.Append("Connection", "keep-alive");
Response.Headers.Append("Access-Control-Allow-Origin", "*");
// 发送连接成功事件
await SendSseEvent("connected", new { message = "日志流连接已建立", timestamp = DateTime.UtcNow });
await Response.Body.FlushAsync(cancellationToken);
int lastLogCount = 0;
var lastLogs = new List<LTELog>();
// 首先,一次性推送所有已缓存的日志
try
{
var newLogs = logCache.Skip(logIndex).ToList();
if (newLogs.Any())
var initialLogs = _webSocketManager.GetLogCache()?.ToList() ?? new List<LTELog>();
if (initialLogs.Any())
{
await SendSseEvent("new_logs", new { logs = newLogs });
await SendSseEvent("history", new { logs = initialLogs, totalCount = initialLogs.Count });
await Response.Body.FlushAsync(cancellationToken);
logIndex = logCache.Count();
lastLogCount = initialLogs.Count;
lastLogs = initialLogs.ToList(); // 保存副本用于比较
}
}
await Task.Delay(250, cancellationToken);
catch (Exception ex)
{
_logger.LogError(ex, "获取初始日志时发生错误");
await SendSseEvent("error", new { message = "获取初始日志失败", error = ex.Message });
await Response.Body.FlushAsync(cancellationToken);
}
while (!cancellationToken.IsCancellationRequested)
{
try
{
var currentLogs = _webSocketManager.GetLogCache()?.ToList() ?? new List<LTELog>();
// 检查是否有新日志
if (currentLogs.Count > lastLogCount)
{
// 计算新增的日志
var newLogs = currentLogs.Skip(lastLogCount).ToList();
if (newLogs.Any())
{
await SendSseEvent("new_logs", new {
logs = newLogs,
totalCount = currentLogs.Count,
newCount = newLogs.Count
});
await Response.Body.FlushAsync(cancellationToken);
// 更新索引和缓存
lastLogCount = currentLogs.Count;
lastLogs = currentLogs.ToList();
}
}
else if (currentLogs.Count < lastLogCount)
{
// 日志被清空或重置的情况
_logger.LogInformation("检测到日志缓存被重置,重新同步");
await SendSseEvent("reset", new {
message = "日志缓存已重置",
totalCount = currentLogs.Count
});
await Response.Body.FlushAsync(cancellationToken);
lastLogCount = currentLogs.Count;
lastLogs = currentLogs.ToList();
}
await Task.Delay(250, cancellationToken);
}
catch (OperationCanceledException)
{
// 正常的取消操作,退出循环
break;
}
catch (Exception ex)
{
_logger.LogError(ex, "StreamLogs 循环中发生错误");
await SendSseEvent("error", new {
message = "处理日志流时发生错误",
error = ex.Message,
timestamp = DateTime.UtcNow
});
await Response.Body.FlushAsync(cancellationToken);
// 等待一段时间后继续,避免频繁错误
await Task.Delay(1000, cancellationToken);
}
}
// 发送断开连接事件
await SendSseEvent("disconnected", new {
message = "日志流连接已断开",
timestamp = DateTime.UtcNow
});
await Response.Body.FlushAsync(cancellationToken);
}
catch (OperationCanceledException)
{
_logger.LogInformation("StreamLogs 连接被客户端取消");
}
catch (Exception ex)
{
_logger.LogError(ex, "StreamLogs 方法执行时发生未处理的异常");
try
{
await SendSseEvent("fatal_error", new {
message = "服务器内部错误",
error = ex.Message,
timestamp = DateTime.UtcNow
});
await Response.Body.FlushAsync();
}
catch
{
// 忽略发送错误事件时的异常
}
}
}
}

175
LTEMvcApp/README_StreamLogs_Optimization.md

@ -0,0 +1,175 @@
# StreamLogs 方法优化说明
## 问题描述
原始的 `StreamLogs` 方法存在以下问题:
1. **线程安全问题**:`GetLogCache()` 返回的集合在循环中多次调用可能导致不一致
2. **性能问题**:每次都调用 `ToList()``Skip()` 操作,效率低下
3. **内存泄漏风险**:没有适当的错误处理和资源清理
4. **索引更新逻辑错误**:`logIndex = logCache.Count()` 可能导致跳过日志
5. **缺少错误处理**:没有处理异常情况
## 优化内容
### 1. 线程安全优化
- 使用 `lastLogCount` 变量跟踪上次处理的日志数量
- 在每次循环中获取当前日志的快照,避免并发修改问题
- 添加空值检查和异常处理
### 2. 性能优化
- 减少不必要的 `ToList()` 调用
- 优化索引更新逻辑,避免重复处理
- 添加日志缓存重置检测
### 3. 错误处理增强
- 添加完整的异常处理机制
- 区分 `OperationCanceledException` 和其他异常
- 添加错误恢复机制,避免频繁错误循环
- 添加连接状态事件(connected, disconnected, error, fatal_error)
### 4. 新增功能
- 添加 CORS 支持
- 添加时间戳信息
- 添加更详细的统计信息(totalCount, newCount)
- 添加日志缓存管理API
## 前端页面优化 (Logs.cshtml)
### 1. 新增功能
- **连接状态指示器**:实时显示连接状态(连接中、已连接、已断开、错误)
- **错误和信息提示**:显示连接错误和操作信息
- **控制按钮**:清空日志、重置日志、重新连接
- **新日志计数**:显示新接收的日志数量
- **自动重连机制**:连接断开时自动重试(最多5次)
### 2. 用户体验改进
- **视觉反馈**:状态指示器使用不同颜色和动画效果
- **操作确认**:重要操作前显示确认对话框
- **实时更新**:新日志数量实时显示并自动消失
- **错误恢复**:连接失败时提供重连选项
### 3. 事件处理
- 处理所有新的SSE事件类型
- 支持日志缓存重置
- 错误状态显示和恢复
## 新增API端点
### 1. 获取日志缓存统计信息
```
GET /api/websocket/logs/stats
```
### 2. 清空日志缓存
```
POST /api/websocket/logs/clear
```
### 3. 重置日志缓存
```
POST /api/websocket/logs/reset
```
## 事件类型
### StreamLogs 事件
- `connected`: 连接建立
- `history`: 历史日志(初始推送)
- `new_logs`: 新日志
- `reset`: 日志缓存重置
- `error`: 处理错误
- `disconnected`: 连接断开
- `fatal_error`: 致命错误
### StreamClientMessages 事件
- `open`: 连接建立
- `update`: 消息更新(sent/received)
- `error`: 处理错误
- `disconnected`: 连接断开
- `fatal_error`: 致命错误
## 使用示例
### JavaScript 客户端示例
```javascript
// 连接日志流
const eventSource = new EventSource('/api/websocket/logs/stream');
eventSource.addEventListener('connected', (event) => {
console.log('日志流连接已建立');
});
eventSource.addEventListener('history', (event) => {
const data = JSON.parse(event.data);
console.log('历史日志:', data.logs);
});
eventSource.addEventListener('new_logs', (event) => {
const data = JSON.parse(event.data);
console.log('新日志:', data.logs);
});
eventSource.addEventListener('reset', (event) => {
console.log('日志缓存已重置');
// 清空前端显示
});
eventSource.addEventListener('error', (event) => {
const data = JSON.parse(event.data);
console.error('错误:', data.message);
});
eventSource.addEventListener('disconnected', (event) => {
console.log('连接已断开');
});
// 错误处理
eventSource.onerror = (error) => {
console.error('EventSource 错误:', error);
};
```
## 前端页面功能
### 连接状态指示器
- 🟢 **绿色脉冲**:已连接
- 🟡 **黄色脉冲**:连接中
- 🔴 **红色**:已断开
- 🔴 **红色闪烁**:错误状态
### 控制按钮
- **清空**:清空所有日志显示
- **重置**:重置服务器日志缓存
- **重连**:手动重新连接
### 状态显示
- 总日志条数
- 新日志数量(绿色显示,2秒后消失)
- 连接状态文本
## 性能改进
1. **内存使用**:减少了不必要的对象创建
2. **CPU使用**:优化了循环逻辑,减少了重复计算
3. **网络效率**:添加了更详细的事件信息,便于客户端处理
4. **稳定性**:增强了错误处理和恢复机制
5. **用户体验**:添加了实时状态反馈和错误提示
## 注意事项
1. 日志缓存大小限制为 10000 条
2. 检查间隔为 250 毫秒
3. 错误恢复等待时间为 1000 毫秒
4. 自动重连最多尝试 5 次,间隔 3 秒
5. 所有时间戳使用 UTC 时间
6. 前端错误提示显示 5 秒后自动消失
7. 信息提示显示 3 秒后自动消失

22
LTEMvcApp/Services/WebSocketManagerService.cs

@ -450,6 +450,28 @@ namespace LTEMvcApp.Services
/// </summary>
public int GetLogCacheCount() => _logCache.Count;
/// <summary>
/// 清空日志缓存
/// </summary>
public void ClearLogCache()
{
_logger.LogInformation("清空日志缓存");
while (_logCache.TryDequeue(out _))
{
// 清空所有日志
}
}
/// <summary>
/// 重置日志缓存(清空并重新初始化)
/// </summary>
public void ResetLogCache()
{
_logger.LogInformation("重置日志缓存");
ClearLogCache();
// 可以在这里添加其他重置逻辑
}
#endregion
#region 私有方法

295
LTEMvcApp/Views/Home/Logs.cshtml

@ -55,7 +55,6 @@
.log-message { flex-basis: 24%; }
.log-info { flex-basis: 40%; }
/* 右侧日志详情 */
.log-detail-panel {
flex-grow: 1;
@ -102,16 +101,126 @@
border-top: 1px solid #ddd;
font-size: 0.9em;
color: #666;
display: flex;
justify-content: space-between;
align-items: center;
}
/* 连接状态指示器 */
.connection-status {
display: flex;
align-items: center;
gap: 8px;
}
.status-indicator {
width: 10px;
height: 10px;
border-radius: 50%;
display: inline-block;
}
.status-connected {
background-color: #28a745;
animation: pulse 2s infinite;
}
.status-connecting {
background-color: #ffc107;
animation: pulse 1s infinite;
}
.status-disconnected {
background-color: #dc3545;
}
.status-error {
background-color: #dc3545;
animation: blink 1s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0.3; }
}
/* 控制按钮 */
.control-buttons {
display: flex;
gap: 8px;
}
.btn-small {
padding: 4px 8px;
font-size: 0.8em;
border: 1px solid #ddd;
background-color: #fff;
border-radius: 3px;
cursor: pointer;
transition: all 0.2s;
}
.btn-small:hover {
background-color: #f8f9fa;
border-color: #007bff;
}
.btn-small:active {
background-color: #e9ecef;
}
/* 错误提示 */
.error-message {
background-color: #f8d7da;
color: #721c24;
padding: 8px 12px;
border: 1px solid #f5c6cb;
border-radius: 4px;
margin-bottom: 10px;
display: none;
}
/* 信息提示 */
.info-message {
background-color: #d1ecf1;
color: #0c5460;
padding: 8px 12px;
border: 1px solid #bee5eb;
border-radius: 4px;
margin-bottom: 10px;
display: none;
}
</style>
<div class="log-container">
<div class="log-list-panel">
<!-- 状态提示区域 -->
<div id="error-message" class="error-message"></div>
<div id="info-message" class="info-message"></div>
<div id="log-scroll-area">
<div id="log-content-area"></div>
</div>
<div class="status-bar">
总日志条数: <span id="total-logs">0</span>
<div>
总日志条数: <span id="total-logs">0</span>
<span id="new-logs-count" style="color: #28a745; margin-left: 10px;"></span>
</div>
<div class="connection-status">
<span class="status-indicator" id="status-indicator"></span>
<span id="status-text">连接中...</span>
<div class="control-buttons">
<button class="btn-small" id="clear-logs-btn" title="清空日志">清空</button>
<button class="btn-small" id="reset-logs-btn" title="重置日志">重置</button>
<button class="btn-small" id="reconnect-btn" title="重新连接">重连</button>
</div>
</div>
</div>
</div>
<div class="log-detail-panel">
@ -133,10 +242,23 @@
const scrollArea = document.getElementById('log-scroll-area');
const contentArea = document.getElementById('log-content-area');
const totalLogsEl = document.getElementById('total-logs');
const newLogsCountEl = document.getElementById('new-logs-count');
const detailPlaceholder = document.getElementById('detail-placeholder');
const detailContent = document.getElementById('detail-content');
const statusIndicator = document.getElementById('status-indicator');
const statusText = document.getElementById('status-text');
const errorMessage = document.getElementById('error-message');
const infoMessage = document.getElementById('info-message');
const clearLogsBtn = document.getElementById('clear-logs-btn');
const resetLogsBtn = document.getElementById('reset-logs-btn');
const reconnectBtn = document.getElementById('reconnect-btn');
let allLogsData = [];
let eventSource = null;
let reconnectAttempts = 0;
const maxReconnectAttempts = 5;
const reconnectDelay = 3000; // 3秒
let clusterize = new Clusterize({
rows: [],
scrollId: 'log-scroll-area',
@ -146,6 +268,40 @@
blocks_in_cluster: 4
});
// 更新连接状态
function updateConnectionStatus(status, text) {
statusIndicator.className = 'status-indicator status-' + status;
statusText.textContent = text;
}
// 显示错误消息
function showError(message) {
errorMessage.textContent = message;
errorMessage.style.display = 'block';
setTimeout(() => {
errorMessage.style.display = 'none';
}, 5000);
}
// 显示信息消息
function showInfo(message) {
infoMessage.textContent = message;
infoMessage.style.display = 'block';
setTimeout(() => {
infoMessage.style.display = 'none';
}, 3000);
}
// 清空日志显示
function clearLogsDisplay() {
allLogsData = [];
clusterize.clear();
totalLogsEl.textContent = '0';
newLogsCountEl.textContent = '';
detailPlaceholder.classList.remove('d-none');
detailContent.classList.add('d-none');
}
// 美化方向
function formatDirection(dir) {
return dir === 0 ? "Uplink" : "Downlink";
@ -165,6 +321,8 @@
// 更新日志列表
function updateLogList(logs, prepend = false) {
if (!logs || logs.length === 0) return;
const newRows = logs.map((log, i) => formatLogItem(log, allLogsData.length + i));
if (prepend) {
@ -174,6 +332,14 @@
}
allLogsData.push(...logs);
totalLogsEl.textContent = allLogsData.length;
// 显示新日志数量
if (!prepend) {
newLogsCountEl.textContent = `+${logs.length}`;
setTimeout(() => {
newLogsCountEl.textContent = '';
}, 2000);
}
}
// 显示日志详情
@ -226,6 +392,79 @@
detailContent.classList.remove('d-none');
}
// 建立SSE连接
function connectSSE() {
if (eventSource) {
eventSource.close();
}
updateConnectionStatus('connecting', '连接中...');
eventSource = new EventSource('/api/websocket/logs/stream');
eventSource.addEventListener('connected', function(event) {
console.log("SSE连接已建立");
updateConnectionStatus('connected', '已连接');
showInfo('日志流连接已建立');
reconnectAttempts = 0; // 重置重连计数
});
eventSource.addEventListener('history', function(event) {
console.log("接收到历史日志...");
const data = JSON.parse(event.data);
updateLogList(data.logs);
showInfo(`加载了 ${data.logs.length} 条历史日志`);
});
eventSource.addEventListener('new_logs', function(event) {
console.log("接收到新日志...");
const data = JSON.parse(event.data);
updateLogList(data.logs);
});
eventSource.addEventListener('reset', function(event) {
console.log("日志缓存已重置");
const data = JSON.parse(event.data);
clearLogsDisplay();
showInfo('日志缓存已重置');
});
eventSource.addEventListener('error', function(event) {
console.log("接收到错误事件");
const data = JSON.parse(event.data);
updateConnectionStatus('error', '连接错误');
showError(`连接错误: ${data.message}`);
});
eventSource.addEventListener('disconnected', function(event) {
console.log("连接已断开");
updateConnectionStatus('disconnected', '已断开');
showInfo('日志流连接已断开');
});
eventSource.addEventListener('fatal_error', function(event) {
console.error("致命错误");
const data = JSON.parse(event.data);
updateConnectionStatus('error', '服务器错误');
showError(`服务器错误: ${data.message}`);
});
eventSource.onerror = function (err) {
console.error("SSE 错误:", err);
updateConnectionStatus('error', '连接失败');
// 自动重连
if (reconnectAttempts < maxReconnectAttempts) {
reconnectAttempts++;
showError(`连接失败,${reconnectDelay/1000}秒后重试 (${reconnectAttempts}/${maxReconnectAttempts})`);
setTimeout(connectSSE, reconnectDelay);
} else {
showError('连接失败,已达到最大重试次数');
updateConnectionStatus('disconnected', '连接失败');
}
};
}
// 事件委托处理点击事件
contentArea.addEventListener('click', function(e) {
const item = e.target.closest('.log-item');
@ -243,26 +482,46 @@
}
});
// SSE 连接
const eventSource = new EventSource('/api/websocket/logs/stream');
// 控制按钮事件
clearLogsBtn.addEventListener('click', function() {
if (confirm('确定要清空所有日志吗?')) {
fetch('/api/websocket/logs/clear', { method: 'POST' })
.then(response => response.json())
.then(data => {
if (data.message) {
showInfo(data.message);
clearLogsDisplay();
}
})
.catch(error => {
showError('清空日志失败: ' + error.message);
});
}
});
eventSource.addEventListener('history', function(event) {
console.log("接收到历史日志...");
const data = JSON.parse(event.data);
updateLogList(data.logs);
resetLogsBtn.addEventListener('click', function() {
if (confirm('确定要重置日志缓存吗?')) {
fetch('/api/websocket/logs/reset', { method: 'POST' })
.then(response => response.json())
.then(data => {
if (data.message) {
showInfo(data.message);
clearLogsDisplay();
}
})
.catch(error => {
showError('重置日志失败: ' + error.message);
});
}
});
eventSource.addEventListener('new_logs', function(event) {
console.log("接收到新日志...");
const data = JSON.parse(event.data);
updateLogList(data.logs);
reconnectBtn.addEventListener('click', function() {
reconnectAttempts = 0; // 重置重连计数
connectSSE();
});
eventSource.onerror = function (err) {
console.error("SSE 错误:", err);
totalLogsEl.textContent += " (连接已断开)";
eventSource.close();
};
// 初始化连接
connectSSE();
});
</script>
}
Loading…
Cancel
Save