需求背景

最近在项目上需要增加对用户操作进行审计日志记录的功能,调研了一圈,在.net core生态里,用的最多的是Audit.NET。浏览完这个库的文档后,觉得大致能满足我们的诉求,于是建立一个控制台项目来先玩一玩。

但是我们还有额外的需求:

  • 我们要记录的数据中包含了一些用户的敏感信息,这些内容是肯定不能记到审计日志里面的,所以得想个办法在写日志的时候把这些内容给去掉,这是这篇文章要解决的问题。
  • 我们需要将日志数据发送到AWS的Simple Queue Service里面去,但是官方提供的一些预定义的DataProvider里没有这个功能,需要自己实现,这部分就不在这篇文章里说了,下次再写一篇。

Audit.NET的基本使用方法

安装

新建一个.net core console application,我的源代码在这里:TryCustomAuditNet, 使用Nuget查找Audit.NET安装到项目中即可。

配置

这个库的官方文档已经有比较详细的配置项说明了,在这里我就不复述了,只记录一下基本配置。

static void Main(string[] args)
{
ConfigureAudit();
} private static void ConfigureAudit()
{
Audit.Core.Configuration.Setup()
.UseFileLogProvider(config => config
.DirectoryBuilder(_ => "./")
.FilenameBuilder(auditEvent => $"{auditEvent.EventType}_{DateTime.Now.Ticks}.json"));
}

使用

定义数据对象

首先我们模拟一个需要被审计的数据对象Order,写一个方法用来修改其中一个属性:

public class Order
{
public Guid Id { get; set; }
public string CustomerName { get; set; }
public int TotalAmount { get; set; }
public DateTime OrderTime { get; set; } public Order(Guid id, string customerName, int totalAmount, DateTime orderTime)
{
Id = id;
CustomerName = customerName;
TotalAmount = totalAmount;
OrderTime = orderTime;
} public void UpdateOrderAmount(int newOrderAmount)
{
TotalAmount = newOrderAmount;
}
}

业务逻辑中进行审计

static void Main(string[] args)
{
ConfigureAudit(); var order = new Order(Guid.NewGuid(), "Jone Doe", 100, DateTime.UtcNow); // 追踪order的审计
using (var scope = AuditScope.Create("Order::Update", () => order))
{
order.UpdateOrderAmount(200); // optional
scope.Comment("this is a test for update order.");
}
}

效果

运行程序,在TryCustomAuditNet/bin/Debug/netcoreapp3.1目录下生成了一个审计日志文件Order::Update_637408091235053310.json

内容如下:

$ cat Order::Update_637408091235053310.json
{
"EventType": "Order::Update",
"Environment": {
"UserName": "yu.li1",
"MachineName": "Yus-MacBook-Pro",
"DomainName": "Yus-MacBook-Pro",
"CallingMethodName": "TryCustomAuditNet.Program.Main()",
"AssemblyName": "TryCustomAuditNet, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
"Culture": ""
},
"Target": {
"Type": "Order",
"Old": {
"Id": "411b43b5-24be-4368-8978-2bf334e7ce8e",
"CustomerName": "Jone Doe",
"TotalAmount": 100,
"OrderTime": "2020-11-12T12:18:43.177177Z"
},
"New": {
"Id": "411b43b5-24be-4368-8978-2bf334e7ce8e",
"CustomerName": "Jone Doe",
"TotalAmount": 200,
"OrderTime": "2020-11-12T12:18:43.177177Z"
}
},
"Comments": [
"this is a test for update order."
],
"StartDate": "2020-11-12T12:18:43.212662Z",
"EndDate": "2020-11-12T12:18:43.498007Z",
"Duration": 285
}

可以看到在审计过程中,数据对象的TotalAmount值从100更新为了200,并且新增了一个Comments字段。

问题

我们的问题是,在审计日志中,我们不希望记录CustomerName这个字段的值,因为具体的人名被认为是显式的隐私数据,而这是不能直接记录到审计日志中的,怎么处理?

解决方案

