Abp + MongoDb 改造默认的审计日志存储位置
一、背景
在实际项目的开发当中,使用 Abp Zero 自带的审计日志功能写入效率比较低。其次审计日志数据量中后期十分庞大,不适合与业务数据存放在一起。所以我们可以重新实现 Abp 的 IAuditingStore 接口,来让我们的审计日志数据存储在 MongoDb 当中。
二、实现
2.0 引入相关包
这里我们需要在模块项目引入 Abp 与 mongocsharpdriver 包,引入之后项目如下图。

2.1 实体封装
基于 Abp 框架的设计,它许多组件都可以随时被我们所替换。这里我们先定义存储到 MongoDb 数据库的实体,取名叫做 MongoDbAuditEntity。下面就是它的基本定义,它是我从 Zero 里面单独扒出来的,是基于 Abp 的审计信息定义重新进行封装的一个实体。
using System;
using System.Linq;
using Abp.Extensions;
using Abp.Runtime.Validation;
using Abp.UI;
namespace Abp.Auditing.MongoDb
{
/// <summary>
/// 审计日志记录实体,仅用于 MongoDb 存储使用。
/// </summary>
public class MongoDbAuditEntity
{
/// <summary>
/// <see cref="ServiceName"/> 属性的最大长度。
/// </summary>
public static int MaxServiceNameLength = 256;
/// <summary>
/// <see cref="MethodName"/> 属性的最大长度。
/// </summary>
public static int MaxMethodNameLength = 256;
/// <summary>
/// <see cref="Parameters"/> 属性的最大长度。
/// </summary>
public static int MaxParametersLength = 1024;
/// <summary>
/// <see cref="ClientIpAddress"/> 属性的最大长度。
/// </summary>
public static int MaxClientIpAddressLength = 64;
/// <summary>
/// <see cref="ClientName"/> 属性的最大长度。
/// </summary>
public static int MaxClientNameLength = 128;
/// <summary>
/// <see cref="BrowserInfo"/> 属性的最大长度。
/// </summary>
public static int MaxBrowserInfoLength = 512;
/// <summary>
/// <see cref="Exception"/> 属性的最大长度。
/// </summary>
public static int MaxExceptionLength = 2000;
/// <summary>
/// <see cref="CustomData"/> 属性的最大长度。
/// </summary>
public static int MaxCustomDataLength = 2000;
/// <summary>
/// 调用接口时用户的编码,如果是匿名访问,则可能为 null。
/// </summary>
public string UserCode { get; set; }
/// <summary>
/// 调用接口时用户的集团 Id,如果是匿名访问,则可能为 null。
/// </summary>
public int? GroupId { get; set; }
/// <summary>
/// 调用接口时,请求的应用服务/控制器名称。
/// </summary>
public string ServiceName { get; set; }
/// <summary>
/// 调用接口时,请求的的具体方法/接口名称。
/// </summary>
public string MethodName { get; set; }
/// <summary>
/// 调用接口时,传递的具体参数。
/// </summary>
public string Parameters { get; set; }
/// <summary>
/// 调用接口的时间,以服务器的时间进行记录。
/// </summary>
public DateTime ExecutionTime { get; set; }
/// <summary>
/// 调用接口执行方法时所消耗的时间,以毫秒为单位。
/// </summary>
public int ExecutionDuration { get; set; }
/// <summary>
/// 调用接口时客户端的 IP 地址。
/// </summary>
public string ClientIpAddress { get; set; }
/// <summary>
/// 调用接口时客户端的名称(通常为计算机名)。
/// </summary>
public string ClientName { get; set; }
/// <summary>
/// 调用接口的浏览器信息。
/// </summary>
public string BrowserInfo { get; set; }
/// <summary>
/// 调用接口时如果产生了异常,则记录在本字段,如果没有异常则可能 null。
/// </summary>
public string Exception { get; set; }
/// <summary>
/// 自定义数据
/// </summary>
public string CustomData { get; set; }
/// <summary>
/// 从给定的 <see cref="auditInfo"/> 审计信息创建一个新的 MongoDb 审计日志实体
/// (<see cref="MongoDbAuditEntity"/>)。
/// </summary>
/// <param name="auditInfo">原始审计日志信息。</param>
/// <returns>创建完成的 <see cref="MongoDbAuditEntity"/> 实体对象。</returns>
public static MongoDbAuditEntity CreateFromAuditInfo(AuditInfo auditInfo)
{
var expMsg = GetAbpClearException(auditInfo.Exception);
return new MongoDbAuditEntity
{
UserCode = auditInfo.UserId?.ToString(),
GroupId = null,
ServiceName = auditInfo.ServiceName.TruncateWithPostfix(MaxServiceNameLength),
MethodName = auditInfo.MethodName.TruncateWithPostfix(MaxMethodNameLength),
Parameters = auditInfo.Parameters.TruncateWithPostfix(MaxParametersLength),
ExecutionTime = auditInfo.ExecutionTime,
ExecutionDuration = auditInfo.ExecutionDuration,
ClientIpAddress = auditInfo.ClientIpAddress.TruncateWithPostfix(MaxClientIpAddressLength),
ClientName = auditInfo.ClientName.TruncateWithPostfix(MaxClientNameLength),
BrowserInfo = auditInfo.BrowserInfo.TruncateWithPostfix(MaxBrowserInfoLength),
Exception = expMsg.TruncateWithPostfix(MaxExceptionLength),
CustomData = auditInfo.CustomData.TruncateWithPostfix(MaxCustomDataLength)
};
}
public override string ToString()
{
return string.Format(
"审计日志: {0}.{1} 由用户 {2} 执行,花费了 {3} 毫秒,请求的源 IP 地址为: {4} 。",
ServiceName, MethodName, UserCode, ExecutionDuration, ClientIpAddress
);
}
/// <summary>
/// 创建更加清楚明确的异常信息。
/// </summary>
/// <param name="exception">要处理的异常数据。</param>
private static string GetAbpClearException(Exception exception)
{
var clearMessage = "";
switch (exception)
{
case null:
return null;
case AbpValidationException abpValidationException:
clearMessage = "异常为参数验证错误,一共有 " + abpValidationException.ValidationErrors.Count + "个错误:";
foreach (var validationResult in abpValidationException.ValidationErrors)
{
var memberNames = "";
if (validationResult.MemberNames != null && validationResult.MemberNames.Any())
{
memberNames = " (" + string.Join(", ", validationResult.MemberNames) + ")";
}
clearMessage += "\r\n" + validationResult.ErrorMessage + memberNames;
}
break;
case UserFriendlyException userFriendlyException:
clearMessage =
$"业务相关错误,错误代码: {userFriendlyException.Code} \r\n 异常详细信息: {userFriendlyException.Details}";
break;
}
return exception + (string.IsNullOrEmpty(clearMessage) ? "" : "\r\n\r\n" + clearMessage);
}
}
}
2.2 编写 MongoDb 配置类
一般来说,我们编写一个 Abp 模块肯定是需要构建一个配置类的,以便其他开发人员在使用我们的模块可以进行一些自定义配置。这里我们的 MongoDb 审计日志模块无非就是需要配置两个信息,第一个就是 MongoDb 数据库的连接字符串,第二个就是要存储的库名称。
/// <summary>
/// 审计日志的 MongoDb 存储模块。
/// </summary>
public interface IAuditingMongoDbConfiguration
{
/// <summary>
/// MongoDb 连接字符串。
/// </summary>
string ConnectionString { get; set; }
/// <summary>
/// 要连接的 MongoDb 数据库名称
/// </summary>
string DataBaseName { get; set; }
}
同理,再编写一个实现。
public class AuditingMongoDbConfiguration : IAuditingMongoDbConfiguration
{
public string ConnectionString { get; set; }
public string DataBaseName { get; set; }
}
2.3 编写 IMongoClient 的工厂类
其实你直接 new 也可以,这里编写一个工厂类是省去一些构建流程而已,首先为工厂类定义一个接口,该接口只有一个方法,就是创建 IMongoClient 的实例对象。
public interface IMongoClientFactory
{
IMongoClient Create();
}
这个工厂的实现也很简单,只不过我们在工厂当中注入了 IAuditingMongoDbConfiguration ,方便我们创建实例。
public class MongoClientFactory : IMongoClientFactory
{
private readonly IAuditingMongoDbConfiguration _mongoDbConfiguration;
public MongoClientFactory(IAuditingMongoDbConfiguration mongoDbConfiguration)
{
_mongoDbConfiguration = mongoDbConfiguration;
}
public IMongoClient Create()
{
return new MongoClient(_mongoDbConfiguration.ConnectionString);
}
}
2.4 审计日志的具体存储动作
上面几点都是做一些准备工作,下面我们需要实现 IAuditingStore 接口,以便将我们的审计日志存储在 MongoDb 数据库当中。IAuditingStore 接口只定义了一个方法,就是 SaveAsync(AuditInfo auditInfo) 方法。该方法是在每次接口请求的时候,通过过滤器/拦截器的时候会被调用。当然整个审计日志的构成不是这么简单的,如果大家有兴趣可以查看我的另一篇博客 《[Abp 源码分析] 十五、自动审计记录》 ,在这篇博客有详细讲述审计日志的相关知识。
我们接着继续,因为 SaveAsync(AuditInfo auditInfo) 方法传入了一个 AuditInfo 对象,我们就可以基于这个对象来构造我们的数据实体。构造完成之后,将其通过 IMongoClient 对象存储到 MongoDb 数据库当中。
/// <summary>
/// <see cref="IAuditingStore"/> 的特殊实现,使用的是 MongoDb 作为持久化存储。
/// </summary>
public class MongoDbAuditingStore : IAuditingStore
{
private readonly IMongoClientFactory _clientFactory;
private readonly IAuditingMongoDbConfiguration _mongoDbConfiguration;
public MongoDbAuditingStore(IMongoClientFactory clientFactory, IAuditingMongoDbConfiguration mongoDbConfiguration)
{
_clientFactory = clientFactory;
_mongoDbConfiguration = mongoDbConfiguration;
}
public async Task SaveAsync(AuditInfo auditInfo)
{
var entity = MongoDbAuditEntity.CreateFromAuditInfo(auditInfo);
await _clientFactory.Create()
.GetDatabase(_mongoDbConfiguration.DataBaseName)
.GetCollection<MongoDbAuditEntity>(typeof(MongoDbAuditEntity).Name)
.InsertOneAsync(entity);
}
}
可以看到整体代码还是十分简单的,直接通过 auditInfo 对象构造好数据实体之后,插入到 MongoDb 数据库当中。
2.5 编写模块类
每一个基于 Abp 的第三方模块都会有一个模块类,模块类的主要作用就是针对于第三方模块进行一些基本配置,以及对一些组件的替换动作。
using Abp.Auditing.MongoDb.Configuration;
using Abp.Auditing.MongoDb.Infrastructure;
using Abp.Dependency;
using Abp.Modules;
namespace Abp.Auditing.MongoDb
{
[DependsOn(typeof(AbpKernelModule))]
public class AbpAuditingMongoDbModule : AbpModule
{
public override void PreInitialize()
{
IocManager.Register<IAuditingMongoDbConfiguration,AuditingMongoDbConfiguration>();
IocManager.Register<IMongoClientFactory,MongoClientFactory>();
// 替换自带的审计日志存储实现
Configuration.ReplaceService(typeof(IAuditingStore),() =>
{
IocManager.Register<IAuditingStore, MongoDbAuditingStore>(DependencyLifeStyle.Transient);
});
}
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(typeof(AbpAuditingMongoDbModule).Assembly);
}
}
}
2.6 编写集成的扩展方法
Abp 模块都会基于 IModuleConfigurations 接口编写一个扩展方法,这样其他基于 Abp 框架的项目开发人员就可以很方便地在其启动模块的 PreInitialzie() 方法当中通过 Configuration.Modules 来进行配置。
/// <summary>
/// MongoDb 审计日志存储提供器的配置类的扩展方法。
/// </summary>
public static class AuditingMongoDbConfigurationExtensions
{
/// <summary>
/// 配置审计日志的 MongoDb 实现的相关参数。
/// </summary>
/// <param name="modules">模块配置类</param>
/// <param name="connectString">MongoDb 连接字符串。</param>
/// <param name="dataBaseName">要操作的 MongoDb 数据库。</param>
public static void ConfigureMongoDbAuditingStore(this IModuleConfigurations modules,string connectString,string dataBaseName)
{
var configuration = modules.AbpConfiguration.Get<IAuditingMongoDbConfiguration>();
configuration.ConnectionString = connectString;
configuration.DataBaseName = dataBaseName;
}
}
三、测试
新建一个项目,并添加对我们库的引用,在其启动模块当中添加对 AbpAuditingMongoDbModule 模块的依赖,在其 PreInitialize() 方法当中加入以下代码,以配置审计日志相关功能。
[DependsOn(typeof(AbpAuditingMongoDbModule))]
public class StartupModule : AbpModule
{
public override void PreInitialize()
{
// 其他代码...
// 开启审计日志记录
Configuration.Auditing.IsEnabled = true;
// 允许记录匿名用户请求
Configuration.Auditing.IsEnabledForAnonymousUsers = true;
// 配置 MonggoDb 数据库地址与名称
Configuration.Modules.ConfigureMongoDbAuditingStore("mongodb://username:Zpassword@ip:port","TestDataBase");
// 其他代码...
}
}
启动项目之后,我们尝试访问测试方法,之后来到 MongoDb 数据库当中,查看具体的审计日志信息。

