TemplatePro/BZPT.Api/Middleware/LoggingWithDMSink.cs

554 lines
20 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);