日志的分类

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

①业务日志: 即业务系统需要查看的日志, 常见的比如谁什么时候修改了什么.

②参数日志: 一般是开发人员遇到问题的时候定位用的, 一般不需要再业务系统里展示.

对于业务日志, 我们现在基本确定” 业务日志是业务” 这么个准则, 即业务日志应该跟随着业务表走.

比如你一个订单的操作日志, 那么订单表再哪它就应该在哪, 业务日志应该要跟着你的业务操作同生共死(事务性), 基于上述理念所以业务日志我们不会用table存

对于参数日志, 我觉得这个说是后端开发人员的撕逼生命线毫不为过, 但是同时由于参数日志其实并不属于业务的一部分(完全没有这玩意,业务也是能跑的转,业务系统也不会显示这些信息)

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

而且参数日志拥有单位价值低, 但是总量却及其庞大的特点, 也因为这个特点导致数据库那边的人(比如DBA)一般也挺抗拒这个的.

而我们用Table最主要就是解决参数日志的问题.

日志存储体系设计理论

首先一个大原则是我们希望业务日志和参数日志是能串通起来, 比如你进行了这个业务操作并且有了这个业务日志, 那么我要能回溯到执行这次业务操作的相关参数.

常规的想法是参数日志里存一个业务主键

但是订单表的话你存订单号, 用户表的话你存用户Id, 然后再来个别的业务又要存一个别的主键, 其实这挺不好扩展的, 然后参数日志就会变得乱七八糟, 另外你就算存了订单号你也没法和业务日志能直接的join出来(一般会匹配下2个日志的操作时间人肉来看)

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

我们就很好奇它是怎么做到的,然后特地扒了一下它的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(而不是存各个业务表的业务主键),同时我甚至能实现类似它的那个“事务诊断”那样的体验,我通过一个业务日志的数据能迅速关联到我的参数日志的记录

扯了那么多,具体怎么做

首先对于如何记录参数日志这件事,比较笨的办法可能是如下这样

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

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

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

参考文档 审计日志

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

然后在需要的地方打上[Audited]即可

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

所以记录日志的时候只需要打一个特性(而且和Filter不同的是我这个特性可以打在任何基于接口获取的Public的方法里,而不局限于Controller里)

规范业务日志表

为了配套参数日志,我们也规范了业务日志表的存储。

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

①新值旧值得存储

②完全拷贝修改前的记录进行完全存储


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

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

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

然后结合下上面的理论篇,我们这里需要获取到一个操作Id用于接下来和参数日志关联。

在现在Core的年代下直接用Activity即可

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

其他一些APM工具(比如我用的AppInsights)现在它内部的追踪Id也都是基于这套来进行运作

如何存参数日志

首先结合之前说到参数日志的特点,量大,单位价值低

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

如果一个问题潜伏3周以上对我们就是个麻烦事了

而且也因此导致我们对存储参数日志也比较谨慎(稍微量上去了就会叫)

所以我们认清了如下几个基本事实:

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

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

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

后面我们就用上了Azure Table Storage

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

我们把分布式追踪Id作为PartitionKey,其他abp里能提供的数据统统塞Table里

最后的代码大概是这样

里面折叠的那个FillAudit方法

经过上述设计,我们整个日志现在基本就玩的比较转,一旦有什么问题,我们先查询业务日志,然后可以通过任意一条业务日志在关联到参数日志定位到当时是什么参数进来的,由此提升排查问题的速度

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

多说几句

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

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

正确做法应该是:

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

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

有兴趣可以看看appInsights相关的代码 传送门

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

