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 的两个字段
#[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,
}
| 字段 | 内容 | 用途 |
|---|---|---|
inner | Some(Inner { id, subscriber }) 或 None | 有它才会调 subscriber;None 表示被过滤掉或本来就是空 span |
meta | 调用点的 &'static Metadata 或 None | record 靠它把字段名换成 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 时的被过滤 span | Span::none() |
|---|---|---|---|---|
is_disabled | inner.is_none() | true | true | true |
is_none | inner 和 meta 都是 None | true | false | true |
后两列是下面实验第 [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,照样能编译,照样会错。
问: 两个守卫做的事一样,为什么 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。
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:只能填声明过的字段
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
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挡掉,没有任何回调,Id2 也没被占用([8] 的e拿到的是 2)。这个 crate 没开logfeature,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一行输出都没有。
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:不开
logfeature 时就是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()。