Rust in Everything
基于 tracing 0.1.44

Span 与 Entered

主线位置:span 主线的用户一侧。info_span! 过了四道门以后,手里拿到的就是本页的 Span;之后的进入、退出、补字段、克隆、丢弃,都是 Span 上的一个方法调用,各自转成一次 subscriber 回调。被门挡掉的 span 也是一个 Span,只是里面没有 Inner。

flowchart TB
  mac["info_span!(...)"] --> gate{"四道门"}
  gate -->|没过| dis["Span inner=None"]
  gate -->|过了| ns["new_span 分配 Id"]
  ns --> sp["Span inner=Some"]
  sp -->|"enter()"| ent["Entered 借用"]
  sp -->|"entered()"| ens["EnteredSpan 拥有"]
  ent -->|drop| exitnode["exit"]
  ens -->|"drop 或 exit()"| exitnode
  sp -->|clone| cs["clone_span"]
  sp -->|drop| tc["try_close"]
  sp -->|record| rec["record"]
  sp -->|follows_from| ff["record_follows_from"]
  dis -->|任何操作| noop["不调 subscriber"]

tracing::Span 是用户代码真正持有的东西,定义在 tracing/src/span.rs。它很薄:span 的名字、字段、父子关系都不在里面,而在 subscriber 那里(见 Subscriber trait)。Span 的每个方法几乎都是「如果有 Inner,就调一次 subscriber」。本页逐个看这些方法,再用一个把每次回调都打印出来的 subscriber 验证。new_span 收到的 Attributes、父 span 的三种写法见 span 与 Event 的数据;宏怎么展开成 Span::new 见 宏展开。

Span 的两个字段

Span 和它的 Inner:

#[derive(Clone)]
pub struct Span {
    /// A handle used to enter the span when it is not executing.
    ///
    /// If this is `None`, then the span has either closed or was never enabled.
    inner: Option<Inner>,
    /// Metadata describing the span.
    ///
    /// This might be `Some` even if `inner` is `None`, in the case that the
    /// span is disabled but the metadata is needed for `log` support.
    meta: Option<&'static Metadata<'static>>,
}

#[derive(Debug)]
pub(crate) struct Inner {
    /// The span's ID, as provided by `subscriber`.
    id: Id,

    /// The subscriber that will receive events relating to this span.
    ///
    /// This should be the same subscriber that provided this span with its
    /// `id`.
    subscriber: Dispatch,
}
字段内容用途
innerSome(Inner { id, subscriber }) 或 None有它才会调 subscriber;None 表示被过滤掉或本来就是空 span
meta调用点的 &'static Metadata 或 Nonerecord 靠它把字段名换成 Field;log feature 靠它在没有 subscriber 时也能发 log 记录

Inner 里存的 Dispatch 是创建时 Span::make_with 克隆进来的,之后所有回调都直接用它,不再查当前默认的 dispatcher(见 Dispatch:全局与线程局部)。一个 span 无论在哪个线程、哪个 set_default 作用域里被进入或丢弃,回调都回到创建它的那个 subscriber。

Inner 是 pub(crate) 的,外面只能通过 id()、metadata()、with_subscriber 间接看到它。

空 span:Span::none 与 is_disabled

被门挡掉的 span 走宏的 else 分支,调 __disabled_span。它有两个版本,取决于 tracing 的 log feature:

    #[inline]
    #[cfg(feature = "log")]
    pub fn __disabled_span(meta: &'static Metadata<'static>) -> crate::Span {
        crate::Span::new_disabled(meta)
    }

    // ...
    #[inline]
    #[cfg(not(feature = "log"))]
    pub fn __disabled_span(_: &'static Metadata<'static>) -> crate::Span {
        crate::Span::none()
    }

Span::new_disabled 是 inner: None, meta: Some(meta),Span::none 是两个都为 None 的 const fn。两个判断方法对应这两种情况:

方法条件没开 log 时的被过滤 span开了 log 时的被过滤 spanSpan::none()
is_disabledinner.is_none()truetruetrue
is_noneinner 和 meta 都是 Nonetruefalsetrue

后两列是下面实验第 [2] 步分别在不开和开 log feature 的两个 crate 里跑出来的结果。想判断「这个 span 有没有被 subscriber 收下」,用 is_disabled;is_none 的结果会随一个 feature 变。

对空 span 调任何方法都不会碰 subscriber:enter / exit / record / follows_from 第一步都是 if let Some(inner) = self.inner,clone 克隆一个 None,drop 也一样。开了 log feature 时例外:meta 还在,这些操作可能发出 log 记录,见 和 log 互通。

Span::none() 的用处是代替 Option<Span>:函数签名里写 Span,「没有 span」就传 Span::none(),调用方不用分支。or_current 是另一个方向:is_disabled() 时换成 Span::current(),常用在 .instrument(debug_span!(..).or_current()) 里,让被过滤掉的子 span 至少挂在当前 span 上。

enter 与 Entered

enter 和守卫的 Drop:

    #[inline(always)]
    pub fn enter(&self) -> Entered<'_> {
        self.do_enter();
        Entered { span: self }
    }

impl Drop for Entered<'_> {
    #[inline(always)]
    fn drop(&mut self) {
        self.span.do_exit()
    }
}

do_enter 就是 inner.subscriber.enter(&inner.id),do_exit 对应 exit,两者后面各跟一段 if_log_enabled!。do_exit 上方的注释说明了为什么退出放在 Drop 里而不是让用户显式调用:「Running this behaviour on drop rather than with an explicit function call means that spans may still be exited when unwinding」,panic 展开时也能退出。

Entered<'a> 只有一个字段 span: &'a Span,带 #[must_use = "once a span has been entered, it should be exited"]。写 let _ = span.enter(); 守卫当场就 drop,等于进去马上出来。

它是 Send 的:Span 里的 Dispatch 装的是 &'static 或 Arc 形式的 dyn Subscriber + Send + Sync,Span 是 Sync,&Span 就是 Send,Entered 没有加任何阻止它的标记。下面这段放进 tokio::spawn,单独编译,cargo build 退出码为 0:

tokio::spawn(async {
  let span = info_span!("a");
  let _g = span.enter();
  tokio::task::yield_now().await;
})

能编译不等于正确:跨 .await 持有守卫会把 span 留在线程的栈上,被下一个任务继承。真实输出和正确写法见 Instrument 与异步。

entered 与 EnteredSpan

同步代码里最常见的写法是 span 对应一个词法作用域。用 enter 要两行:

let span = info_span!("work");
let _g = span.enter();

entered 按值拿走 span,返回拥有它的 EnteredSpan,一行就够:let _g = info_span!("work").entered();。

pub struct EnteredSpan {
    span: Span,

    /// ```compile_fail
    /// use tracing::span::*;
    /// trait AssertSend: Send {}
    ///
    /// impl AssertSend for EnteredSpan {}
    /// ```
    _not_send: PhantomNotSend,
}

EnteredSpan 实现了 Deref<Target = Span>,所以进入状态下照样能 record、follows_from;exit 退出并把 Span 还回来,内部用 mem::replace(&mut self.span, Span::none()) 把 span 换出来(注释:「One does not simply move out of a struct with Drop」),留下的 Span::none() 随后 drop 时什么也不做。

_not_send 的类型是 PhantomNotSend,一个 PhantomData<*mut ()>,再手动 unsafe impl Sync。它上面的文档写明了用意:

/// Technically, `EnteredSpan` _can_ implement both `Send` *and*
/// `Sync` safely. It doesn't, because it has a `PhantomNotSend` field,
/// specifically added in order to make it `!Send`.
///
/// Sending an `EnteredSpan` guard between threads cannot cause memory unsafety.
/// However, it *would* result in incorrect behavior, so we add a
/// `PhantomNotSend` to prevent it from being sent between threads. This is
/// because it must be *dropped* on the same thread that it was created;
/// otherwise, the span will never be exited on the thread where it was entered,
/// and it will attempt to exit the span on a thread that may never have entered
/// it. However, we still want them to be `Sync` so that a struct holding an
/// `Entered` guard can be `Sync`.

换成 entered(),同样放进 tokio::spawn(src/bin/not_send.rs):

tokio::spawn(async {
  let _g = info_span!("b").entered();
  tokio::task::yield_now().await;
})

rustc 1.99.0 拒绝:

error: future cannot be sent between threads safely
   --> src/bin/not_send.rs:15:3
    |
 15 | /   tokio::spawn(async {
 16 | |     let _g = info_span!("b").entered();
 17 | |     tokio::task::yield_now().await;
 18 | |   })
    | |____^ future created by async block is not `Send`
    |
    = help: within `{async block@src/bin/not_send.rs:15:16: 15:21}`, the trait `Send` is not implemented for `*mut ()`
note: future is not `Send` as this value is used across an await
   --> src/bin/not_send.rs:17:30
    |
 16 |     let _g = info_span!("b").entered();
    |         -- has type `EnteredSpan` which is not `Send`
 17 |     tokio::task::yield_now().await;
    |                              ^^^^^ await occurs here, with `_g` maybe used later

报错里的 *mut () 就是 PhantomNotSend 里那个 PhantomData<*mut ()>。注意它挡住的只是「被 tokio::spawn 这类要求 Send 的地方」使用;在 current_thread 运行时的 block_on 或 spawn_local 里跨 .await 持有 EnteredSpan,照样能编译,照样会错。

📝NOTE
没有蠢问题

问: 两个守卫做的事一样,为什么 Entered 是 Send、EnteredSpan 不是?换线程 drop 的问题两者都有。

答: 两个类型不是同时设计的。Entered 从早期就存在,只借用 span,没有加任何标记,自动推导出 Send。EnteredSpan 是后来(tracing 0.1.25)新加的类型,加的时候就带上了 PhantomNotSend。给一个已经发布、已经是 Send 的公开类型去掉 Send,会让依赖这一点的下游代码编译失败,是破坏性改动,0.1 线上不能这样改 Entered。两者的差别是历史留下的,不是因为借用版更安全。

in_scope

in_scope 是 enter 加一个闭包:

    pub fn in_scope<F: FnOnce() -> T, T>(&self, f: F) -> T {
        let _enter = self.enter();
        f()
    }

闭包是同步的,守卫的生命周期不可能跨过 .await,所以 enter 文档里「In Asynchronous Code」一节推荐在 async 函数里用它包住一段同步代码。span 是空的时候,f 照样执行,只是不进任何 span。

📝NOTE
冷知识:entered() 来自一个「多一个局部变量」的抱怨

tracing 0.1.25 之前只有 enter(&self),守卫借用 span,所以 span 本身必须先绑到一个变量上。#1246 提出:「When writing non-async code, the common case is that a span precisely corresponds to a lexical scope. At the moment, this case requires an introduction of otherwise unnecessary local variable」,并建议新加一个拥有 span 的守卫,草案里已经带着 _not_send: PhantomData<*mut ()>。#1252 实现了它,提交说明是「This PR allows the following API: let _guard = tracing::span!("foo").entered();」。同一个提交把 enter 和 entered 的公共部分抽成 do_enter / do_exit,加上了上面引用的那段 PhantomNotSend 文档,并在测试里断言 Entered<'_> 和 EnteredSpan 都是 Sync。

这个 PR 合进的是当时的 master 分支,0.1 线上的回移提交是 a358728b,第一个包含它的版本是 tracing 0.1.25(CHANGELOG:「Span::entered method for entering a span and moving it into a guard by value rather than borrowing it」)。

Clone 与 Drop:引用计数