可以看到,所有对接口的请求都被记录到了 MongoDb 当中,这样后续可以基于这些数据进行二次分析。
四、结语
Abp.Auditing.MongoDb 包 GitHub 地址
Abp + MongoDb 改造默认的审计日志存储位置的更多相关文章
- ABP官方文档翻译 4.6 审计日志
审计日志 介绍 关于IAuditingStore 配置 通过特性启用/禁用 注意事项 介绍 维基百科:“审计追踪(也称为审计日志)是与安全相关的按时间先后的记录.记录集合.记录的目的地和源,提供一系列 ...
- CentOS7更改Docker默认镜像和容器存储位置
图片出处:https://bobcares.com/wp-content/uploads/docker-change-directory.jpg 一.Why? 通常,当你开始使用docker时,我们并 ...
- Python logging模块日志存储位置踩坑
问题描述 项目过程中写了一个小模块,设计到了日志存储的问题,结果发现了个小问题. 代码结构如下: db.py run.py 其中db.py是操作数据库抽象出来的一个类,run.py是业务逻辑代码.两个 ...
- 更改DHCP服务器默认日志存储位置
DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)是一种有效的IP 地址分配手段,已经广泛地应用于各种局域网管理.它能动态地向网络中每台计算机分配唯一 ...
- docker之容器日志存储位置及把运行日志记录至文件
参考:https://www.cnblogs.com/YatHo/p/7866029.html docker启动后日志会在以下位置 /var/lib/docker/containers/容器ID/容器 ...
- Docker Toolbox替换默认docker machine的存储位置
使用Docker Toolbox是因为它不用打开windows的hyper-v组件,这样可以和VMware workstation一起使用. 关于如何迁移可参考:https://www.cnblogs ...
- ABP开发框架前后端开发系列---(7)系统审计日志和登录日志的管理
我们了解ABP框架内部自动记录审计日志和登录日志的,但是这些信息只是在相关的内部接口里面进行记录,并没有一个管理界面供我们了解,但是其系统数据库记录了这些数据信息,我们可以为它们设计一个查看和导出这些 ...
- .Net Core 审计日志实现
前言: 近日在项目协同开发过程中出现了问题,数据出现了异常:其他人员怀疑项目数据丢失程序存在问题.于是通过排查程序提供的审计日志最终还原了当时操作及原因. 可见审计日志在排查.定位问题是相当有用的,那 ...
- ABP(现代ASP.NET样板开发框架)系列之19、ABP应用层——审计日志
点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之19.ABP应用层——审计日志 ABP是“ASP.NET Boilerplate Project (ASP.NET ...
随机推荐
- Markdown 尝试
目录 简介 参数模型 vs. 非参数模型 创新点 at the modeling level at the training procedure 模型结构 attention kernel Full ...
- Altium Designer 16 问题解决
1:同一个工程中,不同原理图里的网络标号不能关联起来 解决---> 选择 工程->工程参数->网络识别符范围 -> GLOBAL 2:PCB中影藏显示相应Net的飞线 解 ...
- es6数值
ES6在Number对象上,新提供了Number.isFinite()和Number.isNaN()两个方法,用来检查Infinite和NaN这两个特殊值. Number.isFinite(15); ...
- 运用PIL库 用来美白,磨皮,瘦脸等操作!
1.安装pillow库: 在cmd下,输入简单的命令: pip install pillow 即可安装pillow库. 2.PIL库的简介: 1. PIL库主要有2个方面的功能: (1) 图像归档: ...
- 【竞价网站绝技】根据访客ip,页面显示访客所属城市的html代码(借用YY IP地址库)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- C语言的数据类型的本质和提高学习
一.数据类型的概念 类型是对数据的抽象 类型是相同的数据有相同的表示形式.存储格式以及相关的操作 程序中使用的数据必定属于某一种数据类型 1.算术类型: 包括三种类型:整数类型.浮点类型,枚举型. ...
- 在JAVA中对于类,对象,继承,多态的看法
这是我第一次学高级语言.很抱歉选择了JAVA,目标是开发一款可以上线的APP. 类:是建立对象的一个模板,就是系列产品中的基础款图纸.只是图纸而已.不是产品. 对象:是一个可以操作的对象.新建一个对象 ...
- 解决更新ssh后在/etc/init.d下无sshd的问题
1.将远程服务器的/etc/init.d/ssd 文件拷贝到本地 scp /etc/init.d/ssh root@IP地址:/etc/init.d 2.vi /etc/init.d/sshd 3 ...
- redis_字符串对象
Redis总共支持五种数据类型:string,hash,list,set及zset.这里介绍字符串类型的实现 首先了解字符串对象的结构 // redis对象内存分配,列出主要相关的属性 redisOb ...
- Linux关于文件,文件夹操作命令
文件 文件夹 相关操作命令 查看文件 cd 切换目录位置 ls 目录 查看指定目录所有文件 --缺省当前目录 ls -l 目录 查看指定目录所有文件的详细信息 --同 ll 命令 ls -a ...