1、添加审核日志实体
1.1 实体定义
在项目 Electric.Entity,添加文件夹:AuditLogs,并添加类:EleAuditLog。
EleAuditLog 完整代码如下:
namespace Electric.Entity.AuditLogs; /// <summary> /// 审核日志 /// </summary> [Index(nameof(AuditLogType))] public class EleAuditLog : EleEntity { /// <summary> /// API接口地址 /// </summary> [MaxLength(256)] [Comment("API接口地址")] public string ApiUrl { get; set; } = string.Empty; /// <summary> /// 接口的方法 /// </summary> [MaxLength(256)] [Comment("接口的方法")] public string Method { get; set; } = string.Empty; /// <summary> /// 参数 /// </summary> [MaxLength(1024)] [Comment("参数")] public string Parameters { get; set; } = string.Empty; /// <summary> /// 返回结果 /// </summary> [Comment("返回结果")] public string ReturnValue { get; set; } = string.Empty; /// <summary> /// 执行时间 /// </summary> [Comment("执行时间")] public int ExecutionDuration { get; set; } /// <summary> /// 客户端IP /// </summary> [MaxLength(64)] [Comment("客户端IP")] public string ClientIpAddress { get; set; } = string.Empty; /// <summary> /// 浏览器信息 /// </summary> [MaxLength(512)] [Comment("浏览器信息")] public string BrowserInfo { get; set; } = string.Empty; /// <summary> /// 日志类型 /// </summary> [Comment("日志类型 0:正常日志记录,99:异常日志")] public AuditLogType AuditLogType { get; set; } /// <summary> /// 异常信息 /// </summary> [MaxLength(1024)] [Comment("异常信息")] public string ExceptionMessage { get; set; } = string.Empty; /// <summary> /// 详细异常 /// </summary> [MaxLength(2000)] [Comment("详细异常")] public string Exception { get; set; } = string.Empty; } /// <summary> /// 审核日志类型 /// </summary> public enum AuditLogType { /// <summary> /// 正常日志记录 /// </summary> Info, /// <summary> /// 异常日志 /// </summary> Exception = 99 }
1.2 添加至上下文
在项目 Electric.EntityFrameworkCore,添加至 ApplicationDbContext。
public DbSet<EleAuditLog> EleAuditLog { get; set; }
1.3 添加数据库迁移
打开:程序包管理器控制台,操作步骤:菜单栏找到:工具 =》NuGet 包管理器 =》程序包管理器控制台。
默认项目:选择 Electric.DbMigrator,并把 Electric.DbMigrator 设置为默认启动项目,输入:Add-Migration InitDB,并回车。其中:Add-Migration 为迁移命令,InitDB:这个是迁移的名称,可以根据自己自定义。
在程序包管理器控制台,输入命令:Update-Database,并回车。
在数据库就可以看到对应的表:
2、添加审核日志服务
2.1 添加接口
在项目 Electric.Service,添加文件夹:AuditLogs,并添加接口:IAuditLogService
IAuditLogService 完整代码:
namespace Electric.Service.AuditLogs; public interface IAuditLogService { /// <summary> /// 插入审核日志 /// </summary> /// <param name="eleAuditLog"></param> /// <returns></returns> EleAuditLog Add(EleAuditLog eleAuditLog); }
接口说明:
定义添加日志方法,用于记录日志。
2.2 添加实现类
在项目 Electric.Service,文件夹 AuditLogs 下添加 AuditLogService。
AuditLogService 完整代码如下:
namespace Electric.Service.AuditLogs; /// <summary> /// 审核日志 /// </summary> public class AuditLogService : BaseService, IAuditLogService { private readonly IBaseRepository<EleAuditLog> _auditLogRepository; private readonly ILogger<AuditLogService> _logger; public AuditLogService(IBaseRepository<EleAuditLog> auditLogRepository, ILogger<AuditLogService> logger) { _auditLogRepository = auditLogRepository; _logger = logger; } /// <summary> /// 插入审核日志 /// </summary> /// <param name="eleAuditLog"></param> /// <returns></returns> public EleAuditLog Add(EleAuditLog eleAuditLog) { try { return _auditLogRepository.Add(eleAuditLog); } catch (Exception ex) { _logger.LogError(ex, "保存日志至数据库异常,接口:{0}\r\nMethod:{1}\r\n参数:{2}\r\nIP:{3}\r\n花费时长:{4}", eleAuditLog.ApiUrl, eleAuditLog.Method, eleAuditLog.Parameters, eleAuditLog.ClientIpAddress, eleAuditLog.ExecutionDuration); return eleAuditLog; } } }
代码说明:
- 继承:BaseService, IAuditLogService;
- 实现接口:调用仓储对象完成日志记录;这边添加了 try...catch,避免记录日志发生异常影响正常业务的功能。
3、API 接口方法过滤器
3.1 添加方法过滤器
在项目 Electric.API,添加文件夹 AuditLog,并添加类 EletricActionFilterAttribute。
EletricActionFilterAttribute 完整代码如下:
/// <summary> /// 方法过滤器 /// </summary> public class EletricActionFilterAttribute : ActionFilterAttribute { private EleAuditLog _auditLog; private readonly IAuditLogService _auditLogService; private readonly UserManager<EleUser> _userManager; private readonly ILogger<ElectricExceptionFilterAttribute> _logger; /// <summary> /// 执行前的时间 /// </summary> private DateTime _executionBefore; /// <summary> /// 执行后的世界 /// </summary> private DateTime _executionAfter; /// <summary> /// 依赖注入日志对象 /// </summary> /// <param name="auditLogService"></param> /// <param name="userManager"></param> public EletricActionFilterAttribute(IAuditLogService auditLogService, UserManager<EleUser> userManager, ILogger<ElectricExceptionFilterAttribute> logger) { _auditLog = new EleAuditLog(); _auditLogService = auditLogService; _userManager = userManager; _logger = logger; } /// <summary> /// 方法执行前 /// </summary> /// <param name="context"></param> public override void OnActionExecuting(ActionExecutingContext context) { _executionBefore = DateTime.Now; _auditLog = AuditLogHelper.GenerateRequestAuditLog(context.HttpContext, _userManager); } /// <summary> /// 方法执行完毕 /// </summary> /// <param name="context"></param> public override void OnActionExecuted(ActionExecutedContext context) { _executionAfter = DateTime.Now; _auditLog.ExecutionDuration = (int)(_executionAfter - _executionBefore).TotalMilliseconds; _auditLog.ReturnValue = context.Result == null ? string.Empty : JsonConvert.SerializeObject(context.Result); //异常信息 if (context.Exception != null) { _auditLog.ExceptionMessage = context.Exception.Message; if (_auditLog.ExceptionMessage.Length > 1024) { _auditLog.ExceptionMessage = _auditLog.ExceptionMessage.Substring(0, 1024); } _auditLog.Exception = context.Exception.ToString(); if (_auditLog.Exception.Length > 2000) { _auditLog.Exception = _auditLog.Exception.Substring(0, 2000); } _auditLog.AuditLogType = AuditLogType.Exception; //记录文件异常日志 _logger.LogError(context.Exception, "接口:{0}\r\nMethod:{1}\r\n参数:{2}\r\nIP:{3}\r\n花费时长:{4}", _auditLog.ApiUrl, _auditLog.Method, _auditLog.Parameters, _auditLog.ClientIpAddress, _auditLog.ExecutionDuration); } _auditLogService.Add(_auditLog); } }
代码说明:
- 继承 ActionFilterAttribute,并重载方法 OnActionExecuting、OnActionExecuted,这两个分别为:方法执行前、方法执行后;
- _executionBefore 记录执行前的时间,_executionAfter 记录执行后的时间,并由此计算出接口的执行时间;
- 在 OnActionExecuted,判断 context.Exception 是否为 null,由此判断接口是否发生异常;
- 在 OnActionExecuting,通过调用 AuditLogHelper.GenerateRequestAuditLog,获取接口的请求参数;AuditLogHelper 是我们自定义封装的,便于代码复用。
AuditLogHelper 完整代码如下:
namespace Electric.API.AuditLog; /// <summary> /// 审核日志帮助类 /// </summary> public class AuditLogHelper { /// <summary> /// 请求日志 /// </summary> /// <param name="httpContext"></param> /// <param name="userManager"></param> /// <returns></returns> public static EleAuditLog GenerateRequestAuditLog(HttpContext httpContext, UserManager<EleUser> userManager) { //审核日志 EleAuditLog auditLog = new EleAuditLog(); var request = httpContext.Request; //当前用户 var userName = httpContext.User.Identity.Name; if (!string.IsNullOrEmpty(userName)) { var user = userManager.FindByNameAsync(userName).Result; auditLog.CreatorId = user.Id; } auditLog.ApiUrl = request.Path; auditLog.Method = request.Method; auditLog.AuditLogType = AuditLogType.Info; //ip var remoteIpAddress = httpContext.Connection.RemoteIpAddress; auditLog.ClientIpAddress = remoteIpAddress == null ? string.Empty : remoteIpAddress.MapToIPv4().ToString(); auditLog.BrowserInfo = request.Headers.ContainsKey("User-Agent") ? request.Headers["User-Agent"] : string.Empty; if (request.Method == "GET") { auditLog.Parameters = request.QueryString.Value ?? string.Empty; } else { using (StreamReader reader = new StreamReader(request.Body)) { request.Body.Position = 0; auditLog.Parameters = reader.ReadToEndAsync().Result; request.Body.Position = 0; } } return auditLog; } }
代码说明:
- 根据 Request 对象,获取接口 Url、方法、参数、浏览器信息等等;
- 根据 Identity,获取当前登录信息。
3.2 开启 Body 重用
在 AuditLogHelper 有使用了 request.Body,request.Body 默认是不允许重复读取的。
所以,我们要开启允许 Body 重用。
在 Program.cs,添加如下代码:
添加的代码:
//允许body重用 app.Use(next => context => { context.Request.EnableBuffering(); return next(context); });
开启后,我们就可以重复使用。
3.3 开启过滤器注入
开启全局过滤器注入,默认记录所有 API 请求日志。
在项目 Electric.API 中的 MVCExtension.cs 添加如下代码:
添加的代码:
configure.Filters.Add<EletricActionFilterAttribute>();
namespace Electric.API.Extensions; /// <summary> /// MVC相关扩展 /// </summary> public static class MVCExtension { /// <summary> /// 控制器自定义返回格式 /// </summary> /// <param name="builder"></param> public static void AddElectricControllers(this WebApplicationBuilder builder) { builder.Services.AddControllers(configure => { configure.Filters.Add<UnitOfWorkFilterAttribute>(); configure.Filters.Add<ElectricExceptionFilterAttribute>(); configure.Filters.Add<EletricActionFilterAttribute>(); }) .AddNewtonsoftJson(option => { option.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss"; }); } }
3.4 权限过滤日志
在 Asp.net Core 优先执行 AuthorizationFilter 的,过滤器 ActionFilterAttribute 是在后面执行的,所以我们要在权限过滤器这边,添加因为无权限返回的请求日志。
添加主要代码如下:
添加的代码如下:
//审核日志 var _auditLogService = context.HttpContext.RequestServices.GetService(typeof(IAuditLogService)) as IAuditLogService; var _userManager = context.HttpContext.RequestServices.GetService(typeof(UserManager<EleUser>)) as UserManager<EleUser>; if (hasPermission == null) { var result = new ForbidResult(); context.Result = result; EleAuditLog auditLog = AuditLogHelper.GenerateRequestAuditLog(context.HttpContext, _userManager); auditLog.ReturnValue = JsonConvert.SerializeObject(result); _auditLogService.Add(auditLog); }
3.5 日志效果
启动项目,操作系统功能,就能在数据库看到对应的 API 请求日志,效果如下:
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战