From d314597e8dc2a8087cacd1b652709cb4f4d1cf38 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 2 Dec 2024 21:39:01 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=B0=83=E8=AF=95=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Controllers/ManagementController.cs | 50 +++++++++++++ src/Models/SystemLog.cs | 22 ++++++ src/Program.cs | 3 + src/SerilogConfig.cs | 27 +++++-- src/Services/DatabaseGateService.cs | 99 +++++++++++++++++++++++++ src/Sinks/DatabaseLogSink.cs | 46 ++++++++++++ 6 files changed, 242 insertions(+), 5 deletions(-) create mode 100644 src/Models/SystemLog.cs create mode 100644 src/Sinks/DatabaseLogSink.cs diff --git a/src/Controllers/ManagementController.cs b/src/Controllers/ManagementController.cs index 81cd778..3ebbd49 100644 --- a/src/Controllers/ManagementController.cs +++ b/src/Controllers/ManagementController.cs @@ -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 }); } } + + /// + /// 获取系统日志 + /// + [Authorize(UserMask.SuperAdmin)] + [HttpGet("GetSystemLogs")] + public async Task> 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(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 = "获取系统日志失败" + }); + } + } } } diff --git a/src/Models/SystemLog.cs b/src/Models/SystemLog.cs new file mode 100644 index 0000000..4472ce1 --- /dev/null +++ b/src/Models/SystemLog.cs @@ -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; } +} \ No newline at end of file diff --git a/src/Program.cs b/src/Program.cs index c941411..98dd5ef 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -127,6 +127,9 @@ namespace iFileProxy app.MapControllers(); + var dbGateService = app.Services.GetRequiredService(); + SerilogConfig.CreateLogger(dbGateService); + app.Run(); } } diff --git a/src/SerilogConfig.cs b/src/SerilogConfig.cs index 3d0b0af..3cbeeb1 100644 --- a/src/SerilogConfig.cs +++ b/src/SerilogConfig.cs @@ -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() diff --git a/src/Services/DatabaseGateService.cs b/src/Services/DatabaseGateService.cs index c803adc..33ba47b 100644 --- a/src/Services/DatabaseGateService.cs +++ b/src/Services/DatabaseGateService.cs @@ -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 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 + { + { "@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> 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(); + + // 添加日志级别过滤 + 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(countSql, parameters); + var logs = await ExecuteQueryAsync(sql.ToString(), parameters); + + return new PagedResult + { + Total = totalCount, + PageSize = pageSize, + CurrentPage = page, + TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize), + Data = logs + }; + } } } diff --git a/src/Sinks/DatabaseLogSink.cs b/src/Sinks/DatabaseLogSink.cs new file mode 100644 index 0000000..63c5526 --- /dev/null +++ b/src/Sinks/DatabaseLogSink.cs @@ -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 + { + // 记录日志失败时不抛出异常,避免影响应用程序运行 + } + } + } +} \ No newline at end of file