一个比较简单粗暴的方法就是在需要记录审计日志的地方,将原始的数据对象经过映射之后传到AuditScope内部,但是这有几个问题:一是这样一来需要在程序中写大量不同的数据对象映射方法,不利于维护;二是我没有实验这种方式的开销有多大以及到底能不能准确实现我们的需求。所以我们去看看源码,然后整理一下思路。

核心代码

打开Audit.NET的源代码,结合测试程序,我们定位到了几个关键的代码块:

AuditScope.cs

public partial class AuditScope : IAuditScope
{
private readonly AuditScopeOptions _options;
#region Constructors [MethodImpl(MethodImplOptions.NoInlining)]
internal AuditScope(AuditScopeOptions options)
{
_options = options;
_creationPolicy = options.CreationPolicy ?? Configuration.CreationPolicy;
_dataProvider = options.DataProvider ?? Configuration.DataProvider;
_targetGetter = options.TargetGetter; // ... 省略中间代码 if (options.TargetGetter != null)
{
var targetValue = options.TargetGetter.Invoke();
_event.Target = new AuditTarget
{
// IMPORTANT: 调用了AuditDataProvider中的Serialize方法来序列化数据对象
Old = _dataProvider.Serialize(targetValue),
Type = targetValue?.GetType().GetFullTypeName() ?? "Object"
};
}
ProcessExtraFields(options.ExtraFields);
} // ...省略其他代码
}

AuditDataProvider.cs

// IMPORTANT: AuditDataProvider这是一个抽象基类,我们可以通过继承AuditDataProvider实现自己的DataProvider。
public virtual object Serialize<T>(T value)
{
// IMPORTANT:重写这个方法,在重写中实现基于Attribute的数据对象字段过滤。
if (value == null)
{
return null;
}
return JToken.FromObject(value, JsonSerializer.Create(Configuration.JsonSettings));
}

基本思路

基本思路就是我们设法在需要记录的数据对象定义里,给需要或者不需要记录的属性加上自定义的Attribute,并且实现自己的DataProvider类重写Serialize方法,在序列化对象的时候根据这个特定的Attribute来过滤需要序列化的字段。

那么就搞起来。

代码实现

添加自定义Attribue

新建类UnAuditableAttribute,实现代码:

[AttributeUsage(AttributeTargets.Property)]
public class UnAuditableAttribute: Attribute
{
}

为我们不希望被审计的属性添加Attribute:

public class Order
{
public Guid Id { get; set; } [UnAuditable]
public string CustomerName { get; set; } // ...省略其他内容
}

添加自定义DataProvider并重写关键方法

为了简单,我们直接复制一份FileDataProvider类的内容到我们新建的CustomFileDataProvider类中:

public class CustomFileDataProvider: AuditDataProvider
{
public override object Serialize<T>(T value)
{
if (value == null)
{
return null;
} // REGION START: 过滤属性
var jo = new JObject();
var serializer = JsonSerializer.Create(Configuration.JsonSettings); foreach (PropertyInfo propInfo in value.GetType().GetProperties())
{
if (propInfo.CanRead)
{
object propVal = propInfo.GetValue(value, null); var cutomAttribute = propInfo.GetCustomAttribute<UnAuditableAttribute>();
if (cutomAttribute == null)
{
// 被打上UnAuditableAttribute标记的属性,不加入序列化中。
jo.Add(propInfo.Name, JToken.FromObject(propVal, serializer));
}
}
}
// REGION END return JToken.FromObject(jo, serializer);
} public CustomFileDataProvider(Action<IFileLogProviderConfigurator> config)
{
// 为了在我们的测试工程中编译通过,需要自定义一个CustomFileDataProviderConfigurator类,实现照搬FileDataProviderConfigurator,只是将字段改为public的。
var fileConfig = new CustomFileDataProviderConfigurator();
if (config != null)
{
config.Invoke(fileConfig);
_directoryPath = fileConfig._directoryPath;
_directoryPathBuilder = fileConfig._directoryPathBuilder;
_filenameBuilder = fileConfig._filenameBuilder;
_filenamePrefix = fileConfig._filenamePrefix;
JsonSettings = fileConfig._jsonSettings;
}
} // ...省略其他相同的内容
}