Span 的 Clone 是派生的,克隆 Option<Inner> 时走 Inner 的 Clone:

impl Clone for Inner {
    fn clone(&self) -> Self {
        Inner {
            id: self.subscriber.clone_span(&self.id),
            subscriber: self.subscriber.clone(),
        }
    }
}

clone_span 返回的 Id 被用作新句柄的 Id。默认实现原样返回,Registry 也返回同一个 Id,只把计数加一;文档另外提到,如果 Id 本身编码的是某种指针,可以把这里当作「克隆指针」的钩子。Span 的 Drop 对应地调 try_close:

impl Drop for Span {
    #[inline(always)]
    fn drop(&mut self) {
        if let Some(Inner {
            ref id,
            ref subscriber,
        }) = self.inner
        {
            subscriber.try_close(id.clone());
        }
        // ...
    }
}

Span 本身不计数,它只负责「每多一个句柄告诉 subscriber 一次、每少一个告诉一次」。计数在 subscriber 里,try_close 返回 true 才表示最后一个句柄没了;Registry 怎么计数、关闭时怎么保证所有 layer 的 on_close 先跑完,见 Registry。这也是为什么 Span::current() 和 .instrument(span.clone()) 这类写法不会提前关掉 span:它们各自持有一个句柄。

Span 实现了 PartialEq 和 Hash:相等要求两边的元数据来自同一个调用点、Inner 的 Id 相等;两个都没有 meta 的 span(比如两个 Span::none())不相等。

record:只能填声明过的字段

record:

    pub fn record<Q: field::AsField + ?Sized, V: field::Value>(
        &self,
        field: &Q,
        value: V,
    ) -> &Self {
        if let Some(meta) = self.meta {
            if let Some(field) = field.as_field(meta) {
                self.record_all(
                    &meta
                        .fields()
                        .value_set(&[(&field, Some(&value as &dyn field::Value))]),
                );
            }
        }

        self
    }

field.as_field(meta) 在调用点的 FieldSet 里按名字找;FieldSet 是宏在编译期写死的名字数组(见 字段:Field、Value 与 Visit),创建时没写的名字找不到,返回 None,整个调用静默地什么也不做,不报错也不 panic。要在创建之后才知道值的字段,创建时先写成 field::Empty 占个名字:

let span = info_span!("req", id = 1, status = field::Empty);
// ...
span.record("status", 200);

找到之后 record_all 把它包成 span::Record,交给 Subscriber::record。Record 只带值,不带元数据,subscriber 按 Id 找到自己存的 span 数据再合并。注意判断用的是 meta 而不是 inner:被过滤掉的 span 在开了 log feature 时 meta 还在,record 会走到 record_all,只是 inner 为 None,不调 subscriber。

record 能对同一个字段记多次,Registry + fmt 的组合会把新值追加到已经格式化好的字段串后面,而不是替换(fmt 怎么存字段见 fmt)。

follows_from

一个 span 只有一个父,但可以「跟随」任意多个别的 span。follows_from:

    pub fn follows_from(&self, from: impl Into<Option<Id>>) -> &Self {
        if let Some(ref inner) = self.inner {
            if let Some(from) = from.into() {
                inner.follows_from(&from);
            }
        }
        self
    }

参数是 impl Into<Option<Id>>,&Span、Span、&EnteredSpan、Option<Id> 都能传;空 span 转出来是 None,于是两边任何一边是空 span,这次调用都什么也不做。最后调到 Subscriber::record_follows_from(&self.id, &from)。文档说这种关系不同于父子:「spans are not considered to be executing inside of the spans they follow from」,用来表示一个任务派生出的后台任务这类因果关系。Registry 收到它什么也不存(它的 record_follows_from 是空实现),要用到它的是 OpenTelemetry 这类导出 link 的 layer。

Span::current

