在做日志库设计的时候,我会特别考虑日志里面需要带上时间和 TracerId 和 SessionId 两个属性,本文告诉大家带上这两个属性的意义和优势

在开始之前,先需要了解为什么需要写日志。其实无论日志内容是什么,都会比没有写日志更好。但是比没有设计的日志,有经过设计的日志的价值更高,详细请看 程序猿修养 日志应该如何写

本文来聊聊一个更细的话题,日志里面的 TracerId 和 SessionId 两个属性的含义和作用,以及添加这两个属性的优势

首先假定咱这个大的应用是一个战斗集团,不是由单个进程组成的,他将包括了 WPF 客户端,以及 .NET 客户端服务,还有后台 ASP.NET Core 的服务集群等。当然如果我只是单个进程,那么此时使用 TracerId 和 SessionId 两个属性的作用不会很大

那么在一个由很多应用组成的战斗集团会遇到什么问题?最大的问题就是定责。也就是说我服务器端没有给我客户端预期的内容,究竟是我客户端发错信息了,还是复杂的服务器端凉凉了

日志里面如果有记录 TracerId 和 SessionId 两个属性,就可以比较方便定位链路,也就是调用顺序是从哪到哪的过程

什么是 TracerId 和 SessionId 属性?这是两个不同的属性,咱先聊聊 TracerId 属性

其实在我团队内,这个 TracerId 也叫 TracerTag 哈,或者叫 TracerTagList 应该会包含一整组的值

在我团队内的所有的组件库,都会引用一个叫 Core 的组件库。这个库有一个功能就是提供 Trace 追踪的功能,因为让所有的组件库记日志是不靠谱的,如我 A 应用的日志是记到 文件 去的,而 B 应用是需要过滤某些信息上报到日志服务上的。因此让所有组件库了解日志细节是不靠谱的,此时解决方法就是让所有组件库都走 Trace 追踪机制

说到 TracerId 就必须聊下追踪这个机制,什么是追踪机制?其实就是组件库以及非业务逻辑运行过程中,对外抛出的事件。这里说的 事件 不是 C# 里面的 event 哈,可以理解为通知。而此时上层应用,可以根据自己的需求,从 Trace 追踪里面过滤出自己需要的信息。如过滤出所有 Warning 的信息,记录日志。或者调试的时候将所有的信息输出在控制台等

使用追踪的优势在于所有的组件库都不会耦合日志业务,所有的组件库在各个应用里面都是通用的,因为组件库都是依赖追踪,而不依赖具体的日志定义

那么业务逻辑呢?刚才聊的都是组件库,其实业务也相同。例如我在 WPF 客户端里面,默认业务都是将日志记录到本地,但是有时候发现某个信息产品大佬想要知道,如用户点了 A 按钮,此时产品大佬想要知道有多少次点击等,于是就可以让上报埋点模块从追踪里面过滤出 A 按钮点击的事件,用来上报。这样做能大大减少业务埋点和业务模块的耦合

同时业务逻辑使用追踪还能做到在后续产品大佬加需求的时候,想要了解用户是通过哪个渠道点击了 A 按钮的时候,可以做到灵活配置

那么 TracerId 可以如何写?由业务发起方写入,基本上都是由 Guid 组成的字符串。然后传入 Trace 机制,传入到底层模块或其他业务模块,或传入到调用其他进程包括后台服务,其他模块在记录日志的时候,会额外添加被传入的 TracerId 的值

此时能做到的就是了解这个数据或调用是从哪里发起的,经过哪些步骤。例如我的 WPF 客户端,问了我的 OTA 自动更新服务说有没有更新,这是由用户点击客户端界面的更新按钮发起的。于是在用户点击按钮的逻辑里面,就生成了 Guid 作为 TracerId 记录日志。接着是调用客户端的更新模块,这个模块将会接收到上层传入的 TracerId 属性,在更新模块记录日志的时候都会加上 TracerId 属性。接着更新模块通过 IPC 进程间调用,调用了一个 .NET 服务,这个服务负责做软件自动更新,此时更新模块在调用本机服务的时候,将会传入 TracerId 属性。而在 .NET 服务里面,需要访问 ASP.NET Core 更新后台,在访问的时候将会传入给后台这个 TracerId 属性

这样做有什么好处?假如用户发起的点击按钮问有没有更新版本,此时界面显示没有更新版本。但是明明我已经发布了更新版本,请问此时问题出在哪?有 TracerId 就好定位了