修改配置

最后我们修改一下最初的配置,使用我们自定义的DataProvider:

private static void ConfigureAudit()
{
Audit.Core.Configuration.Setup()
.UseCustomProvider(new CustomFileDataProvider(config => config
.DirectoryBuilder(_ => "./")
.FilenameBuilder(auditEvent => $"{auditEvent.EventType}_{DateTime.Now.Ticks}.json")
.JsonSettings(new JsonSerializerSettings
{
Formatting = Formatting.Indented,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
NullValueHandling = NullValueHandling.Include
})));
}

测试

再运行一次程序,我们来看生成的审计文件:

$ cat Order::Update_637408110805063180.json
{
"EventType": "Order::Update",
"Environment": {
"UserName": "yu.li1",
"MachineName": "Yus-MacBook-Pro",
"DomainName": "Yus-MacBook-Pro",
"CallingMethodName": "TryCustomAuditNet.Program.Main()",
"AssemblyName": "TryCustomAuditNet, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
"Culture": ""
},
"Target": {
"Type": "Order",
"Old": {
"Id": "f5c08f91-c0e4-4c25-b5ba-97edfb418346",
"TotalAmount": 100,
"OrderTime": "2020-11-12T12:51:19.081762Z"
},
"New": {
"Id": "f5c08f91-c0e4-4c25-b5ba-97edfb418346",
"TotalAmount": 200,
"OrderTime": "2020-11-12T12:51:19.081762Z"
}
},
"Comments": [
"this is a test for update order."
],
"StartDate": "2020-11-12T12:51:19.215596Z",
"EndDate": "2020-11-12T12:51:20.472729Z",
"Duration": 1257
}

注意到新的审计日志中已经不再包含CustomerName这个属性了,完美。

总结

Audit.NET这个框架还是非常强大的,这篇文章只探讨了其中非常小的一个特性点,当然基于本文的思路,我们还可以实现更复杂的审计日志数据对象过滤逻辑,可扩展性还是很好的。

.NET Core工程应用系列(1) 定制化Audit.NET实现自定义AuditTarget的更多相关文章

  1. .NET Core工程应用系列(2) 实现可配置Attribute的Json序列化方案

    背景 在这篇文章中,我们实现了基于自定义Attribute的审计日志数据对象属性过滤,但是在实际项目的应用中遇到了一点麻烦.需要进行审计的对象属性中会包含其他类对象,而我们之前的实现是没办法处理这种类 ...

  2. ASP.NET Core应用的错误处理[3]:ExceptionHandlerMiddleware中间件如何呈现“定制化错误页面”

    DeveloperExceptionPageMiddleware中间件利用呈现出来的错误页面实现抛出异常和当前请求的详细信息以辅助开发人员更好地进行纠错诊断工作,而ExceptionHandlerMi ...

  3. kettle系列-4.kettle定制化开发工具类

    要说的话这个工具类还是比较简单的,每个方法体都比较小,但用起来还是可以的,把开发中一些常用的步骤封装了下,不用去kettle源码中找相关操作的具体实现了. 算了废话不多了,直接上重点,代码如下: im ...

  4. jquery-ui-datepicker定制化,汉化,因手机布局美观化源码修改

    感谢浏览,欢迎交流=.= 公司微信网页需要使用日历控件,想到jquery-mobile,但是css影响页面布局,放弃后使用jquery-ui-datepicker. 话不多说,进入正题: 1.jque ...

  5. 定制化Azure站点Java运行环境(3)

    定制化Azure Website提供的默认的Tomcat和JDK环境 在我们之前的测试中,如果你访问你的WEB站点URL时不加任何上下文,实际上你看到的web界面是系统自带的测试页面index.jsp ...

  6. 使用beanstalkd实现定制化持续集成过程中pipeline

    持续集成是一种项目管理和流程模型,依赖于团队中各个角色的配合.各个角色的意识和配合不是一朝一夕能练就的,我们的工作只是提供一种方案和能力,这就是持续集成能力的服务化.而在做持续集成能力服务化的过程中, ...

  7. .net core实践系列之短信服务-Api的SDK的实现与测试

    前言 上一篇<.net core实践系列之短信服务-Sikiro.SMS.Api服务的实现>讲解了API的设计与实现,本篇主要讲解编写接口的SDK编写还有API的测试. 或许有些人会认为, ...

  8. U-Mail:如何实现EDM的个性化和定制化?

    设想一下,一个上班族一天要接到多少垃圾邮件?据媒体报道,目前来往的邮件中,高达95%以上的是垃圾邮件,而且有些垃圾邮件还会故意占据着邮箱的最前列.同时,随着人们接受资讯越来越快捷便利,渠道越来越多,也 ...

  9. ElasticSearch 2 (13) - 深入搜索系列之结构化搜索

    ElasticSearch 2 (13) - 深入搜索系列之结构化搜索 摘要 结构化查询指的是查询那些具有内在结构的数据,比如日期.时间.数字都是结构化的.它们都有精确的格式,我们可以对这些数据进行逻 ...