Span::current:

    pub fn current() -> Span {
        dispatcher::get_default(|dispatch| {
            if let Some((id, meta)) = dispatch.current_span().into_inner() {
                let id = dispatch.clone_span(&id);
                Self {
                    inner: Some(Inner::new(id, dispatch)),
                    meta: Some(meta),
                }
            } else {
                Self::none()
            }
        })
    }

两次回调:先 current_span 问「当前在哪个 span 里」,再 clone_span 为新句柄加一个计数。tracing 自己不知道当前 span,这个问题只有 subscriber 能回答(span::Current 的三种状态见 span 与 Event 的数据)。拿不到时返回 Span::none(),而不是 new_disabled,因为没有调用点的元数据可放。

和 make_with 不同,这里的 Dispatch 来自 get_default,是调用 Span::current() 那一刻、那个线程的默认 dispatcher。

用一个打印 subscriber 看每个操作

下面这个 subscriber 实现了 span 生命周期组的全部回调,每个回调打印一行:自己分配 Id、自己数引用、用一个线程局部的栈记录当前 span;enabled 关掉 DEBUG 及更详细的级别。下一页 Instrument 与异步 也用它。

tracing = "=0.1.44"
tracing-core = "=0.1.36"
// src/printer.rs
use std::{
  cell::RefCell,
  collections::HashMap,
  sync::{
    Mutex,
    atomic::{AtomicU64, Ordering},
  },
};

use tracing::{
  Event, Level, Metadata, Subscriber, field,
  span::{Attributes, Id, Record},
};
use tracing_core::span::Current;

// 每个回调打印一行;自己分配 Id、数引用、维护线程局部的 span 栈
pub struct Printer {
  next_id: AtomicU64,
  spans: Mutex<HashMap<u64, (&'static Metadata<'static>, usize)>>, // id -> (元数据, 引用计数)
}

thread_local! {
  static STACK: RefCell<Vec<Id>> = const { RefCell::new(Vec::new()) };
}

impl Printer {
  pub fn new() -> Self {
    Printer { next_id: AtomicU64::new(1), spans: Mutex::new(HashMap::new()) }
  }

  fn name(&self, id: &Id) -> &'static str {
    self.spans.lock().unwrap()[&id.into_u64()].0.name()
  }
}

// 把字段拼成 "k=v k=v"
fn fields(rec: impl FnOnce(&mut dyn field::Visit)) -> String {
  let mut out = String::new();
  let mut visit = |f: &field::Field, v: &dyn std::fmt::Debug| {
    out.push_str(&format!(" {}={:?}", f.name(), v));
  };
  rec(&mut visit);
  out
}

impl Subscriber for Printer {
  fn enabled(&self, meta: &Metadata<'_>) -> bool {
    *meta.level() <= Level::INFO
  }
  fn new_span(&self, attrs: &Attributes<'_>) -> Id {
    let id = self.next_id.fetch_add(1, Ordering::Relaxed);
    let name = attrs.metadata().name();
    self.spans.lock().unwrap().insert(id, (attrs.metadata(), 1));
    println!("  new_span({name}) -> {id}{}", fields(|v| attrs.record(v)));
    Id::from_u64(id)
  }
  fn record(&self, id: &Id, values: &Record<'_>) {
    println!("  record({}){}", self.name(id), fields(|v| values.record(v)));
  }
  fn record_follows_from(&self, id: &Id, follows: &Id) {
    println!("  record_follows_from({}, {})", self.name(id), self.name(follows));
  }
  fn event(&self, event: &Event<'_>) {
    // 事件发生时栈顶是哪个 span
    let top = STACK.with(|s| s.borrow().last().map(|id| self.name(id)));
    println!("  event{} (in {})", fields(|v| event.record(v)), top.unwrap_or("-"));
  }
  fn enter(&self, id: &Id) {
    STACK.with(|s| s.borrow_mut().push(id.clone()));
    println!("  enter({})", self.name(id));
  }
  fn exit(&self, id: &Id) {
    STACK.with(|s| s.borrow_mut().pop());
    println!("  exit({})", self.name(id));
  }
  fn clone_span(&self, id: &Id) -> Id {
    let mut spans = self.spans.lock().unwrap();
    let entry = spans.get_mut(&id.into_u64()).unwrap();
    entry.1 += 1;
    println!("  clone_span({}) refs={}", entry.0.name(), entry.1);
    id.clone()
  }
  fn try_close(&self, id: Id) -> bool {
    let mut spans = self.spans.lock().unwrap();
    let entry = spans.get_mut(&id.into_u64()).unwrap();
    entry.1 -= 1;
    let closed = entry.1 == 0;
    println!("  try_close({}) refs={} -> {closed}", entry.0.name(), entry.1);
    closed
  }
  fn current_span(&self) -> Current {
    println!("  current_span()");
    match STACK.with(|s| s.borrow().last().cloned()) {
      Some(id) => {
        let meta = self.spans.lock().unwrap()[&id.into_u64()].0;
        Current::new(id, meta)
      }
      None => Current::none(),
    }
  }
}

