添加调试页
This commit is contained in:
parent
d34b491c11
commit
d314597e8d
6 changed files with 242 additions and 5 deletions
|
@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc;
|
|||
using Serilog;
|
||||
using System.Text.Json;
|
||||
using iFileProxy.Attributes;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace iFileProxy.Controllers
|
||||
{
|
||||
|
@ -386,5 +387,54 @@ namespace iFileProxy.Controllers
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取系统日志
|
||||
/// </summary>
|
||||
[Authorize(UserMask.SuperAdmin)]
|
||||
[HttpGet("GetSystemLogs")]
|
||||
public async Task<ActionResult<CommonRsp>> GetSystemLogs(
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 10,
|
||||
[FromQuery] string? level = null,
|
||||
[FromQuery] string? keyword = null,
|
||||
[FromQuery] DateTime? startTime = null,
|
||||
[FromQuery] DateTime? endTime = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 解析日志级别
|
||||
LogEventLevel? logLevel = null;
|
||||
if (!string.IsNullOrEmpty(level) && Enum.TryParse<LogEventLevel>(level, true, out var parsedLevel))
|
||||
{
|
||||
logLevel = parsedLevel;
|
||||
}
|
||||
|
||||
var result = await _dbGateService.GetPagedSystemLogsAsync(
|
||||
page,
|
||||
pageSize,
|
||||
logLevel,
|
||||
keyword,
|
||||
startTime,
|
||||
endTime
|
||||
);
|
||||
|
||||
return Ok(new CommonRsp
|
||||
{
|
||||
Retcode = 0,
|
||||
Message = "success",
|
||||
Data = result
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "获取系统日志失败");
|
||||
return Ok(new CommonRsp
|
||||
{
|
||||
Retcode = 1,
|
||||
Message = "获取系统日志失败"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
22
src/Models/SystemLog.cs
Normal file
22
src/Models/SystemLog.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
public class SystemLog
|
||||
{
|
||||
[JsonProperty("log_id")]
|
||||
public string LogId { get; set; } = Guid.NewGuid().ToString();
|
||||
|
||||
[JsonProperty("level")]
|
||||
public string Level { get; set; } = string.Empty;
|
||||
|
||||
[JsonProperty("message")]
|
||||
public string Message { get; set; } = string.Empty;
|
||||
|
||||
[JsonProperty("exception")]
|
||||
public string? Exception { get; set; }
|
||||
|
||||
[JsonProperty("properties")]
|
||||
public string Properties { get; set; } = "{}";
|
||||
|
||||
[JsonProperty("timestamp")]
|
||||
public DateTime Timestamp { get; set; }
|
||||
}
|
|
@ -127,6 +127,9 @@ namespace iFileProxy
|
|||
|
||||
app.MapControllers();
|
||||
|
||||
var dbGateService = app.Services.GetRequiredService<DatabaseGateService>();
|
||||
SerilogConfig.CreateLogger(dbGateService);
|
||||
|
||||
app.Run();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,11 +3,16 @@
|
|||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using System.Net;
|
||||
using iFileProxy.Sinks;
|
||||
using iFileProxy.Services;
|
||||
|
||||
public static class SerilogConfig
|
||||
{
|
||||
public static void CreateLogger()
|
||||
private static DatabaseGateService? _dbGateService;
|
||||
|
||||
public static void CreateLogger(DatabaseGateService? dbGateService = null)
|
||||
{
|
||||
_dbGateService = dbGateService; // 保存实例以供后续使用
|
||||
var baseLogPath = Path.Combine(AppContext.BaseDirectory, "logs");
|
||||
var appName = AppDomain.CurrentDomain.FriendlyName;
|
||||
|
||||
|
@ -17,7 +22,7 @@
|
|||
Directory.CreateDirectory(baseLogPath);
|
||||
}
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
var loggerConfiguration = new LoggerConfiguration()
|
||||
#if RELEASE
|
||||
.MinimumLevel.Information()
|
||||
#else
|
||||
|
@ -61,8 +66,20 @@
|
|||
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{SourceContext}] {ClientIp} {Message:lj} {contentType} {queryString}{NewLine}{Exception}",
|
||||
rollingInterval: RollingInterval.Day,
|
||||
fileSizeLimitBytes: 1073741824))
|
||||
.Enrich.WithProperty("node_ip", GetIpAddress())
|
||||
.CreateLogger();
|
||||
.Enrich.WithProperty("node_ip", GetIpAddress());
|
||||
|
||||
// 只有在提供了 DatabaseGateService 时才添加数据库 Sink
|
||||
if (_dbGateService != null)
|
||||
{
|
||||
loggerConfiguration.WriteTo.Sink(
|
||||
new DatabaseLogSink(
|
||||
_dbGateService,
|
||||
LogEventLevel.Error
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Log.Logger = loggerConfiguration.CreateLogger();
|
||||
}
|
||||
|
||||
public static void RefreshLogger()
|
||||
|
@ -71,7 +88,7 @@
|
|||
{
|
||||
Log.CloseAndFlush();
|
||||
}
|
||||
CreateLogger();
|
||||
CreateLogger(_dbGateService); // 使用保存的实例
|
||||
}
|
||||
|
||||
private static string GetIpAddress()
|
||||
|
|
|
@ -6,6 +6,7 @@ using MySql.Data.MySqlClient;
|
|||
using iFileProxy.Models;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace iFileProxy.Services
|
||||
{
|
||||
|
@ -65,6 +66,20 @@ namespace iFileProxy.Services
|
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
""";
|
||||
|
||||
private const string CREATE_SYSTEM_LOGS_TABLE_SQL = """
|
||||
CREATE TABLE IF NOT EXISTS `t_system_logs` (
|
||||
`log_id` varchar(36) NOT NULL,
|
||||
`level` varchar(20) NOT NULL,
|
||||
`message` text NOT NULL,
|
||||
`exception` text,
|
||||
`properties` json,
|
||||
`timestamp` datetime NOT NULL,
|
||||
PRIMARY KEY (`log_id`),
|
||||
KEY `idx_timestamp` (`timestamp`),
|
||||
KEY `idx_level` (`level`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
""";
|
||||
|
||||
public DatabaseGateService(AppConfig appConfig)
|
||||
{
|
||||
_logger.Information("Initializing DatabaseGateService...");
|
||||
|
@ -966,5 +981,89 @@ namespace iFileProxy.Services
|
|||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> CreateSystemLogAsync(SystemLog log)
|
||||
{
|
||||
var sql = @"INSERT INTO t_system_logs
|
||||
(log_id, level, message, exception, properties, timestamp)
|
||||
VALUES
|
||||
(@logId, @level, @message, @exception, @properties, @timestamp)";
|
||||
|
||||
var parameters = new Dictionary<string, object>
|
||||
{
|
||||
{ "@logId", log.LogId },
|
||||
{ "@level", log.Level },
|
||||
{ "@message", log.Message },
|
||||
{ "@exception", log.Exception },
|
||||
{ "@properties", JsonConvert.SerializeObject(log.Properties) },
|
||||
{ "@timestamp", log.Timestamp }
|
||||
};
|
||||
|
||||
var result = await ExecuteNonQueryAsync(sql, parameters);
|
||||
return result > 0;
|
||||
}
|
||||
|
||||
public async Task<PagedResult<SystemLog>> GetPagedSystemLogsAsync(
|
||||
int page,
|
||||
int pageSize,
|
||||
LogEventLevel? level = null,
|
||||
string? keyword = null,
|
||||
DateTime? startTime = null,
|
||||
DateTime? endTime = null)
|
||||
{
|
||||
// 构建基础SQL
|
||||
var sql = new StringBuilder("SELECT * FROM t_system_logs WHERE 1=1");
|
||||
var parameters = new Dictionary<string, object>();
|
||||
|
||||
// 添加日志级别过滤
|
||||
if (level.HasValue)
|
||||
{
|
||||
sql.Append(" AND level = @level");
|
||||
parameters.Add("@level", level.Value.ToString());
|
||||
}
|
||||
|
||||
// 添加关键词搜索
|
||||
if (!string.IsNullOrEmpty(keyword))
|
||||
{
|
||||
sql.Append(" AND (message LIKE @keyword OR exception LIKE @keyword)");
|
||||
parameters.Add("@keyword", $"%{keyword}%");
|
||||
}
|
||||
|
||||
// 添加时间范围过滤
|
||||
if (startTime.HasValue)
|
||||
{
|
||||
sql.Append(" AND timestamp >= @startTime");
|
||||
parameters.Add("@startTime", startTime.Value);
|
||||
}
|
||||
if (endTime.HasValue)
|
||||
{
|
||||
sql.Append(" AND timestamp <= @endTime");
|
||||
parameters.Add("@endTime", endTime.Value);
|
||||
}
|
||||
|
||||
// 添加分页和排序
|
||||
sql.Append(" ORDER BY timestamp DESC LIMIT @offset, @limit");
|
||||
parameters.Add("@offset", (page - 1) * pageSize);
|
||||
parameters.Add("@limit", pageSize);
|
||||
|
||||
// 获取总记录数
|
||||
var countSql = "SELECT COUNT(*) FROM t_system_logs WHERE 1=1" +
|
||||
(level.HasValue ? " AND level = @level" : "") +
|
||||
(!string.IsNullOrEmpty(keyword) ? " AND (message LIKE @keyword OR exception LIKE @keyword)" : "") +
|
||||
(startTime.HasValue ? " AND timestamp >= @startTime" : "") +
|
||||
(endTime.HasValue ? " AND timestamp <= @endTime" : "");
|
||||
|
||||
var totalCount = await ExecuteScalarAsync<long>(countSql, parameters);
|
||||
var logs = await ExecuteQueryAsync<SystemLog>(sql.ToString(), parameters);
|
||||
|
||||
return new PagedResult<SystemLog>
|
||||
{
|
||||
Total = totalCount,
|
||||
PageSize = pageSize,
|
||||
CurrentPage = page,
|
||||
TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize),
|
||||
Data = logs
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
46
src/Sinks/DatabaseLogSink.cs
Normal file
46
src/Sinks/DatabaseLogSink.cs
Normal file
|
@ -0,0 +1,46 @@
|
|||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
using iFileProxy.Services;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace iFileProxy.Sinks
|
||||
{
|
||||
public class DatabaseLogSink : ILogEventSink
|
||||
{
|
||||
private readonly DatabaseGateService _dbGateService;
|
||||
private readonly LogEventLevel _restrictedToMinimumLevel;
|
||||
|
||||
public DatabaseLogSink(DatabaseGateService dbGateService, LogEventLevel restrictedToMinimumLevel)
|
||||
{
|
||||
_dbGateService = dbGateService;
|
||||
_restrictedToMinimumLevel = restrictedToMinimumLevel;
|
||||
}
|
||||
|
||||
public void Emit(LogEvent logEvent)
|
||||
{
|
||||
if (logEvent.Level < _restrictedToMinimumLevel) return;
|
||||
|
||||
try
|
||||
{
|
||||
var log = new SystemLog
|
||||
{
|
||||
Level = logEvent.Level.ToString(),
|
||||
Message = logEvent.RenderMessage(),
|
||||
Exception = logEvent.Exception?.ToString(),
|
||||
Properties = JsonConvert.SerializeObject(logEvent.Properties.ToDictionary(
|
||||
kvp => kvp.Key,
|
||||
kvp => kvp.Value.ToString()
|
||||
)),
|
||||
Timestamp = logEvent.Timestamp.DateTime
|
||||
};
|
||||
|
||||
// 异步写入数据库
|
||||
Task.Run(async () => await _dbGateService.CreateSystemLogAsync(log));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 记录日志失败时不抛出异常,避免影响应用程序运行
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue