554 lines
20 KiB
C#
554 lines
20 KiB
C#
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
|
||
{
|
||
/// <summary>
|
||
/// 日志记录类
|
||
/// </summary>
|
||
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<LogEvent> events)
|
||
{
|
||
try
|
||
{
|
||
// 批量分离错误日志和操作日志
|
||
var errorLogs = new List<Sys_ErrorLog>();
|
||
var operationLogs = new List<Sys_OperationLog>();
|
||
|
||
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<Task>();
|
||
|
||
if (errorLogs.Count > 0)
|
||
{
|
||
tasks.Add(_db.Fastest<Sys_ErrorLog>().BulkCopyAsync(errorLogs)); // 达梦批量插入
|
||
}
|
||
if (operationLogs.Count > 0)
|
||
{
|
||
tasks.Add(_db.Fastest<Sys_OperationLog>().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;
|
||
}
|
||
}
|
||
/// <summary>
|
||
///OperationLog,操作日志(一个月)
|
||
/// </summary>
|
||
[Serializable]
|
||
[Table(name: "Sys_OperationLog")]
|
||
public partial class Sys_OperationLog : EntityBase<string>
|
||
{
|
||
/// <summary>
|
||
/// 操作类型(如 LOGIN, CREATE, UPDATE, DELETE, EXPORT, API_CALL 等)
|
||
/// </summary>
|
||
|
||
[Display(Name = "操作类型(如 LOGIN, CREATE, UPDATE, DELETE, EXPORT, API_CALL 等)")]
|
||
[StringLength(200)]
|
||
public string OperationType { get; set; }
|
||
|
||
/// <summary>
|
||
/// 系统名称
|
||
/// </summary>
|
||
|
||
[Display(Name = "系统名称")]
|
||
[StringLength(400)]
|
||
public string SysName { get; set; }
|
||
|
||
/// <summary>
|
||
/// 模块名称
|
||
/// </summary>
|
||
|
||
[Display(Name = "模块名称")]
|
||
[StringLength(400)]
|
||
public string ModuleName { get; set; }
|
||
|
||
/// <summary>
|
||
/// 操作描述(如 用户登录系统, 删除ID: 1001)
|
||
/// </summary>
|
||
|
||
[Display(Name = "操作描述(如 用户登录系统, 删除ID: 1001)")]
|
||
[StringLength(2000)]
|
||
public string Description { get; set; }
|
||
|
||
/// <summary>
|
||
/// 操作用户ID
|
||
/// </summary>
|
||
|
||
[Display(Name = "操作用户ID")]
|
||
[StringLength(144)]
|
||
public string UserID { get; set; }
|
||
|
||
/// <summary>
|
||
/// 用户名称
|
||
/// </summary>
|
||
|
||
[Display(Name = "用户名称")]
|
||
[StringLength(144)]
|
||
public string UserName { get; set; }
|
||
|
||
/// <summary>
|
||
/// IP
|
||
/// </summary>
|
||
|
||
[Display(Name = "IP")]
|
||
[StringLength(180)]
|
||
public string IP { get; set; }
|
||
|
||
/// <summary>
|
||
/// 用户客户端信息(浏览器、设备等)
|
||
/// </summary>
|
||
|
||
[Display(Name = "用户客户端信息(浏览器、设备等)")]
|
||
[StringLength(2000)]
|
||
public string UserAgent { get; set; }
|
||
|
||
/// <summary>
|
||
/// HTTP请求方法(如 GET, POST, DELETE)
|
||
/// </summary>
|
||
|
||
[Display(Name = "HTTP请求方法(如 GET, POST, DELETE)")]
|
||
[StringLength(40)]
|
||
public string RequestMethod { get; set; }
|
||
|
||
/// <summary>
|
||
/// 请求url
|
||
/// </summary>
|
||
|
||
[Display(Name = "请求url")]
|
||
[StringLength(2000)]
|
||
public string RequestUri { get; set; }
|
||
|
||
/// <summary>
|
||
/// 请求参数或操作详情(JSON格式,记录请求体或关键参数)
|
||
/// </summary>
|
||
|
||
[Display(Name = "请求参数或操作详情(JSON格式,记录请求体或关键参数)")]
|
||
[StringLength(2147483647)]
|
||
public string Parameters { get; set; }
|
||
|
||
/// <summary>
|
||
/// 操作结果
|
||
/// </summary>
|
||
|
||
[Display(Name = "操作结果")]
|
||
[StringLength(2147483647)]
|
||
public string Result { get; set; }
|
||
|
||
/// <summary>
|
||
/// 操作状态
|
||
/// </summary>
|
||
|
||
[Display(Name = "操作状态")]
|
||
public int? Status { get; set; }
|
||
|
||
/// <summary>
|
||
/// 错误码
|
||
/// </summary>
|
||
|
||
[Display(Name = "错误码")]
|
||
[StringLength(200)]
|
||
public string ErrorCode { get; set; }
|
||
|
||
/// <summary>
|
||
/// 错误详情
|
||
/// </summary>
|
||
|
||
[Display(Name = "错误详情")]
|
||
[StringLength(2147483647)]
|
||
public string ErrorMessage { get; set; }
|
||
|
||
/// <summary>
|
||
/// 对象ID
|
||
/// </summary>
|
||
|
||
[Display(Name = "对象ID")]
|
||
[StringLength(144)]
|
||
public string RequestID { get; set; }
|
||
|
||
/// <summary>
|
||
/// 耗时
|
||
/// </summary>
|
||
|
||
[Display(Name = "耗时")]
|
||
public int? CostTime { get; set; }
|
||
|
||
public string ObjID { get; set; }
|
||
|
||
public string ObjType { get; set; }
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
///ErrorLog,异常日志
|
||
/// </summary>
|
||
[Serializable]
|
||
[Table(name: "Sys_ErrorLog")]
|
||
public partial class Sys_ErrorLog : EntityBase<string>
|
||
{
|
||
/// <summary>
|
||
/// 操作类型(如 LOGIN, CREATE, UPDATE, DELETE, EXPORT, API_CALL 等)
|
||
/// </summary>
|
||
|
||
[Display(Name = "操作类型(如 LOGIN, CREATE, UPDATE, DELETE, EXPORT, API_CALL 等)")]
|
||
[StringLength(200)]
|
||
public string OperationType { get; set; }
|
||
|
||
/// <summary>
|
||
/// 系统名称
|
||
/// </summary>
|
||
|
||
[Display(Name = "系统名称")]
|
||
[StringLength(400)]
|
||
public string SysName { get; set; }
|
||
|
||
/// <summary>
|
||
/// 模块名称
|
||
/// </summary>
|
||
|
||
[Display(Name = "模块名称")]
|
||
[StringLength(400)]
|
||
public string ModuleName { get; set; }
|
||
|
||
/// <summary>
|
||
/// 操作描述(如 用户登录系统, 删除ID: 1001)
|
||
/// </summary>
|
||
|
||
[Display(Name = "操作描述(如 用户登录系统, 删除ID: 1001)")]
|
||
[StringLength(2000)]
|
||
public string Description { get; set; }
|
||
|
||
/// <summary>
|
||
/// 操作用户ID
|
||
/// </summary>
|
||
|
||
[Display(Name = "操作用户ID")]
|
||
[StringLength(144)]
|
||
public string UserID { get; set; }
|
||
|
||
/// <summary>
|
||
/// 用户名称
|
||
/// </summary>
|
||
|
||
[Display(Name = "用户名称")]
|
||
[StringLength(144)]
|
||
public string UserName { get; set; }
|
||
|
||
/// <summary>
|
||
/// IP
|
||
/// </summary>
|
||
|
||
[Display(Name = "IP")]
|
||
[StringLength(180)]
|
||
public string IP { get; set; }
|
||
|
||
/// <summary>
|
||
/// 用户客户端信息(浏览器、设备等)
|
||
/// </summary>
|
||
|
||
[Display(Name = "用户客户端信息(浏览器、设备等)")]
|
||
[StringLength(2000)]
|
||
public string UserAgent { get; set; }
|
||
|
||
/// <summary>
|
||
/// HTTP请求方法(如 GET, POST, DELETE)
|
||
/// </summary>
|
||
|
||
[Display(Name = "HTTP请求方法(如 GET, POST, DELETE)")]
|
||
[StringLength(40)]
|
||
public string RequestMethod { get; set; }
|
||
|
||
/// <summary>
|
||
/// 请求url
|
||
/// </summary>
|
||
|
||
[Display(Name = "请求url")]
|
||
[StringLength(2000)]
|
||
public string RequestUri { get; set; }
|
||
|
||
/// <summary>
|
||
/// 请求参数或操作详情(JSON格式,记录请求体或关键参数)
|
||
/// </summary>
|
||
|
||
[Display(Name = "请求参数或操作详情(JSON格式,记录请求体或关键参数)")]
|
||
[StringLength(2147483647)]
|
||
public string Parameters { get; set; }
|
||
|
||
/// <summary>
|
||
/// 操作结果
|
||
/// </summary>
|
||
|
||
[Display(Name = "操作结果")]
|
||
[StringLength(2147483647)]
|
||
public string Result { get; set; }
|
||
|
||
/// <summary>
|
||
/// 操作状态
|
||
/// </summary>
|
||
|
||
[Display(Name = "操作状态")]
|
||
public int? Status { get; set; }
|
||
|
||
/// <summary>
|
||
/// 错误码
|
||
/// </summary>
|
||
|
||
[Display(Name = "错误码")]
|
||
[StringLength(200)]
|
||
public string ErrorCode { get; set; }
|
||
|
||
/// <summary>
|
||
/// 错误详情
|
||
/// </summary>
|
||
|
||
[Display(Name = "错误详情")]
|
||
[StringLength(2147483647)]
|
||
public string ErrorMessage { get; set; }
|
||
|
||
/// <summary>
|
||
/// 对象ID
|
||
/// </summary>
|
||
|
||
[Display(Name = "对象ID")]
|
||
[StringLength(144)]
|
||
public string RequestID { get; set; }
|
||
|
||
/// <summary>
|
||
/// 耗时
|
||
/// </summary>
|
||
|
||
[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); |