Rust 语言的全链路追踪库 tracing

news2025/1/15 20:43:44

在一个应用程序或库的开发过程中,除了其本身的逻辑以外,开发人员还需要做很多额外的工作,以保证编写的代码可以正确的运行,或者在出错时可以快速定位到错误的位置以及原因,这就需要引入一些额外的工具,trace 就是其中特别好用的一种,下文我将会简单介绍 trace,并以 Rust 为例,演示 trace 在 Rust 中的使用方法。

 

可观测性

Logs、Metrics 和 Traces 并称为可观测性三大支柱,通过分析它们输出的数据,开发人员能够更好的观测到系统的运行状况,更快的定位问题,从而提高系统的可靠性。

日志(Logs)

日志作为最常用的可观测性数据源之一,相信多数开发者都比较熟悉。其本质上就是一种带有时间戳的离散事件记录,通常用于记录系统的运行状态,日志的使用十分简单,只需要在代码中需要报告信息的点添加一行代码,就可以将这些信息输出到控制台或文件中,但是日志也有很大的缺点,它的输出是离散的,这意味着在记录的时候,无法将日志信息相互关联,也无法知道日志信息的上下文,尤其是在多线程的环境下,最终输出的信息比较混乱,不便于检索和分析。

指标(Metrics)

指标是一种定量衡量,例如平均值、比率和百分比等。其值始终为数字而非文本,可以通过数学方法统计和分析,其主要用于描述系统运行状态的数据,比如 CPU 的使用率、内存的使用率、磁盘的使用率等,这些数据可以用来监控系统的运行状态,也可以用来预警。

追踪(Traces)

追踪是一种用于记录系统中一次请求的完整生命周期的数据,它可以记录下一个请求从开始到结束的所有信息,包括请求的发起者、接收者、请求的路径、请求的状态、请求的耗时、请求的错误信息等,这些信息可以用来分析系统的性能瓶颈,也可以用来分析系统的错误。追踪本质上也是一种日志,他与日志的数据结构十分相似,但是它能够提供比日志更丰富的信息。特别是在分布式系统中,追踪能够跨越多个服务,汇总出一次请求的完整信息,让开发人员能够更方便的找到系统中的问题。

Rust 中的 Trace

Rust 社区中比较有名的 trace 实现有三个:

  • tracing 由 tokio 团队维护,目前使用最广泛,生态也比较完善
  • rustracing 使用人数相对较少
  • minitrace tikv 团队打造,性能最好

接下来就以 tracing 为例,介绍一下trace 的核心概念以及使用方法

Span

Span 可以说是 trace 中最关键的概念之一,它表示的是一个过程,也就是一段时间内发生的所有事件的集合,其数据结构中包含着 Span 的开始时间和结束时间,在分析数据是可以借助工具直观的看到某次请求或操作的耗时情况。在同一个 trace 流程中的所有 Span 都共享这相同的 Trace Id ,每个 Span 也有着自己的 Span Id,并且 Span 还支持嵌套,嵌套的 Span 中也会保存着相应的父子关系,最终可以靠这些信息,将请求的完整生命周期串联起来,并且不会与相同时间段内的其他请求产生干扰。

use tracing::{span, Level};

fn main() {
    let span = span!(Level::INFO, "span");
    let _enter = span.enter();
    // enter 后进入该 span 的上下文
    // 可以记录信息到 span 中
} // 离开作用域后,_enter 被 drop,对应的 span 在此结束

以上代码是创建并使用一个 Span 最简单的方式,除此以外还有几种不同的方式

#[instrument] // tracing 会为当前函数自动创建 span ,该 span 名与函数相同,并且整个函数都在该 span 的上下文内
fn do_something() {
    // some event
    let span = span!(Level::INFO, "external function");
    span.in_scope(|| some_external_function()); //对于无法添加 #[instrument] 的外部函数,也可以使用 in_scope 方法让其在 span 的上下文中执行
}

#[instrument] // 此方法同样对异步函数适用
async fn do_something_async() {
    let future = async {
        // some async code
    };
    let span = span!(Level::INFO, "future");
    future.instrument(span).await; // 也可以在 future .await 之前将 span 附加给 future
}

