关于rust:Rust-语言的全链路追踪库-tracing

39次阅读

共计 4714 个字符,预计需要花费 12 分钟才能阅读完成。

Rust 语言的全链路追踪库 tracing

在一个应用程序或库的开发过程中,除了其自身的逻辑以外,开发人员还须要做很多额定的工作,以保障编写的代码能够正确的运行,或者在出错时能够疾速定位到谬误的地位以及起因,这就须要引入一些额定的工具,trace 就是其中特地好用的一种,下文我将会简略介绍 trace,并以 Rust 为例,演示 traceRust 中的应用办法。

可观测性

LogsMetricsTraces 并称为可观测性三大支柱,通过剖析它们输入的数据,开发人员可能更好的观测到零碎的运行状况,更快的定位问题,从而进步零碎的可靠性。

日志 (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

以上的示例不会有任何可见的输入,因为咱们还没有配置 Collectortracing 中所有的 SpanEvent 都是通过 Collector 来收集的,Collector 会将 SpanEvent 以肯定的格局输入到指定的中央,比方 stdoutstderr、文件、网络等。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 还主动增加了函数的参数信息。上面介绍的 OpenTelemetryJaeger,还能够让咱们更加直观的查看 Span 之间的工夫关系。

Trace 的标准化

想要让 Trace 逾越多个服务,集成到多种不同的语言,那就必须要规定大家互相调用的标准,要恪守一套雷同的协定,能力让 Trace 的数据在不同的零碎中都可能失常传递,Trace 晚期诞生了两种标准,别离是 OpenTracingOpenCensus,起初为了标准的对立,OpenTracingOpenCensus 合并成了 OpenTelemetry,当初曾经成为了 Trace 的事实标准。OpenTelemetry 提供了不同语言的 SDK,能够不便的集成到不同的零碎中,对于 Rust,它提供了一系列相干的 crate 用于集成。tracing 也提供了 tracing-OpenTelemetry 用来将其收集到的信息发送到兼容 OpenTelemetry 的分布式追踪零碎中。

Trace 数据的可视化剖析

Jaeger 是受到 DapperOpenZipkin 启发的开源分布式跟踪零碎,由 Uber 开发,现已捐献给 CNCF。Jaeger 通过收集 Trace 数据,将其可视化展现,不便开发者剖析零碎的问题。下图为 Jaeger 部署的示例。

要将 Trace 数据发送给 Jaeger,须要在咱们的利用中增加 jaeger-clientOpenTelemetry 提供的 crate 中,就包含了响应的 jaeger-clinet 实现: opentelemetry-jaeger。它会将 Span 信息以 UDP 包的模式发送到 jaeger-agentjaeger-agent 将一段时间内的数据打包分批发送到 jaeger-collector,再由 jaeger-collector 把数据存入数据库内,咱们在 jaeger 的 UI 中就能够查问到这些数据。

OpenTelemetry 的仓库中也提供了以上流程的示例,咱们能够间接运行这个示例,而后在 jaeger 的前端咱们就能够失去下图的内容:

有了这些数据,开发人员就可能疾速定位到申请的次要耗时局部,也可能通过其中蕴含的事件获取到申请内的音讯记录。

总结

对于大多数同步程序,用 Log 就可能满足需要,并且应用起来也足够简略,然而一旦波及到异步程序或其余的一些简单产隔膜,Log 就会变得不那么好用了,一段时间内的 Log 信息可能来自于多个不同的解决流程,难以疾速不便的获取咱们须要的信息,而 Trace 则可能很好的解决这个问题。

正文完
 0