namespace WebApi.Controllers; [ApiController] [Route("api/[controller]/[action]")] public class LogController(ILogger logger) : ControllerBase { /// /// 获取日志列表(分页) /// [HttpGet] public async Task> GetListAsync( [FromQuery] int pageIndex = 1, [FromQuery] int pageSize = 50, [FromQuery] string? searchKeyword = null, [FromQuery] string? logLevel = null, [FromQuery] string? date = null ) { try { // 获取日志目录 var logDirectory = Path.Combine(Directory.GetCurrentDirectory(), "logs"); if (!Directory.Exists(logDirectory)) { return new PagedResponse { Success = true, Data = [], Total = 0, Message = "日志目录不存在" }; } // 确定要读取的日志文件 string logFilePath; if (!string.IsNullOrEmpty(date)) { logFilePath = Path.Combine(logDirectory, $"log-{date}.txt"); } else { // 默认读取今天的日志 var today = DateTime.Now.ToString("yyyyMMdd"); logFilePath = Path.Combine(logDirectory, $"log-{today}.txt"); } // 检查文件是否存在 if (!System.IO.File.Exists(logFilePath)) { return new PagedResponse { Success = true, Data = [], Total = 0, Message = "日志文件不存在" }; } // 读取所有日志行(使用共享读取模式,允许其他进程写入) var allLines = await ReadAllLinesAsync(logFilePath); var logEntries = new List(); foreach (var line in allLines) { if (string.IsNullOrWhiteSpace(line)) continue; var logEntry = ParseLogLine(line); if (logEntry != null) { // 应用筛选条件 if (!string.IsNullOrEmpty(searchKeyword) && !logEntry.Message.Contains(searchKeyword, StringComparison.OrdinalIgnoreCase)) { continue; } if (!string.IsNullOrEmpty(logLevel) && !logEntry.Level.Equals(logLevel, StringComparison.OrdinalIgnoreCase)) { continue; } logEntries.Add(logEntry); } } // 倒序排列(最新的在前面) logEntries.Reverse(); var total = logEntries.Count; var skip = (pageIndex - 1) * pageSize; var pagedData = logEntries.Skip(skip).Take(pageSize).ToList(); return new PagedResponse { Success = true, Data = pagedData.ToArray(), Total = total, Message = "获取日志成功" }; } catch (Exception ex) { logger.LogError(ex, "获取日志失败"); return new PagedResponse { Success = false, Data = [], Total = 0, Message = $"获取日志失败: {ex.Message}" }; } } /// /// 获取可用的日志日期列表 /// [HttpGet] public IActionResult GetAvailableDates() { try { var logDirectory = Path.Combine(Directory.GetCurrentDirectory(), "logs"); if (!Directory.Exists(logDirectory)) { return Ok(new { success = true, data = new List() }); } var logFiles = Directory.GetFiles(logDirectory, "log-*.txt"); var dates = logFiles .Select(f => Path.GetFileNameWithoutExtension(f)) .Select(name => name.Replace("log-", "")) .OrderByDescending(d => d) .ToList(); return Ok(new { success = true, data = dates }); } catch (Exception ex) { logger.LogError(ex, "获取日志日期列表失败"); return Ok(new { success = false, message = $"获取日志日期列表失败: {ex.Message}" }); } } /// /// 解析单行日志 /// private LogEntry? ParseLogLine(string line) { try { // 日志格式示例: [2025-12-29 16:38:45.730 +08:00] [INF] Message here // 使用正则表达式解析 var match = System.Text.RegularExpressions.Regex.Match( line, @"^\[(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\.\d{3}\s+[+-]\d{2}:\d{2})\]\s+\[(\w+)\]\s+(.*)$" ); if (match.Success) { return new LogEntry { Timestamp = match.Groups[1].Value, Level = match.Groups[2].Value, Message = match.Groups[3].Value }; } // 如果不匹配标准格式,将整行作为消息 return new LogEntry { Timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff zzz"), Level = "LOG", Message = line }; } catch { return null; } } /// /// 读取文件所有行(支持共享读取) /// private async Task ReadAllLinesAsync(string path) { var lines = new List(); using (var fileStream = new FileStream( path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) using (var streamReader = new StreamReader(fileStream)) { string? line; while ((line = await streamReader.ReadLineAsync()) != null) { lines.Add(line); } } return lines.ToArray(); } } /// /// 日志条目 /// public class LogEntry { /// /// 时间戳 /// public string Timestamp { get; set; } = string.Empty; /// /// 日志级别 /// public string Level { get; set; } = string.Empty; /// /// 日志消息 /// public string Message { get; set; } = string.Empty; }