我们NetCore下日志存储设计的更多相关文章

  1. Es+kafka搭建日志存储查询系统(设计)

    现在使用的比较常用的日志分析系统有Splunk和Elk,Splunk功能齐全,处理能力强,但是是商用项目,而且收费高.Elk则是Splunk项目的一个开源实现,Elk是ElasticSearch(Es ...

  2. .netcore下的微服务、容器、运维、自动化发布

    原文:.netcore下的微服务.容器.运维.自动化发布 微服务 1.1     基本概念 1.1.1       什么是微服务? 微服务架构是SOA思想某一种具体实现.是一种将单应用程序作为一套小型 ...

  3. C#日志记录设计与实现(BenXHLog)

    C#日志记录设计与实现 日志记录: 日志记录在程序设计开发过程中,是非常重要的,可以供调试和记录数据,虽然说有开源的强大日志管理系统,比如apache的Log4Net,功能可谓强悍,但是有时候,不需要 ...

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

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

  5. 【虚拟化实战】存储设计之六latency

    在[虚拟化实战]存储设计之五IOPS中我们讲了评估存储性能的三个关键指标.也就是Throughput,IOPs和latency.以及三者之间的关系.本文深入介绍Latency过高的原因和一些建议. L ...

  6. Abp + MongoDb 改造默认的审计日志存储位置

    一.背景 在实际项目的开发当中,使用 Abp Zero 自带的审计日志功能写入效率比较低.其次审计日志数据量中后期十分庞大,不适合与业务数据存放在一起.所以我们可以重新实现 Abp 的 IAuditi ...

  7. .NET下日志系统的搭建——log4net+kafka+elk

    .NET下日志系统的搭建--log4net+kafka+elk 前言     我们公司的程序日志之前都是采用log4net记录文件日志的方式(有关log4net的简单使用可以看我另一篇博客),但是随着 ...

  8. .NetCore 下开发独立的(RPL)含有界面的组件包 (四)授权过滤

    .NetCore 下开发独立的(RPL)含有界面的组件包 (一)准备工作 .NetCore 下开发独立的(RPL)含有界面的组件包 (二)扩展中间件及服 务 .NetCore 下开发独立的(RPL)含 ...

  9. SQL Server 2008下日志清理方法 2

    SQL Server 2008下日志清理方法 (2011-07-14 10:30:45) 转自 http://blog.sina.com.cn/s/blog_4bdd3d0b0100wfvq.html ...

随机推荐

  1. 使用IDEA搭建SpringBoot进行增删改查

    功能环境:java1.8以上  .IntellJIDEA  First: 创建项目,请根据项目图一步一步完成建立. 二.配置数据库 三.创建实体对象建表或对应存在表,根据需要加入相应注解 四.创建应用 ...

  2. shell,计算指定行的和,计算指定列的和

    有一个文本文件,里面某行某列为数字,那么如何用shell计算指定行(列)的和,方法如下 计算指定行的和: awk 'NR==3{for(i=1;i<=NF;i++)sum=sum+$i;}END ...

  3. Unity 自动烘培Cpu占用过高电脑卡死

    起因 打开Unity项目   发现右下方一直  

  4. mybatis默认返回类型

    在mybatis中,无论你指定还是不指定返回类型,mybatis都会默认的先将查询回的值放入一个hashMap中(如果返回的值不止一条就是一个包含hashMap的list).这其中的区别在于,如果你指 ...

  5. [日常摸鱼]bzoj1968 [Ahoi2005]COMMON 约数研究

    题意:记$f(n)$为$n$的约数个数,求$\sum_{i=1}^n f(i)$,$n \leq 10^6$. 我也不知道为什么我要来做这个- 直接枚举每个数会是哪些数的约数-复杂度$O(n log ...

  6. #2020征文-手机#深鸿会深大小组:HarmonyOS手机游戏—数字华容道

    目录: 前言 概述 正文 创建项目 实现初始界面布局 实现数字的随机打乱 实现滑动或点击调换数字 实现游戏成功界面 结语 源码包 前言 12月16号HarmonyOS2.0手机开发者Beta版已经发布 ...

  7. 【Azure Service Bus】 Service Bus如何确保消息发送成功,发送端是否有Ack机制 

    问题描述 Service Bus如何确保消息发送成功,发送端是否有Ack机制(是否有回调API告诉发送端,服务端已经收到消息)?根据对.NET发送Service Bus消息代码的分析,发送方法queu ...

  8. 2020年Spring Cloud最后一个大版本发布!

    2020年12月22日,Spring Cloud 2020.0 正式发布GA版本! 版本说明 每次Spring Cloud的大版本发布,我们都要先弄清楚,它对应的Spring Boot版本是哪个! 该 ...

  9. Centos8自动挂载U盘移动硬盘解决办法

    Centos默认是不能识别NTFS文件系统的U盘.移动硬盘的.查阅了很多资料讲到的都是需要安装ntfs-3g安装包. 安装完后每次插入移动存储介质时,都需要手动去挂载. 作为一个做技术的,如果不能解决 ...

  10. C# 托管与非托管类型 堆和栈 值类型与引用类型 装箱与拆箱

    一.托管类型与非托管类型 1.托管类型 托管类型包括 引用类型 以及 包含有引用类型或托管类型成员的结构. 引用类型 含引用类型或托管类型成员(字段.自动实现 get 访问器的属性)的结构 // 托管 ...