// async 代码中要避免以下情况
async fn some_async_code() {
    let span = span!(Level::INFO, "span");
    let _enter = span.enter();
    // 此处进入 span 的上下文,直到 _enter 被 drop 后才会结束
    async_fn().await; // .await 时,task 可能会让出当前线程的执行权,而此时 _enter 还没有 drop,因此可能会错误的记录到其他 task 的 enent.
}


Event

Event 与日志类似,表示的是某一个时间点发生的事件,但与日志不同的是,Event 可以将信息记录到 Span 的上下文中,这样在分析数据时,可以直接查看 Span 中发生的所有事件。

use tracing::{event, info, span, Level};

fn main() {
    event!(Level::INFO, "event"); // 在 span 的上下文之外记录一个 Leval 为 INFO 的 event

    let span = span!(Level::INFO, "span");
    let _enter = span.enter();

    event!(Level::INFO, "event"); // 在 span 的上下文内记录 event

    info!("something with info level"); // 也可以使用和 log 相同的形式记录 event
}

Collector

以上的示例不会有任何可见的输出,因为我们还没有配置 Collector,tracing 中所有的 Span 和 Event 都是通过 Collector 来收集的,Collector 会将 Span 和 Event 以一定的格式输出到指定的地方,比如 stdout、stderr、文件、网络等。tracing-subscriber 的 fmt 模块提供了一个 Collector ,可以方便的输出事件信息。

use tracing::info;
use tracing_subscriber;

fn main() {
    // 初始化全局 Collector
    tracing_subscriber::fmt::init();

    info!("Hello, world!");
}

运行上面这段代码,可以在终端中看到一条 INFO 级别的事件,如果需要将 Trace 信息发送到其他地方,就要用到其他的 Collector 实现,比如 tracing-appender 这个 crate,可以将 Trace 信息输出到文件中。

在 Rust 中使用

tracing 的完整示例

use std::{thread::sleep, time::Duration};

use tracing::{debug, info, info_span, instrument};

#[instrument]
fn expensive_work(secs: u64) {
    debug!("doing expensive work");
    sleep(Duration::from_secs(secs));
    debug!("done with expensive work");
}

fn main() {
    tracing_subscriber::fmt()
        // enable everything
        .with_max_level(tracing::Level::TRACE)
        // sets this to be the default, global collector for this application.
        .init();
    let span = info_span!("root");
    let _enter = span.enter();

    info!("some info in the root span");

    expensive_work(1);
}

运行以上代码将会的到以下输出

2022-12-01T02:50:59.425475Z  INFO root: tracing_example: some info in the root span
2022-12-01T02:50:59.425518Z DEBUG root:expensive_work{secs=1}: tracing_example: doing expensive work
2022-12-01T02:51:00.425722Z DEBUG root:expensive_work{secs=1}: tracing_example: done with expensive work

每个事件都已相同的格式输出,此输出模式下,与 log 的输出十分相似,

但 tracing 输出的内容多出了 Span 相关的信息。由 instrument 生成的 Span 还自动添加了函数的参数信息。下面介绍的 OpenTelemetry 和 Jaeger,还可以让我们更加直观的查看 Span 之间的时间关系。

Trace 的标准化

想要让 Trace 跨越多个服务,集成到多种不同的语言,那就必须要规定大家相互调用的规范,要遵守一套相同的协议,才能让 Trace 的数据在不同的系统中都能够正常传递,Trace 早期诞生了两种规范,分别是 OpenTracing 和 OpenCensus,后来为了规范的统一,OpenTracing 和 OpenCensus 合并成了 OpenTelemetry,现在已经成为了 Trace 的事实标准。OpenTelemetry 提供了不同语言的 SDK,可以方便的集成到不同的系统中,对于 Rust ,它提供了一系列相关的 crate 用于集成。tracing 也提供了 tracing-OpenTelemetry 用来将其收集到的信息发送到兼容 OpenTelemetry 的分布式追踪系统中。

Trace 数据的可视化分析

Jaeger 是受到 Dapper 和 OpenZipkin 启发的开源分布式跟踪系统,由 Uber 开发,现已捐赠给 CNCF。Jaeger 通过收集 Trace 数据,将其可视化展示,方便开发者分析系统的问题。下图为 Jaeger 部署的示例。