span::Current 在 tracing 里没有重新导出,所以要直接依赖 tracing-core。主程序按编号做一遍本页讲过的操作:

// src/bin/span_ops.rs
#[path = "../printer.rs"]
mod printer;

use tracing::{Span, field, info, info_span};

fn main() {
  let sub = printer::Printer::new();
  tracing::subscriber::with_default(sub, || {
    println!("[1] info_span!(\"a\", x = 1, later = field::Empty)");
    let a = info_span!("a", x = 1, later = field::Empty);

    println!("[2] debug_span!(\"off\")");
    let off = tracing::debug_span!("off");
    println!(
      "    off: is_disabled={} is_none={} | Span::none(): is_disabled={} is_none={}",
      off.is_disabled(),
      off.is_none(),
      Span::none().is_disabled(),
      Span::none().is_none(),
    );

    println!("[3] a.record(\"later\", 5); a.record(\"nope\", 6)");
    a.record("later", 5);
    a.record("nope", 6);

    println!("[4] let b = a.clone()");
    let b = a.clone();

    println!("[5] let g = a.enter(); info!(..); Span::current()");
    let g = a.enter();
    info!("inside a");
    let cur = Span::current();
    println!("[6] drop(cur); drop(g)");
    drop(cur);
    drop(g);

    println!("[7] a.in_scope(|| info!(..))");
    a.in_scope(|| info!("in scope"));

    println!("[8] let e = info_span!(\"e\").entered(); b.follows_from(&e)");
    let e = info_span!("e").entered();
    b.follows_from(&e);
    println!("[9] let e = e.exit()");
    let e = e.exit();

    println!("[10] off.enter(); off.record(..); off.clone()");
    let _g = off.enter();
    off.record("x", 1);
    let _off2 = off.clone();

    println!("[11] drop(e); drop(b); drop(a)");
    drop(e);
    drop(b);
    drop(a);
    println!("[end]");
  });
}

M2 Max、rustc 1.99.0、--release,跑 3 次输出相同。第 [11] 步以后的输出放在本页末尾的「动脑筋答案」里:

[1] info_span!("a", x = 1, later = field::Empty)
  new_span(a) -> 1 x=1
[2] debug_span!("off")
    off: is_disabled=true is_none=true | Span::none(): is_disabled=true is_none=true
[3] a.record("later", 5); a.record("nope", 6)
  record(a) later=5
[4] let b = a.clone()
  clone_span(a) refs=2
[5] let g = a.enter(); info!(..); Span::current()
  enter(a)
  event message=inside a (in a)
  current_span()
  clone_span(a) refs=3
[6] drop(cur); drop(g)
  try_close(a) refs=2 -> false
  exit(a)
[7] a.in_scope(|| info!(..))
  enter(a)
  event message=in scope (in a)
  exit(a)
