You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

487 lines
19 KiB

1 month ago
using Microsoft.AspNetCore.Mvc;
using LTEMvcApp.Models;
using LTEMvcApp.Services;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
using System.Threading;
using System.IO;
using System.Text;
namespace LTEMvcApp.Controllers
{
/// <summary>
/// 日志管理控制器 - 负责日志相关的管理功能
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class LogController : ControllerBase
{
private readonly WebSocketManagerService _webSocketManager;
private readonly ILogger<LogController> _logger;
private readonly string _logsDirectory = "ClientMessageLogs";
public LogController(WebSocketManagerService webSocketManager, ILogger<LogController> logger)
{
_webSocketManager = webSocketManager;
_logger = logger;
// 确保日志目录存在
if (!Directory.Exists(_logsDirectory))
{
Directory.CreateDirectory(_logsDirectory);
}
}
/// <summary>
/// 获取日志缓存统计信息
/// </summary>
/// <returns>统计信息</returns>
[HttpGet("stats")]
public ActionResult<object> GetLogCacheStats()
{
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 });
}
}
/// <summary>
/// 清空全局日志缓存
/// </summary>
/// <returns>操作结果</returns>
[HttpPost("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 });
}
}
/// <summary>
/// 重置全局日志缓存
/// </summary>
/// <returns>操作结果</returns>
[HttpPost("reset")]
public ActionResult ResetLogCache()
{
try
{
_webSocketManager.ResetLogCache();
return Ok(new { message = "日志缓存已重置" });
}
catch (Exception ex)
{
_logger.LogError(ex, "重置日志缓存时发生错误");
return StatusCode(500, new { message = "重置日志缓存失败", error = ex.Message });
}
}
/// <summary>
/// 添加测试日志数据
/// </summary>
/// <returns>操作结果</returns>
[HttpPost("add-test-data")]
public ActionResult AddTestLogData()
{
try
{
var testLogs = new List<LTELog>();
_webSocketManager.AddLogsToCache(testLogs);
return Ok(new { message = $"已添加 {testLogs.Count} 条测试日志" });
}
catch (Exception ex)
{
_logger.LogError(ex, "添加测试日志数据时发生错误");
return StatusCode(500, new { message = "添加测试日志失败", error = ex.Message });
}
}
/// <summary>
/// 使用 Server-Sent Events (SSE) 实时推送全局日志
/// </summary>
[HttpGet("stream")]
public async Task StreamLogs(CancellationToken cancellationToken)
{
try
{
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>();
var lastLogHash = string.Empty;
// 首先,一次性推送所有已缓存的日志
try
{
var initialLogs = _webSocketManager.GetLogCache()?.ToList() ?? new List<LTELog>();
_logger.LogInformation("StreamLogs: 获取到初始日志 {Count} 条", initialLogs.Count);
if (initialLogs.Any())
{
_logger.LogInformation("StreamLogs: 发送历史日志事件,日志数量: {Count}", initialLogs.Count);
await SendSseEvent("history", new { logs = initialLogs, totalCount = initialLogs.Count });
await Response.Body.FlushAsync(cancellationToken);
lastLogCount = initialLogs.Count;
lastLogs = initialLogs.ToList();
lastLogHash = CalculateLogsHash(initialLogs);
}
else
{
_logger.LogInformation("StreamLogs: 没有历史日志数据");
}
}
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>();
var currentLogHash = CalculateLogsHash(currentLogs);
bool hasNewLogs = false;
List<LTELog> newLogs = new List<LTELog>();
if (currentLogs.Count > lastLogCount)
{
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)
{
_logger.LogInformation("检测到日志缓存被重置,重新同步");
await SendSseEvent("reset", new {
message = "日志缓存已重置",
totalCount = currentLogs.Count
});
await Response.Body.FlushAsync(cancellationToken);
lastLogCount = currentLogs.Count;
lastLogs = currentLogs.ToList();
lastLogHash = currentLogHash;
continue;
}
if (hasNewLogs && newLogs.Any())
{
_logger.LogInformation("StreamLogs: 发送新日志事件,新增日志数量: {NewCount}, 总日志数量: {TotalCount}", newLogs.Count, currentLogs.Count);
var eventData = new {
logs = newLogs,
totalCount = currentLogs.Count,
newCount = newLogs.Count
};
await SendSseEvent("new_logs", eventData);
await Response.Body.FlushAsync(cancellationToken);
lastLogCount = currentLogs.Count;
lastLogs = currentLogs.ToList();
lastLogHash = currentLogHash;
}
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
{
// 忽略发送错误事件时的异常
}
}
}
/// <summary>
/// 获取日志缓存详细状态(调试用)
/// </summary>
/// <returns>日志缓存详细状态</returns>
[HttpGet("debug")]
public ActionResult<object> GetLogCacheDebugInfo()
{
try
{
var logs = _webSocketManager.GetLogCache()?.ToList() ?? new List<LTELog>();
var logCount = _webSocketManager.GetLogCacheCount();
var cacheStatus = _webSocketManager.GetLogCacheStatus();
var debugInfo = new
{
totalLogs = logCount,
actualLogsCount = logs.Count,
cacheSize = 10000,
timestamp = DateTime.UtcNow,
sampleLogs = logs.TakeLast(5).Select(log => new
{
timestamp = log.Timestamp,
layer = log.Layer,
message = log.Message?.Substring(0, Math.Min(100, log.Message?.Length ?? 0)) + "..."
}).ToList(),
logHash = CalculateLogsHash(logs),
cacheStatus = cacheStatus
};
return Ok(debugInfo);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取日志缓存调试信息时发生错误");
return StatusCode(500, new { message = "获取调试信息失败", error = ex.Message });
}
}
/// <summary>
/// 获取SSE连接状态(调试用)
/// </summary>
/// <returns>连接状态信息</returns>
[HttpGet("connection-status")]
public ActionResult<object> GetSseConnectionStatus()
{
try
{
var status = new
{
timestamp = DateTime.UtcNow,
requestHeaders = Request.Headers.ToDictionary(h => h.Key, h => h.Value.ToString()),
userAgent = Request.Headers["User-Agent"].ToString(),
accept = Request.Headers["Accept"].ToString(),
cacheControl = Request.Headers["Cache-Control"].ToString(),
connection = Request.Headers["Connection"].ToString(),
isHttps = Request.IsHttps,
host = Request.Host.ToString(),
path = Request.Path.ToString(),
queryString = Request.QueryString.ToString(),
method = Request.Method,
contentType = Request.ContentType,
contentLength = Request.ContentLength
};
return Ok(status);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取SSE连接状态时发生错误");
return StatusCode(500, new { message = "获取连接状态失败", error = ex.Message });
}
}
/// <summary>
/// 测试SSE连接(调试用)
/// </summary>
/// <returns>测试结果</returns>
[HttpGet("test-connection")]
public ActionResult<object> TestSseConnection()
{
try
{
var testResult = new
{
message = "SSE连接测试成功",
timestamp = DateTime.UtcNow,
serverTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
logCacheCount = _webSocketManager.GetLogCacheCount(),
testData = new { test = true, message = "这是一个测试消息" }
};
return Ok(testResult);
}
catch (Exception ex)
{
_logger.LogError(ex, "测试SSE连接时发生错误");
return StatusCode(500, new { message = "测试连接失败", error = ex.Message });
}
}
/// <summary>
/// 强制推送测试日志(调试用)
/// </summary>
/// <returns>操作结果</returns>
[HttpPost("force-push-test")]
public ActionResult ForcePushTestLogs()
{
try
{
var testLogs = new List<LTELog>();
_webSocketManager.AddLogsToCache(testLogs);
_logger.LogInformation("强制推送测试日志: {Message}", testLogs[0].Message);
return Ok(new {
message = $"已强制推送测试日志: {testLogs[0].Message}",
timestamp = testLogs[0].Timestamp
});
}
catch (Exception ex)
{
_logger.LogError(ex, "强制推送测试日志时发生错误");
return StatusCode(500, new { message = "强制推送失败", error = ex.Message });
}
}
private async Task SendSseEvent(string eventName, object data)
{
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>
/// 计算日志列表的哈希值,用于检测内容变化
/// </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;
}
}
}