要将 Trace 数据发送给 Jaeger,需要在我们的应用中添加 jaeger-client 。OpenTelemetry 提供的 crate 中,就包括了响应的 jaeger-clinet 实现: opentelemetry-jaeger。它会将 Span 信息以 UDP 包的形式发送到 jaeger-agent,jaeger-agent 将一段时间内的数据打包分批发送到 jaeger-collector,再由 jaeger-collector 把数据存入数据库内,我们在 jaeger 的 UI 中就可以查询到这些数据。

OpenTelemetry 的仓库中也提供了以上流程的示例,我们可以直接运行这个示例,然后在 jaeger 的前端我们就可以得到下图的内容:

有了这些数据,开发人员就能够快速定位到请求的主要耗时部分,也能够通过其中包含的事件获取到请求内的消息记录。

总结

对于大多数同步程序,用 Log 就能够满足需求,并且使用起来也足够简单,但是一旦涉及到异步程序或其他的一些复杂情况,Log 就会变得不那么好用了,一段时间内的 Log 信息可能来自于多个不同的处理流程,难以快速方便的获取我们需要的信息,而 Trace 则能够很好的解决这个问题。

推荐阅读

DatenLord|Rust程序性能分析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/109985.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Verilog刷题HDLBits——Exams/ece241 2014 q5a

Verilog刷题HDLBits——Exams/ece241 2014 q5a题目描述代码结果题目描述 You are to design a one-input one-output serial 2’s complementer Moore state machine. The input (x) is a series of bits (one per clock cycle) beginning with the least-significant bit of …

HistoSeg:具有多损失函数的快速注意,用于数字组织学图像中的多结构分割

摘要 大多数分割网络都是基于编解码的网络结构,也利用了复杂的注意力模块或者Trasnsformer模块。但是这些网络在捕获相关的局部和全局特征时还不够准确。无法在多个尺度上进行准确的边界检测。因此,我们提出了一个编码器-解码器网络,快速注意…

【C++11】可变参数和lambda表达式

目录 1.可变参数模板 1.1可变参数的模板 1.2参数包的展开方式 1.21递归函数展开参数包 1.3逗号表达式展开参数包 2.STL库中的emplace相关接口 3.lambda表达式 3.1lambda的引入 3.2lambda的介绍 列表使用 lambda 表达式捕获 lambda实现swap函数 lambda表达式之间…

开放路径最短优先协议OSPF(计算机网络)