[8] let e = info_span!("e").entered(); b.follows_from(&e)
  new_span(e) -> 2
  enter(e)
  record_follows_from(a, e)
[9] let e = e.exit()
  exit(e)
[10] off.enter(); off.record(..); off.clone()

逐步对照:

  • [1] new_span 只带了 x=1。later 是 field::Empty,在 FieldSet 里有名字,但 Attributes::record 访问不到它。
  • [2] off 被 enabled 挡掉,没有任何回调,Id 2 也没被占用([8] 的 e 拿到的是 2)。这个 crate 没开 log feature,off 就是 Span::none(),is_none 为 true。把同一个程序放进 tracing = { version = "=0.1.44", features = ["log"] } 的 crate 里,这一行变成 off: is_disabled=true is_none=false | Span::none(): is_disabled=true is_none=true。
  • [3] later 被记下;nope 没有声明过,没有回调,也没有报错。
  • [4] clone 变成 clone_span,计数到 2。
  • [5] Span::current() 是 current_span 加 clone_span 两次回调,返回的新句柄让计数到 3。
  • [6] drop 这个新句柄,try_close 返回 false,span 还活着;drop 守卫,exit。
  • [7] in_scope 就是一对 enter / exit。
  • [8] entered() 在 new_span 之后马上 enter;follows_from(&e) 传的是 &EnteredSpan,转成 Option<Id> 后调 record_follows_from。
  • [9] exit() 退出并还回 Span,没有 try_close:span 还在 e 手里。
  • [10] 对空 span 的 enter、record、clone 一行输出都没有。
❗IMPORTANT
动脑筋:第 [11] 步的三次 drop 各打印什么?

drop(e); drop(b); drop(a)。会有几行 try_close,每行的 refs 和返回值是多少?b 是 a 的克隆,e 被 a 的克隆 b 「跟随」过,这会不会让 e 晚一点关闭?

动脑筋答案

第 [11] 步之后的输出(同一次运行,接在上面的输出后面):

[11] drop(e); drop(b); drop(a)
  try_close(e) refs=0 -> true
  try_close(a) refs=1 -> false
  try_close(a) refs=0 -> true
[end]
  • drop(e):e 从创建到现在没被克隆过,计数从 1 减到 0,try_close 返回 true,span 关闭。follows_from 不增加计数:Span::follows_from 只把 Id 交给 subscriber,没有调用 clone_span,所以被跟随的 span 照样可以先关闭,这和文档「a span may close even if subsequent spans that follow from it are still open」一致。
  • drop(b):a 和 b 是同一个 span 的两个句柄([5] 的那个 Span::current() 句柄在 [6] 已经还掉),计数从 2 减到 1,返回 false。
  • drop(a):最后一个句柄,减到 0,返回 true。
  • off 和它的克隆 _off2、守卫 _g 在闭包结束时 drop,都是空 span,没有输出。

小结

  • Span 只有两个字段:inner: Option<Inner>(Id + 创建时的 Dispatch)和 meta;span 的数据都在 subscriber 里,之后的回调都回到创建它的那个 subscriber。
  • 被过滤掉的 span:不开 log feature 时就是 Span::none(),开了时是 new_disabled(meta);判断用 is_disabled,is_none 会随 feature 变。对它的操作不调 subscriber。
  • enter 返回借用的 Entered,是 Send 的;entered 返回拥有 span 的 EnteredSpan,靠 PhantomNotSend 不是 Send(tracing 0.1.25,#1252)。退出都在守卫的 Drop 里,展开时也能退出。
  • in_scope 是 enter 加同步闭包;clone / drop 对应 clone_span / try_close,计数在 subscriber 里。
  • record 只能填创建时声明过的字段,没声明的静默忽略,要后填的先写 field::Empty;follows_from 不影响计数,空 span 两边都忽略。
  • Span::current() 是 current_span 加 clone_span,subscriber 不跟踪当前 span 时得到 Span::none()。

💬 关联讨论

发起讨论
加载中...