先是看更新模块有没有调用本机服务,通过 TracerId 能确定更新模块的某次调用本机服务确实是由用户端发起的,而不是软件的定时更新模块发起的。然后在本机更新服务日志里面,查看有没有这个 TracerId 相关记录。如果没有相关记录,尽管本机更新服务有收到我这个客户端软件的查询更新的信息,但如果 TracerId 没对上,那么就可以证明 IPC 等模块存在锅

当然,如果用户端这里的所有日志都对上了,而看到本机更新服务从后台拿到的数据里面说没有更新可用,那么此时就可以通过 TracerId 去找 ASP.NET Core 后台的日志

例如在后台里面确实收到了信息,但是发现此时出发了限制CDN流量,通过 TracerId 可以了解到,这个 TracerId 就是被限制 CDN 流量的。这样就能了解问题所在

如果没有 TracerId 呢?那么在本机更新服务里面,写入了很多次请求客户端更新的数据,那么是否能证明这是用户点击的这一次发起的?如果在后台 CDN 限制下载流量服务里面说有某次请求更新,因为需要限制 CDN 流量而禁止更新,那么是否可以证明本次客户端查询更新失败的原因就是这个?其实都不能除非是用户量非常小,使用时间的方式定位

而 TracerId 大部分时候也会和 TracerTag 或 TracerTagList 配合,这是用来做什么的?其实这是用来找到调用树的,通过调用树可以了解调用的层次

依然使用刚才用户点击更新作为例子,在我当前的应用里面的 TracerTagList 大概内容如下

  • 【TracerId】【更新界面】点击查询更新按钮
    • 【TracerId】【更新界面】【更新模块】开始查询更新
    • 【TracerId】【更新界面】【更新模块】【版本管理模块】读取软件版本号
    • 【TracerId】【更新界面】【更新模块】【文件管理模块】读取软件安装路径
    • 【TracerId】【更新界面】【更新模块】【IPC通讯模块】查找OTA服务通讯方式
      • 【TracerId】【更新界面】【更新模块】【IPC通讯模块】【发送模块】发送查询更新
  • 【TracerId】【IPC通讯模块】【接收模块】收到消息
    • 【TracerId】【IPC通讯模块】【队列模块】排队消息
  • 【TracerId】【IPC通讯模块】【消息解析模块】解析消息
    • 【TracerId】【IPC通讯模块】【消息解析模块】【更新模块】通知更新信息
      • 【TracerId】【IPC通讯模块】【消息解析模块】【更新模块】【更新界面】更新界面

加上 TracerTagList 的优势仅仅是让每条日志之间可以相对独立,比较适合在本机记录里面。通过时间线,可以只使用 TracerId 串起来,但是有 TracerTagList 能做到更方便看日志。因此 TracerTagList 是非必须的,只是有维护就更好

那么 SessionId 又是做什么的?其实 TracerId 仅仅能做到的是一条链路,而有些问题不是一条链路的问题,因此仅通过 TracerId 是做不到定位这部分问题的

在我团队这里,其实 SessionId 和 UserId 是不同的属性,因为我是做客户端开发的,客户端是不一定有登录,而且一个账号不一定只登录一个端。因此这里的 SessionId 在客户端发起的时候,更多的是一个进程。一个进程一个 SessionId 的值

如果我没有和其他进程和服务进行通讯,那么 SessionId 的作用不大,更好的方法是一个进程一个日志文件。只有在将日志上报到后台,以及和其他进程进行通讯的时候,才能用上 SessionId 的功能

依然使用上面的软件更新作为例子。假定我的业务设计是如果软件正在下载资源,此时不允许软件进行更新。因为如果在下载资源过程更新软件了,那么这个状态很难维护。当然,这是假设哈

此时如果软件更新查询是没有更新,因为软件边界也没做好,那么此时仅从后台能否找到原因是刚好此时正在下载资源的原因,确定是这个原因呢

其实也可以的,通过 SessionId 可以了解到这个进程之前做了哪些事情,当然也就包含了下载资源。而此时通过链路最终可以看到用户是通过什么途径下载资源的,例如用户其实是无意间触发的,此时就需要交互大佬协助了。如果是用户明确点击了下载资源的,那么是不是客户端部分没有处理好逻辑,或者写更新界面的小伙伴忘了这个业务


本文会经常更新,请阅读原文: https://dotnet-campus.github.io//post/dotnet-%E6%97%A5%E5%BF%97%E4%B8%8A%E6%8A%A5%E7%9A%84-TracerId-%E5%92%8C-SessionId-%E7%9A%84%E6%84%8F%E4%B9%89.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 lindexi (包含链接: https://dotnet-campus.github.io/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系