咱们NetCore下日志存储设计


日志的分类

首先往大的来讲,日志分2种html

①业务日志: 即业务系统须要查看的日志, 常见的好比谁何时修改了什么.git

②参很多天志: 通常是开发人员遇到问题的时候定位用的, 通常不须要再业务系统里展现.github


对于业务日志, 咱们如今基本肯定” 业务日志是业务” 这么个准则, 即业务日志应该跟随着业务表走.数据库

好比你一个订单的操做日志, 那么订单表再哪它就应该在哪, 业务日志应该要跟着你的业务操做同生共死(事务性), 基于上述理念因此业务日志咱们不会用table存后端


对于参很多天志, 我以为这个说是后端开发人员的撕逼生命线绝不为过, 可是同时因为参很多天志其实并不属于业务的一部分(彻底没有这玩意,业务也是能跑的转,业务系统也不会显示这些信息)缓存

因此不少时候除开发人员以外的其它利益相关方其实并不在乎是否有这个参很多天志, 甚至很多入门级开发人员也没法理解其重要性. 并发

并且参很多天志拥有单位价值低, 可是总量却及其庞大的特色, 也由于这个特色致使数据库那边的人(好比DBA)通常也挺抗拒这个的. app

而咱们用Table最主要就是解决参很多天志的问题. 分布式


日志存储体系设计理论

首先一个大原则是咱们但愿业务日志和参很多天志是能串通起来, 好比你进行了这个业务操做而且有了这个业务日志, 那么我要能回溯到执行此次业务操做的相关参数. ide

常规的想法是参很多天志里存一个业务主键

可是订单表的话你存订单号, 用户表的话你存用户Id, 而后再来个别的业务又要存一个别的主键, 其实这挺很差扩展的, 而后参很多天志就会变得乱七八糟, 另外你就算存了订单号你也无法和业务日志能直接的join出来(通常会匹配下2个日志的操做时间人肉来看)


而咱们用Application Insights来做为主力监控, 咱们发现它可以把一个请求/依赖项/异常等信息串联在一个列表里  参见: 统一的跨组件事务诊断

image

咱们就很好奇它是怎么作到的,而后特意扒了一下它的SDK

发如今不一样年代AppInsights经过不一样的机制生产了一个在当前请求操做内的Id,而后用于各个操做之间进行关联,分别是经过:

1.早期的AppInsights里(Net 4.6以前)是经过CallContext

2.Net 4.6之后是经过AsyncLocal

3.如今NetCore年代则经过Activity

而后它Id分3个,一个是Id自身,一个是ParentId,一个是RootId

这个属于分布式追踪的内容,里面包含相对较多知识点这里就不展开太多了,具体能够看Github上微软对于Activity的用户手册里有详细描述 Activity User Guide


AppInsights这个算是给了我较大的启示,因而乎我就在想,若是个人业务日志也存下它的那个Id,而后个人参很多天志也存这个Id

那么我就拥有了一个和业务无关的统一关联Id(而不是存各个业务表的业务主键),同时我甚至能实现相似它的那个“事务诊断”那样的体验,我经过一个业务日志的数据能迅速关联到个人参很多天志的记录 


扯了那么多,具体怎么作

首先对于如何记录参很多天志这件事,比较笨的办法多是以下这样 

image

厉害点的人可能会把这个步骤放到Filter里

可是,拜托,都2021年了,咱们来点稍微主流靠谱点的技术吧。


咱们是使用了abp的,我以为里面的Audit(审计日志)特性就蛮不错,咱们就是经过这个来记录日志。

参考文档 审计日志 

咱们只须要重写一下它的 IAuditingStore(里面只有个SaveAsync方法)

而后在须要的地方打上[Audited]便可

image 


Abp的审计日志本质是基于Castle的动态代理(DynamicProxy)来实现了AOP,而后它能获取到一个方法调用的入参/出参/执行时间/异常信息/方法名等各类信息,咱们只要重写下告诉ABP怎么存就能够了

因此记录日志的时候只须要打一个特性(并且和Filter不一样的是我这个特性能够打在任何基于接口获取的Public的方法里,而不局限于Controller里)


规范业务日志表

为了配套参很多天志,咱们也规范了业务日志表的存储。

业务日志通常会有2种比较常见的存储模式

①新值旧值得存储

②彻底拷贝修改前的记录进行彻底存储


我采用的是模式②,我我的不太喜欢模式①,首先新老值存储会带来存储量(存储行数)暴涨的问题,另外新老值存储我感受很容易遗失一些数据的细节。

我这边的业务日志通常是: 原始业务表的数据Copy + 日志建立时间 + 操做人 + 操做Id + (可选)操做类型(通常是一个枚举)

至于怎么Copy原始业务表,AutoMapper映射下不要太简单

而后结合下上面的理论篇,咱们这里须要获取到一个操做Id用于接下来和参很多天志关联。

在如今Core的年代下直接用Activity便可 

image

在你请求进来的时候它默认就已经构造了,而且能确保当前请求内是惟一,Activity生成的Id也是符合opentelemetry规范的分布式追踪Id

其余一些APM工具(好比我用的AppInsights)如今它内部的追踪Id也都是基于这套来进行运做 


如何存参很多天志 

首先结合以前说到参很多天志的特色,量大,单位价值低

以前咱们没更好的存储介质的时候也是直接存数据库里,而后DBA就常常跟咱们说这个太大了,要按期清理下,而后咱们大概是3周一清 

若是一个问题潜伏3周以上对咱们就是个麻烦事了 

并且也所以致使咱们对存储参很多天志也比较谨慎(稍微量上去了就会叫)


因此咱们认清了以下几个基本事实:

①关系型数据库是属于昂贵存储,它应该存储的是价值高的昂贵数据

②数据必须分层,高价值的和低价值的分开 

在结合下前面咱们提到的基于一个分布式追踪Id的日志设计体系,因此还要提供不低于1个索引能力的查询支持 


后面咱们就用上了Azure Table Storage 

具体Table是什么我以前有一篇文章有简单介绍 Azure Table Storage 简单介绍 


咱们把分布式追踪Id做为PartitionKey,其余abp里能提供的数据通通塞Table里

最后的代码大概是这样 

image


里面折叠的那个FillAudit方法

image


通过上述设计,咱们整个日志如今基本就玩的比较转,一旦有什么问题,咱们先查询业务日志,而后能够经过任意一条业务日志在关联到参很多天志定位到当时是什么参数进来的,由此提高排查问题的速度 


Note:可能有些眼尖的人会发现个人Async的方法没await,通过测试证实Table那边的调用能够FireAndForgot的,并且基本上也不会丢数据,So这不是Bug或者疏忽,是故意的,反而那个Catch多是一句无效代码


多说几句

上面我重写Audit的是每次来一个请求我就往Table存一条记录,若是是面向高并发接口(好比查询类的接口)

上面我所说的这个作法会让你死得很惨 


正确作法应该是:

将数据先在本地内存缓存一段时间后,当达到某个时间阈值或者数据量累计到必定程度再发送 

这个作法背后仍是蛮复杂的,不过当年咱们再琢磨这个东西的时候发觉咱们用的AppInsights的SDK里也有这个玩意,咱们直接拿出来稍微定制了下后发觉还真能用。

有兴趣能够看看appInsights相关的代码 传送门 

你只要想办法重写下它的Send方法,那么它就能为你所用了。

相关文章
相关标签/搜索