当前位置: 首页 > news >正文

Rustt 异步调试:tracing 与 tokio-console 的可观测性

目 录

  • 📝 文章摘要
  • 一、背景介绍
  • 二、原理详解
    • 2.1 `tracing`:Span 与 Event
    • 2.2 `tracing-subscriber`:收集数据
    • 2.3 `tokio-console`:`tokio` 运行时诊断
  • 三、代码实战
    • 3.1 实战:一个有锁竞争的 `async` 应用
        • 3.2 运行与分析
  • 四、结果分析
    • 4.1 JSON 输出 (标准 Tracing)
  • 五、总结与讨论
    • 5.1 核心要点
    • 5.2 讨论问题
  • 参考链接

📝 文章摘要

async Rust 提供了极高的并发性能,但也带来了“黑盒”问题。当 tokio 任务(Task)卡住、Future 执行缓慢或Mutex锁竞争激烈时,传统的调试器(GDB)和perf(第四篇已介绍)几乎无能为力。本文将深入探讨 Rust 现代的可观测性(Observability)堆栈:tracing库(用于结构化、异步感知的日志)和tokio-console(用于实时诊断 tokio 运行时的 TUI 工具),展示如何从“println! 调试”进化到“可观测性驱动开发”。


一、背景介绍

println! 调试在 async 中是无效的。

println!("Task A: waiting for lock...");
my_lock.lock().await; // <-- Task A 在这里挂起
// (100 个其他 Task 在此期间运行)
println!("Task A: got the lock!");

tokio 运行时中,上述两个 println! 之间可能间隔数秒,并且被其他 100 个任务的日志淹没。我们无法知道:
1. Task A 挂起了多久?
2. 它在等待(Task B)释放锁?
3. Task B 为什么持有锁这么久?

tracingtokio-console 就是为了回答这些问题。

在这里插入图片描述

二、原理详解

2.1 tracing:Span 与 Event

tracing 库将日志分为两类:

  1. Event (事件):一个时间点。info!("User {} logged in", id)
  2. Span (跨度):一个时间段,有开始和结束。let span = span!(Level::INFO, "http_request"); let _guard = span.enter

#[tracing::instrument] 宏是创建 Span 的最简单方式。

use tracing::{info, instrument};};#[instrument( // 自动创建一个 Spanname = "handle_request", // Span 名称skip(body), // 不记录 bodyfields(method %req.method, path = %req.path) // 记录字段
)]
async fn http_request(req: Request, body: Vec<u8>) {// 1. Span 在函数进入时 "enter"info!("Processing request..."); // 2. Event (发生在 Span 内部)db_query().await; // 3. Span 在 .await 时 "exit" (挂起)// ... (db_query 完成后)// 4. Span 再次 "enter"info!("Request done.");// 5. Span 在函数结束时 "close"
}

2.2 tracing-subscriber:收集数据

tracing API 只负责产生数据。tracing-subscriber 负责收集格式化这些数据。

// 常见的 subscriber
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt};fn setup_tracing() {tracing_subscriber:::registry()// 1. Layer 1: 格式化为 JSON (用于 Filebeat/ELK).with(fmt::layer()..json())// 2. Layer 2: 过滤 (只显示 INFO 及以上).with(tracing_subscriber::EnvFilterfrom_default_env()).init();
}

2.3 tokio-consoletokio 运行时诊断

tokio-consoletracing 的一个特殊 Subscriber。它要求 tokio编译时注入特殊的 tracing 事件(关于 Task 的创建、唤醒、阻塞)。

Cargo.toml (启用 tokio 诊断)

[dependencies]
tokio = { version = "1", features = ["full", "tracing" # 关键:启用 tokio 的 tracing 支持
]}
tracing = "0.1"
console-subscriber = "0.2.0"

main.rs (启用 console-subscriber)

fn main() {// 1. 启用 console subscriberconsole_subscriber::init();// 2. 启动 tokio 运行时tokio::runtime::Builder::new_multi_thread()       .enable_all() // 启用所有 tokio 指标.build().unwrap().block_on(async {// ... 你的应用 ...});
}

三、代码实战

