using Serilog.Events; using Serilog.Sinks.PeriodicBatching; using SqlSugar; using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using IdentityServer4.Models; using Mysqlx.Crud; using Azure.Core; using Consul; using Google.Protobuf.Collections; using IdentityModel.Client; using log4net.Core; using MySqlX.XDevAPI.Common; using System.Collections.Concurrent; using System.Data; using System.Runtime.Serialization; using NPlatform.Infrastructure.IdGenerators; using BZPT.Domains.Application; using NuGet.Protocol.Core.Types; namespace BZPT { /// /// 日志记录类 /// public class LoggingWithDMSink : PeriodicBatchingSink { private readonly string _connectionString; private readonly string _sysName; private readonly IHttpContextAccessor _httpContextAccessor; private readonly SqlSugarScope _db; public LoggingWithDMSink( string connectionString, string sysName, IHttpContextAccessor httpContextAccessor, int batchSizeLimit = 200, // 增大批量大小 TimeSpan period = default) : base(batchSizeLimit, period == default ? TimeSpan.FromSeconds(15) : period) { _connectionString = connectionString; _sysName = sysName; _httpContextAccessor = httpContextAccessor; // 使用 SqlSugarScope 确保线程安全 _db = new SqlSugarScope(new ConnectionConfig { ConnectionString = _connectionString, DbType = SqlSugar.DbType.Dm, IsAutoCloseConnection = true, InitKeyType = InitKeyType.Attribute, ConfigId= "LoggingID" }); } protected override async Task EmitBatchAsync(IEnumerable events) { try { // 批量分离错误日志和操作日志 var errorLogs = new List(); var operationLogs = new List(); foreach (var logEvent in events) { var (errorLog, operationLog) = ParseLogEvent(logEvent); if (logEvent.Level >= LogEventLevel.Error && errorLog != null) { errorLogs.Add(errorLog); MessageApplication.SendErrorMessage($"{_sysName}:{errorLog.ErrorMessage}"); } else if(logEvent.Level== LogEventLevel.Warning && errorLog != null) { errorLogs.Add(errorLog); var message = errorLog.Description ?? errorLog.ErrorMessage; if (message!=null&&message.StartsWith("SqlExecutionTime")) { MessageApplication.SendErrorMessage($"{_sysName}发现慢查询:" +message); } } else if (operationLog != null && logEvent.Level == LogEventLevel.Information) operationLogs.Add(operationLog); } // 并行批量写入 var tasks = new List(); if (errorLogs.Count > 0) { tasks.Add(_db.Fastest().BulkCopyAsync(errorLogs)); // 达梦批量插入 } if (operationLogs.Count > 0) { tasks.Add(_db.Fastest().BulkCopyAsync(operationLogs)); } await Task.WhenAll(tasks); } catch (Exception ex) { // 错误处理(可记录到本地文件) Console.WriteLine($"日志写入失败: {ex.Message}"); } } private (Sys_ErrorLog?, Sys_OperationLog?) ParseLogEvent(LogEvent logEvent) { var httpContext = _httpContextAccessor.HttpContext; var id = GetPropertyValue(logEvent, "RequestID"); if (logEvent.Level >= LogEventLevel.Warning) { if(logEvent.RenderMessage().Contains("SameSite=None"))return (null,null); return (new Sys_ErrorLog { SysName = _sysName, OperationType = GetPropertyValue(logEvent, "OperationType"), ModuleName = GetPropertyValue(logEvent, "ModuleName"), Description = logEvent.RenderMessage(), UserID = GetPropertyValue(logEvent, "UserID"), UserName = GetPropertyValue(logEvent, "UserName"), CreateUser = GetPropertyValue(logEvent, "UserName"), CreateTime = logEvent.Timestamp.DateTime, IP = httpContext?.Connection?.RemoteIpAddress?.ToString(), UserAgent = httpContext?.Request?.Headers["User-Agent"].ToString(), RequestMethod = httpContext?.Request?.Method, RequestUri = httpContext?.Request?.Path.ToString(), Parameters = GetPropertyValue(logEvent, "Parameters"), Result = GetPropertyValue(logEvent, "Result"), Status = GetIntPropertyValue(logEvent, "Status"), ErrorCode = GetPropertyValue(logEvent, "ErrorCode"), RequestID = id, ObjID= GetPropertyValue(logEvent, "ObjID"), ObjType = GetPropertyValue(logEvent, "ObjType"), ErrorMessage = logEvent.Exception?.ToString(), CostTime = GetIntPropertyValue(logEvent, "CostTime") }, null); } else if (!string.IsNullOrEmpty(id)) //只记录请求日志 { return (null, new Sys_OperationLog { SysName = _sysName, OperationType = GetPropertyValue(logEvent, "OperationType"), ModuleName = GetPropertyValue(logEvent, "ModuleName"), Description = logEvent.RenderMessage(), UserID = GetPropertyValue(logEvent, "UserID"), UserName = GetPropertyValue(logEvent, "UserName"), CreateUser = GetPropertyValue(logEvent, "UserName"), CreateTime = logEvent.Timestamp.DateTime, IP = GetPropertyValue(logEvent, "IP"), UserAgent = GetPropertyValue(logEvent, "UserAgent"), RequestMethod = httpContext?.Request?.Method, RequestUri = httpContext?.Request?.Path.ToString(), Parameters = GetPropertyValue(logEvent, "Parameters"), Result = GetPropertyValue(logEvent, "Result"), Status = GetIntPropertyValue(logEvent, "Status"), ErrorCode = GetPropertyValue(logEvent, "ErrorCode"), ErrorMessage = logEvent.MessageTemplate.ToStrNoNull(), RequestID =id, ObjID = GetPropertyValue(logEvent, "ObjID"), ObjType = GetPropertyValue(logEvent, "ObjType"), CostTime = GetIntPropertyValue(logEvent, "CostTime") }); } else { return (null,null); } } private string GetPropertyValue(LogEvent logEvent, string propertyName) { if (logEvent.Properties.TryGetValue(propertyName, out var value)) { return value.ToString().Trim('"'); } return string.Empty; } private int? GetIntPropertyValue(LogEvent logEvent, string propertyName) { if (logEvent.Properties.TryGetValue(propertyName, out var value) && int.TryParse(value.ToString().Trim('"'), out var result)) { return result; } return null; } } /// ///OperationLog,操作日志(一个月) /// [Serializable] [Table(name: "Sys_OperationLog")] public partial class Sys_OperationLog : EntityBase { /// /// 操作类型(如 LOGIN, CREATE, UPDATE, DELETE, EXPORT, API_CALL 等) /// [Display(Name = "操作类型(如 LOGIN, CREATE, UPDATE, DELETE, EXPORT, API_CALL 等)")] [StringLength(200)] public string OperationType { get; set; } /// /// 系统名称 /// [Display(Name = "系统名称")] [StringLength(400)] public string SysName { get; set; } /// /// 模块名称 /// [Display(Name = "模块名称")] [StringLength(400)] public string ModuleName { get; set; } /// /// 操作描述(如 用户登录系统, 删除ID: 1001) /// [Display(Name = "操作描述(如 用户登录系统, 删除ID: 1001)")] [StringLength(2000)] public string Description { get; set; } /// /// 操作用户ID /// [Display(Name = "操作用户ID")] [StringLength(144)] public string UserID { get; set; } /// /// 用户名称 /// [Display(Name = "用户名称")] [StringLength(144)] public string UserName { get; set; } /// /// IP /// [Display(Name = "IP")] [StringLength(180)] public string IP { get; set; } /// /// 用户客户端信息(浏览器、设备等) /// [Display(Name = "用户客户端信息(浏览器、设备等)")] [StringLength(2000)] public string UserAgent { get; set; } /// /// HTTP请求方法(如 GET, POST, DELETE) /// [Display(Name = "HTTP请求方法(如 GET, POST, DELETE)")] [StringLength(40)] public string RequestMethod { get; set; } /// /// 请求url /// [Display(Name = "请求url")] [StringLength(2000)] public string RequestUri { get; set; } /// /// 请求参数或操作详情(JSON格式,记录请求体或关键参数) /// [Display(Name = "请求参数或操作详情(JSON格式,记录请求体或关键参数)")] [StringLength(2147483647)] public string Parameters { get; set; } /// /// 操作结果 /// [Display(Name = "操作结果")] [StringLength(2147483647)] public string Result { get; set; } /// /// 操作状态 /// [Display(Name = "操作状态")] public int? Status { get; set; } /// /// 错误码 /// [Display(Name = "错误码")] [StringLength(200)] public string ErrorCode { get; set; } /// /// 错误详情 /// [Display(Name = "错误详情")] [StringLength(2147483647)] public string ErrorMessage { get; set; } /// /// 对象ID /// [Display(Name = "对象ID")] [StringLength(144)] public string RequestID { get; set; } /// /// 耗时 /// [Display(Name = "耗时")] public int? CostTime { get; set; } public string ObjID { get; set; } public string ObjType { get; set; } } /// ///ErrorLog,异常日志 /// [Serializable] [Table(name: "Sys_ErrorLog")] public partial class Sys_ErrorLog : EntityBase { /// /// 操作类型(如 LOGIN, CREATE, UPDATE, DELETE, EXPORT, API_CALL 等) /// [Display(Name = "操作类型(如 LOGIN, CREATE, UPDATE, DELETE, EXPORT, API_CALL 等)")] [StringLength(200)] public string OperationType { get; set; } /// /// 系统名称 /// [Display(Name = "系统名称")] [StringLength(400)] public string SysName { get; set; } /// /// 模块名称 /// [Display(Name = "模块名称")] [StringLength(400)] public string ModuleName { get; set; } /// /// 操作描述(如 用户登录系统, 删除ID: 1001) /// [Display(Name = "操作描述(如 用户登录系统, 删除ID: 1001)")] [StringLength(2000)] public string Description { get; set; } /// /// 操作用户ID /// [Display(Name = "操作用户ID")] [StringLength(144)] public string UserID { get; set; } /// /// 用户名称 /// [Display(Name = "用户名称")] [StringLength(144)] public string UserName { get; set; } /// /// IP /// [Display(Name = "IP")] [StringLength(180)] public string IP { get; set; } /// /// 用户客户端信息(浏览器、设备等) /// [Display(Name = "用户客户端信息(浏览器、设备等)")] [StringLength(2000)] public string UserAgent { get; set; } /// /// HTTP请求方法(如 GET, POST, DELETE) /// [Display(Name = "HTTP请求方法(如 GET, POST, DELETE)")] [StringLength(40)] public string RequestMethod { get; set; } /// /// 请求url /// [Display(Name = "请求url")] [StringLength(2000)] public string RequestUri { get; set; } /// /// 请求参数或操作详情(JSON格式,记录请求体或关键参数) /// [Display(Name = "请求参数或操作详情(JSON格式,记录请求体或关键参数)")] [StringLength(2147483647)] public string Parameters { get; set; } /// /// 操作结果 /// [Display(Name = "操作结果")] [StringLength(2147483647)] public string Result { get; set; } /// /// 操作状态 /// [Display(Name = "操作状态")] public int? Status { get; set; } /// /// 错误码 /// [Display(Name = "错误码")] [StringLength(200)] public string ErrorCode { get; set; } /// /// 错误详情 /// [Display(Name = "错误详情")] [StringLength(2147483647)] public string ErrorMessage { get; set; } /// /// 对象ID /// [Display(Name = "对象ID")] [StringLength(144)] public string RequestID { get; set; } /// /// 耗时 /// [Display(Name = "耗时")] public int? CostTime { get; set; } public string ObjID { get; set; } public string ObjType { get; set; } } } /// CREATE TABLESPACE LOGS_DATA DATAFILE 'LOGS_DATA01.DBF' SIZE 1024 AUTOEXTEND ON; ///ALTER TABLE Sys_ErrorLog MOVE TABLESPACE LOGS_DATA; /// ALTER TABLE Sys_OperationLog MOVE TABLESPACE LOGS_DATA; /// ALTER TABLE SYS_OPERATIONLOGHIS MOVE TABLESPACE LOGS_DATA; /// GRANT USE TABLESPACE LOGS_DATA TO BZPT; ///-- 则需要授予对这些表的具体操作权限,例如查询、插入、更新、删除等 ///GRANT SELECT, INSERT, UPDATE, DELETE ON Sys_ErrorLog TO BZPT; ///GRANT SELECT, INSERT, UPDATE, DELETE ON Sys_OperationLog TO BZPT; ///GRANT SELECT, INSERT, UPDATE, DELETE ON SYS_OPERATIONLOGHIS TO BZPT; //--删除表和索引 //DROP TABLE IF EXISTS Sys_OperationLog; //DROP INDEX IF EXISTS idx_module_type; //DROP INDEX IF EXISTS idx_resource; //DROP INDEX IF EXISTS Index_USER_TIME; //DROP INDEX IF EXISTS Index_OPTime; //DROP INDEX IF EXISTS Index_SysTimeOP; //--创建表 Sys_OperationLog(修正后的分区语法) //CREATE TABLE Sys_OperationLog ( // ID BIGINT NOT NULL COMMENT 'ID', // OperationType VARCHAR(50) COMMENT '操作类型', // SysName VARCHAR(100) COMMENT '系统名称', // ModuleName VARCHAR(100) COMMENT '模块名称', // Description VARCHAR(500) COMMENT '操作描述', // UserID VARCHAR(36) COMMENT '用户ID', // UserName VARCHAR(36) COMMENT '用户名称', // OperationTime TIMESTAMP COMMENT '操作时间(精确到毫秒)', // IP VARCHAR(45) COMMENT 'IP', // UserAgent VARCHAR(500) COMMENT '客户端信息', // RequestMethod VARCHAR(10) COMMENT 'HTTP方法', // RequestUri VARCHAR(500) COMMENT '请求URI', // Parameters TEXT COMMENT '请求参数', // Result1 TEXT COMMENT '操作结果', // Status INT COMMENT '状态', // ErrorCode VARCHAR(50) COMMENT '错误码', // ErrorMessage TEXT COMMENT '错误详情', // ObjType VARCHAR(200) COMMENT '对象类型', // ObjID VARCHAR(36) COMMENT '对象ID', // CostTime INT COMMENT '耗时', // PRIMARY KEY (ID) //) //TABLESPACE LOGS_DATA //PARTITION BY RANGE (OperationTime) ( -- 直接使用日期字符串 // PARTITION P2025H1 VALUES LESS THAN (TO_TIMESTAMP('2025-07-01', 'YYYY-MM-DD')), // PARTITION P2025H2 VALUES LESS THAN (TO_TIMESTAMP('2026-01-01', 'YYYY-MM-DD')), // PARTITION P2026H1 VALUES LESS THAN (TO_TIMESTAMP('2026-07-01', 'YYYY-MM-DD')), // PARTITION P2026H2 VALUES LESS THAN (TO_TIMESTAMP('2027-01-01', 'YYYY-MM-DD')), // PARTITION P2027H1 VALUES LESS THAN (TO_TIMESTAMP('2027-07-01', 'YYYY-MM-DD')), // PARTITION P2027H2 VALUES LESS THAN (TO_TIMESTAMP('2028-01-01', 'YYYY-MM-DD')), // PARTITION P2028H1 VALUES LESS THAN (TO_TIMESTAMP('2028-07-01', 'YYYY-MM-DD')), // PARTITION P2028H2 VALUES LESS THAN (TO_TIMESTAMP('2029-01-01', 'YYYY-MM-DD')), // PARTITION P2029H1 VALUES LESS THAN (TO_TIMESTAMP('2029-07-01', 'YYYY-MM-DD')), // PARTITION P2029H2 VALUES LESS THAN (TO_TIMESTAMP('2030-01-01', 'YYYY-MM-DD')), // PARTITION P2030H1 VALUES LESS THAN (TO_TIMESTAMP('2030-07-01', 'YYYY-MM-DD')), // PARTITION P2030H2 VALUES LESS THAN (TO_TIMESTAMP('2031-01-01', 'YYYY-MM-DD')) //); //--添加表注释 //COMMENT ON TABLE Sys_OperationLog IS '操作日志(一个月)'; //--创建索引(保持原样) //CREATE INDEX Index_SysTimeOP ON Sys_OperationLog(SysName, OperationTime DESC); //CREATE INDEX Index_OPTime ON Sys_OperationLog(OperationTime DESC); //CREATE INDEX Index_USER_TIME ON Sys_OperationLog(UserID, OperationTime DESC); //CREATE INDEX idx_resource ON Sys_OperationLog(ObjType, ObjID); //CREATE INDEX idx_module_type ON Sys_OperationLog(ModuleName, OperationType);