随机推荐

  1. [bzoj1089]严格n元树

    设f[i]表示深度不超过i的方案数,那么有f[0]=1,$f[i]=f[i-1]^{n}+1$,然后用高精度即可(注意深度恰好为d还要用f[d]-f[d-1]才是答案) 1 #include<b ...

  2. Java 插入html字符串到PPT幻灯片

    通过Java后端代码操作PPT幻灯片时,可直接在幻灯片中绘制形状,并在形状中添加文本字符串内容.本篇文章,介绍一种通过html字符串来添加内容到PPT幻灯片的的方法,可添加文字.图片.视频.音频等.下 ...

  3. 【JVM源码解析】模板解释器解释执行Java字节码指令(上)

    本文由HeapDump性能社区首席讲师鸠摩(马智)授权整理发布 第17章-x86-64寄存器 不同的CPU都能够解释的机器语言的体系称为指令集架构(ISA,Instruction Set Archit ...

  4. 低代码开发Paas平台时代来了

    概述 **本人博客网站 **IT小神 www.itxiaoshen.com 低代码理论 概念 低代码开发基于可视化和模型驱动的概念,结合了云原生和多终端体验技术,它可以在大多数业务场景中,帮助企业显著 ...

  5. myeclipse激活、破解教程

    myeclipse安装注意事项 首先要下载jdk 配置jdk的环境变量 如果1和2 都打不开说明没有下载jdk文件,点击下载就可以了,点击1的时候会出现要求下载的界面,直接下载就可以了...下载完成之 ...

  6. Java开发最实用最好用的11个技术网站

    作为一个Java开发者,学习最新技术和关注行业内容是你不断提升自我的有效手段.因此,我会特别关注一些质量高口碑好的Java技术网站,在这里分享给大家. 1.Stackoverflow Stackove ...

  7. 【R】如何将重复行转化为多列(一对一转化一对多)?

    目录 需求 方法一 方法二 需求 一个数据框一列或多列中有重复行,如何将它的重复行转化为多列?即本来两列一对一的关系,如何转化为一对多的关系?普通的spread函数实现较为麻烦. 示例数据如下: It ...

  8. C语言 fastq文件转换为fasta文件

    目前只能处理短序列,若要处理长序列,可按照https://www.cnblogs.com/mmtinfo/p/13036039.html的读取方法. 1 #include <stdio.h> ...

  9. 搭建简单的SpringCloud项目一:注册中心和公共层

    注:笔者在搭建途中其实遇见不少问题,统一放在后面的文章说明,现在的搭建是测试OK的. GitHub:https://github.com/ownzyuan/test-cloud 后续:搭建简单的Spr ...

  10. 记一次 .NET 某化妆品 webapi 卡死分析

    一:背景 1. 讲故事 10月份星球里的一位老朋友找到我,说他们公司的程序在一个网红直播带货下给弄得无响应了,无响应期间有大量的 RabbitMQ 超时,寻求如何找到根源,聊天截图我就不发了. 既然无 ...