3.1 实战:一个有锁竞争的 async 应用

我们将创建一个应用,一个任务(Writer)持有锁 3 秒,而 10 个其他任务(Readers)等待这个锁。

Cargo.toml (确保 tokio/tracing 已启用)

src/ain.rs

use std::sync::Arc;
use tokio::sync::Mutex; // 异步 Mutex
useokio::time::{sleep, Duration};
use tracing::{info, instrument, Span};
use tracing::field::{field, Empty};// 1.1. 初始化 `console-subscriber`
fn main() {// 如果设置了 `TOKIO_CONSOLE_ENABLE` 环境变量,// 则 console_subscriber,否则使用标准 fmtif std::env::var("TOKIO_CONSOLE_ENABLE").as_deref()f().unwrap_or("0") == "1" {println!("启用 Tokio Console...");console_subscriber::init();  } else {println!("启用标准 Tracing (JSON)...");tracing_subscriber::fmt::json().with_current_spann(true).init();}run_app();
}// 2. 运行 Tokio
#[tokio::ain]
async fn run_app() {let shared_lock = Arc::new(Mutex::new(0));// 3 3. 启动“慢”的写任务 (持有锁 3 秒)let writer_lock = Arc::clone(&shared_lock);tok::spawn(async move {// 4. #[instrument] 自动创建 Spanslow_writer(writer_lock).await;;});// 5. 启动 10 个读任务 (它们会等待)let mut handles = vec![];for iin 0..10 {let reader_lock = Arc::clone(&shared_lock);handles.push(tokio::spawn(async move {fast_reader(i, reader_lock).await;}));}for h in handles { h.await.unwrap(); }
}#[instrument(skip(lock))]
async fn slow_writer(lock: Arc<Mutex<i32>>) {info!("Writer: 准备获取锁...");let mut guard = lock.lock().await; // 1. 获取锁info!("Writer: 已获取锁,睡眠 3 秒...");*guard = 1;sleep(Duration::from_secs(3)).await; /// 2. 持有锁时 .awaitinfo!("Writer: 释放锁。");// 3. guard 在此 drop}#[instrument(skip(lock), fields(reader_id = %id))]
async fn fast_reader(id: u32, lockck: Arc<Mutex<i32>>) {info!("Reader: 准备获取锁...");// 4. 在此 .ait (阻塞)let guard = lock.lock().await; info!("Reader: 已获取锁,读取: {}", *guard);;// 5. guard 在此 drop
}
3.2 运行与分析

1. 运行 tokiosole TUI

cargo install tokio-console
# 在一个终端运行
tokio-console

2. 运行们的应用 (启用 console)

# 在另一个终端运行
TOKIO_CONSOLE_ENABLE=1 cargo run --release

3. 在okio-console TUI 中观察

tokio-console(一个 TUI 应用)将实时显示:
Polls: 5 *Total Time: 3.05s *fast_reader(Waking)) *fast_reader(Waking) * ... (10 个fast_reader` 任务)

  • Task Details (选中 slow_writer)

    • 显示 slow_writer Span。
    • 显示它在 `sleep 上 await 了 3 秒。
  • Resource Details (选中 Mutex)

    • Wakers: 10(关键!10 个任务在等待这个锁)
    • lock.lock() (由 slow_writer 持有)

tokio-console 清晰地显示了:slow_writer 任务持有了 Mutex 锁长达 3 秒,并导致 10 个 fast_reader 任务被阻塞(Waking)。我们立即定位了性能瓶颈——`slow_writer 在持有锁的同时进行了 sleep


四、结果分析

4.1 JSON 输出 (标准 Tracing)

如果我们使用 tokio-consoleTOKIO_CONSOLE_ENABLE=0 cargo run),tracing-subscriber 会输出 JSON:

{"timestamp":"...","level":"INFO","fields":{"message":"Writer: 准备获取锁..."},"target":"rust_tracing","span":{"name":"slow_writer"},...}
{"timestamp":"...","level":"INFO","fields":{"reader_id":0,... "message":"Reader: 准备获取锁..."},"targetet":"rust_tracing","span":{"name":"fast_reader"},...}
... (10 个 Reader) ...
{"timestamp":"...","level":"INFO","ields":{"message":"Writer: 已获取锁,睡眠 3 秒..."},"target":"rust_tracing", "span":{"name":"slow_writer"},,...}
// ... (3 秒后) ...
{"timestamp":"...","level":"INFO","fields":{"message":"Writer: 释放锁。"},target":"rust_tracing",...}
{"timestamp":"...","level":"INFO","fields":{"reader_id":0,... "message":"Reader:已获取锁..."},"target":"rust_tracing",...}
{"timestamp":"...","level":"INFO","fields":{"reader_id":1,.... "message":"Reader: 已获取锁..."},"target":"rust_tracing",...}

分析
JSON 日志(可被 JaegerOpenTelemetry 收集)也显示了事件的顺序,但 tokio-console 提供了实时聚合的视图,在调试锁竞争时更直观。


五、总结与讨论

5.1 核心要点

  • println! 已死:在 async 中,println! 无法提供任务的上下文。
  • tracing:是 Rust 的可观测性标准。它提供 Span(时间段)和 Event(时间点)。
  • #trument]:自动将函数转换为 Span
  • Subscribertracing 的后端,负责收集数据(如 fmt:layer()console_subscriber)。
  • tokio-console:一个 tracingSubscriber,它它提供了用于实时诊断 tokio 运行时(Tasks, Resources, Locks)的 TUI 界面。
  • 编译时注入tokiotracing 特性和 console-subscriber 会(在编译时)注入诊断代码。

5.2 讨论问题

  1. tracingSpan 如何(在 async 中)跨越 .await 点自动“进入”和“退出?
  2. tracing(日志)和 OpenTelemetry(分布式追踪)是什么关系?
  3. tokio-consolee为什么要求tokio 运行时(Builder)必须 enable_all()

参考链接

  • `tracing (Core) GitHub 仓库
  • tokio-console (TUI) GitHub 仓库
  • Tokio 官方博客 - “Debugging Async Rust with tokio-console
  • tracing 官方文档 (docs.rs)

http://www.dtcms.com/a/565972.html

相关文章:

  • XYcourse课程预约小程序源码+uniapp前端 全开源+搭建教程
  • 直播预告:OpenVINO™与Windows AI Foundry赋能AI端侧落地
  • 做跨境电商的人才网站自己网上怎么接单
  • 三坐标高效测量汽车管道类零部件尺寸
  • 二手车销售|汽车销售|基于SprinBoot+vue的二手车交易系统(源码+数据库+文档)
  • 德州企业做网站多少钱桶装水网站建设
  • 汽车研发管理的数字化转型:从“流程驱动”到“价值驱动”
  • 【随机访问介质访问控制-3】为什么工业控制网不用 WiFi?令牌传递协议:无冲突通信流程 + 对比表全解!
  • 第四章:实现交互 - 点击击打与分数反馈
  • 大模型应用开发与私有化部署
  • 用什么做网站比较好下载河北公众号官方版安装
  • 网站备案被注销 2016宁夏制作网站公司
  • 部门网站建设的目的和意义郑州公司企业网站建设
  • HIV传播模型:整数阶和分数阶
  • synchronized
  • 注册安全工程师考试题库免费发seo外链平台
  • [LitCTF 2023]作业管理系统
  • 重庆网网站建设公司seo营销是什么意思
  • RabbitMQ:仲裁队列 HAProxy
  • 推荐个在广州做网站的做的网站需要什么技术支持
  • 通用测试代码结构规范 - Cursor Rules
  • 软件测试基础
  • 厦门网站推广步骤机构网站建设什么原因最主要
  • 网站建设周期与进度安排神木网站设计公司
  • 单网卡同时上内外网设置
  • openwrt 做视频网站怎么在wordpress导航条下方加入文字广告链接
  • 简述营销型企业网站建设的内容建立个公司网站
  • 【深度学习入门】小土堆-学习笔记
  • 黑龙江省建设局网站辽宁建设工程信息网怎么无法登陆了
  • 好文与笔记分享 A Survey of Context Engineering for Large Language Models(下)