​ 目录 开放最短路径优先(Open Shortest Path First) 链路状态算法 链路状态算法 链路状态数据库(link-state database) OSPF的分组类型 类型1:问候(Hello)分组 类型2:数据库描述(Database Description)分组 类型3:链路状态请求(Li…

Adobe 2023全家桶12月版本更新

Adobe 2023全家桶12月版本更新 Adobe 2023 发布有两个多月了,您们用上了新版本吗?12月又迎来了一次小版本更新,主要更新还是对已知问题的修复,当然也少不了一些新功能更新。 最新的Adobe2023全家桶,有更强大的内容&am…

SAP ABAP——SAP简介(二)【SAP主要产品时间线】

💂作者简介: THUNDER王,一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学会计学专业大二本科在读,同时任汉硕云(广东)科技有限公司ABAP开发顾问。在学习工作中,我通常使用偏后…

知识图谱库汇总!——教育领域能够直接应用的知识图谱

教育领域开源的知识图谱实体 在教育领域,有许多开源的知识图谱实体可供使用。下面列出了一些例子: DBpedia:这是一个知识图谱,由 Wikipedia 的内容构建而成。DBpedia 中包含了许多关于人、地方、事物和概念的实体,并且这些实体都具有相关的属性和关系。 Wikidata:这是一个…

智牛股_第9章_CEPH_Swift+文件上传与下载

智牛股_第9章_CEPH_Swift文件上传与下载 文章目录智牛股_第9章_CEPH_Swift文件上传与下载学习目标第1章 CEPH Swift Api实践1. 目标2. 步骤3. 实现3.1 Ceph Swift Api 实践说明3.2 Ceph Swift Api 特点3.3 Ceph RGW 介绍3.4 Ceph 存储结构3.5 Ceph Swift Api 服务端的配置3.6 C…

计算机网络~物理层

一、物理层基本概念 1. 物理层接口特性 物理层解决如何在连接各种计算机的传输媒体上传输数据比特流,而不是指具体的传输媒体物理层主要任务:确定与传输媒体接口有关的一些特性(定义标准) 机械特性:定义屋里连接的特性,规定物理…

多播网络(Multicast)应用权限

本文介绍如何在苹果开发者官网申请 多播网络(Multicast)应用权限,从而正常使用 Wi-Fi 快连配网功能。本文适用于 iOS 版本的 OEM App 或者其他有相同需求的 App。 背景信息 Wi-Fi 快连配网又称 快连模式(Easy-Connect&#xff0…

docker https 证书/多域名通配符自动续期(群晖https证书)

本文基于 freessl.cn 申请通配符域名自动续期。 使用docker的原因是为了方便可靠,不会因为不同的操作系统缺包无法安装 acme.sh,也不会在操作系统中留下灿烂内容,acme 版的docker 包含了运行环境。 主要步骤如下: 1、打开 http…

圣诞节学算法---线段树

线段树 快到圣诞节了,圣诞树是不是很漂亮?今天我们就来学习一下它的近亲的线段树 (话说这两玩意好像除了读音相似没啥关系) 引入 例题 1 给定一个数组 aaa 求数组中下标为l−rl - rl−r元素的和 看到这题大家都很容易想到用前缀和以O(n)O(n)O(n)预处…

3.2 多级放大电路的动态分析

一个 NNN 级放大电路的交流等效电路可用图3.2.1所示方框图表示。由图可知,放大电路中前级的输出电压就是后级的输入电压,即 U˙o1U˙i2\dot U_{o1}\dot U_{i2}U˙o1​U˙i2​、U˙o2U˙i3\dot U_{o2}\dot U_{i3}U˙o2​U˙i3​、⋯\cdots⋯、U˙o(N−1)U…

react笔记_07 hooks

什么是hook? 以前我们称函数组件为简单组件,因为函数组件是无状态的(没有state)。 而在React 16.8版本增加了 Hook,它可以让你在不编写 class 组件的情况下,也就是我们可以在函数组件中使用 state 以及其他的 React 特性。 Hook 不能在 c…

5G无线技术基础自学系列 | 5G服务完整性KPI

素材来源:《5G无线网络规划与优化》 一边学习一边整理内容,并与大家分享,侵权即删,谢谢支持! 附上汇总贴:5G无线技术基础自学系列 | 汇总_COCOgsta的博客-CSDN博客 5G服务完整性KPI用来评估5G RAN中终端用…

java: 无效的目标发行版: 17 新建springBoot项目

问题 java: 无效的目标发行版: 17 详细问题 新建springBoot项目,对数据库配置后启动项目,控制台报错 java: 无效的目标发行版: 17 如下图 解决方案 查看JDK版本 (事实上, 该步骤查看本机的已配置环境变量的JDK版本&#xff…

Python基础(十)模块与包

目录 1. 简介 1.1 模块 1.2 包 2. 使用 2.1 创建 2.2 引用 1. 简介 1.1 模块 Python 中一个以 .py 结尾的文件就是一个模块,模块中定义了变量、函数等来实现一些类似的功能。Python 有很多自带的模块(标准库)和第三方模块&#xff0c…

UMC产品UI升级说明

随着产品功能的逐渐完善,一款好的产品需要不断地打磨才能变得更完整、更稳定。所以,UMC作为数通畅联的核心产品,为了满足更多的需求,更好的视觉效果和体验感,一直都在不断地完善迭代。 本次升级主要是针对整体页面进行…

实现股票交易c接口​​​​​​​需要的注意事项有哪些?

实现股票交易c接口需要的注意事项有哪些?最近有很多朋友问小编这个问题,小编今天就说说! 在基类列表中包含接口名称 为每一个接口的成员提供实现 如果类从基类继承并实现了接口,基类列表中的基类名称必须放在所有接口之前。(一个…

PMAC的PVT功能实现解析笔记

从上图中我们可以得到如下信息: 速度截面是一个抛物线 P0P_0P0​、V0V_0V0​是上一次指定的,P1P_1P1​、V1V_1V1​是当前期望的,TA是当前期望的运动时间 A0A_0A0​是上一次计算的,A1A_1A1​是当前计算的,加加速度dA/…