一、背景

在实际项目的开发当中,使用 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 包下载地址

Abp.Auditing.MongoDb 包 GitHub 地址

Abp + MongoDb 改造默认的审计日志存储位置的更多相关文章

  1. ABP官方文档翻译 4.6 审计日志

    审计日志 介绍 关于IAuditingStore 配置 通过特性启用/禁用 注意事项 介绍 维基百科:“审计追踪(也称为审计日志)是与安全相关的按时间先后的记录.记录集合.记录的目的地和源,提供一系列 ...

  2. CentOS7更改Docker默认镜像和容器存储位置

    图片出处:https://bobcares.com/wp-content/uploads/docker-change-directory.jpg 一.Why? 通常,当你开始使用docker时,我们并 ...

  3. Python logging模块日志存储位置踩坑

    问题描述 项目过程中写了一个小模块,设计到了日志存储的问题,结果发现了个小问题. 代码结构如下: db.py run.py 其中db.py是操作数据库抽象出来的一个类,run.py是业务逻辑代码.两个 ...

  4. 更改DHCP服务器默认日志存储位置

    DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)是一种有效的IP 地址分配手段,已经广泛地应用于各种局域网管理.它能动态地向网络中每台计算机分配唯一 ...

  5. docker之容器日志存储位置及把运行日志记录至文件

    参考:https://www.cnblogs.com/YatHo/p/7866029.html docker启动后日志会在以下位置 /var/lib/docker/containers/容器ID/容器 ...

  6. Docker Toolbox替换默认docker machine的存储位置

    使用Docker Toolbox是因为它不用打开windows的hyper-v组件,这样可以和VMware workstation一起使用. 关于如何迁移可参考:https://www.cnblogs ...

  7. ABP开发框架前后端开发系列---(7)系统审计日志和登录日志的管理

    我们了解ABP框架内部自动记录审计日志和登录日志的,但是这些信息只是在相关的内部接口里面进行记录,并没有一个管理界面供我们了解,但是其系统数据库记录了这些数据信息,我们可以为它们设计一个查看和导出这些 ...

  8. .Net Core 审计日志实现

    前言: 近日在项目协同开发过程中出现了问题,数据出现了异常:其他人员怀疑项目数据丢失程序存在问题.于是通过排查程序提供的审计日志最终还原了当时操作及原因. 可见审计日志在排查.定位问题是相当有用的,那 ...

  9. ABP(现代ASP.NET样板开发框架)系列之19、ABP应用层——审计日志

    点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之19.ABP应用层——审计日志 ABP是“ASP.NET Boilerplate Project (ASP.NET ...

随机推荐

  1. PNP的学习-P3P

    PNP方法是为了解决在当前两帧图像中,已知前一帧图像上的3dLandmark点和当前帧的2d特征点,求取当前帧的pose. PNP主要有P3P.EPNP.UPNP.DLT.MRE(LS Iterati ...

  2. Scrum冲刺阶段4

    成员今日完成的任务 人员 任务 何承华 学习后端设计 陈宇 后端设计 丁培辉 学习后端设计 温志铭 日程添加界面设计(一半) 杨宇潇 日程添加界面设计(一半) 张主强 服务器构建 成员遇到的问题 人员 ...

  3. git的使用方式总结

    1.先用 git clone url 克隆下来项目 2.查看下载的项目里面有没有一个名字叫git的文件夹 3.用git branch查看当前所有的本地分支,绿色的代表当前所处的分支 4.若本地只有一个 ...

  4. 移动端常见bug

    meta基础知识 H5页面窗口自动调整到设备宽度,并禁止用户缩放页面 <meta name="viewport" content="width=device-wid ...

  5. GitHub上最受欢迎的 5 大 Java 项目

    1. Mockito Mockito 并不是无酒精混合饮料的意思.Mockito 是一个针对 Java 的 mocking 框架.它与 EasyMock 和jMock 很相似,但是通过在执行后校验什么 ...

  6. DOM对象和jQuery对象的转换

    <script type="text/javascript"> //js的页面加载事件 window.onload = function () { //获取DOM对象 ...

  7. 在 npm 中使用 ES6 module

    node 从 v8.5.0起 支持了 ES6 module. 只需保存文件名为 .mjs ,并通过一个option 可以开启执行,如 node --experimental-modules index ...

  8. python xss相关的编码解码小脚本

    1.功能分析: 实际工作中经常会遇到alert()之类的函数被防火墙过滤,而把alert()转化为ascii码放到String.fromCharCode()中就可以绕过,之前会一个一个查ascii表, ...

  9. SVN完全备份,增量备份,库同步

    svn备份一般采用三种方式:1)svnadmin dump 2)svnadmin hotcopy 3)svnsync. 优缺点分析: ============== 第一种svnadmin hotcop ...

  10. React 和 Redux 结合 1

    React依赖: "devDependencies": { "babel-core": "^6.26.0", "babel-loa ...