添加开发者日志功能
This commit is contained in:
228
WebApi/Controllers/LogController.cs
Normal file
228
WebApi/Controllers/LogController.cs
Normal file
@@ -0,0 +1,228 @@
|
||||
namespace WebApi.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]/[action]")]
|
||||
public class LogController(ILogger<LogController> logger) : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取日志列表(分页)
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public async Task<PagedResponse<LogEntry>> 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<LogEntry>
|
||||
{
|
||||
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<LogEntry>
|
||||
{
|
||||
Success = true,
|
||||
Data = [],
|
||||
Total = 0,
|
||||
Message = "日志文件不存在"
|
||||
};
|
||||
}
|
||||
|
||||
// 读取所有日志行(使用共享读取模式,允许其他进程写入)
|
||||
var allLines = await ReadAllLinesAsync(logFilePath);
|
||||
var logEntries = new List<LogEntry>();
|
||||
|
||||
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<LogEntry>
|
||||
{
|
||||
Success = true,
|
||||
Data = pagedData.ToArray(),
|
||||
Total = total,
|
||||
Message = "获取日志成功"
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "获取日志失败");
|
||||
return new PagedResponse<LogEntry>
|
||||
{
|
||||
Success = false,
|
||||
Data = [],
|
||||
Total = 0,
|
||||
Message = $"获取日志失败: {ex.Message}"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取可用的日志日期列表
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public IActionResult GetAvailableDates()
|
||||
{
|
||||
try
|
||||
{
|
||||
var logDirectory = Path.Combine(Directory.GetCurrentDirectory(), "logs");
|
||||
if (!Directory.Exists(logDirectory))
|
||||
{
|
||||
return Ok(new { success = true, data = new List<string>() });
|
||||
}
|
||||
|
||||
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}" });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析单行日志
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取文件所有行(支持共享读取)
|
||||
/// </summary>
|
||||
private async Task<string[]> ReadAllLinesAsync(string path)
|
||||
{
|
||||
var lines = new List<string>();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 日志条目
|
||||
/// </summary>
|
||||
public class LogEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// 时间戳
|
||||
/// </summary>
|
||||
public string Timestamp { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 日志级别
|
||||
/// </summary>
|
||||
public string Level { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 日志消息
|
||||
/// </summary>
|
||||
public string Message { get; set; } = string.Empty;
|
||||
}
|
||||
Reference in New Issue
Block a user