Rust in Everything
基于 tracing-subscriber 0.3.23

reload 与运行时改级别

主线位置:事件主线的第二、三道门。LevelFilter::current()(全局 MAX_LEVEL)和调用点里缓存的 Interest,都是按装上时的 filter 算出来的。运行时换掉 filter,这两个缓存不会自己更新;reload 的 Handle 在每次修改之后调用 callsite::rebuild_interest_cache,把它们按新的 filter 重算一遍。

flowchart TB
  modify["Handle::modify(f)"] --> up["Weak::upgrade"]
  up -->|失败| gone["Error:subscriber 已没了"]
  up --> wl["inner.write()"]
  wl --> fcall["f(&mut L)"]
  fcall --> dl["drop 写锁"]
  dl --> rb["rebuild_interest_cache"]
  rb --> rc["每个调用点重问 register_callsite"]
  rb --> ml["重算 MAX_LEVEL"]
  ml --> logmax["log::set_max_level"]

tracing_subscriber::fmt().init() 装上的 subscriber 是一棵类型固定的 Layered<...>,全局默认又只能设一次(见 Dispatch:全局与线程局部)。程序跑起来以后想把级别从 info 调到 debug,既不能重装 subscriber,也不能改它的类型,只能在类型里预留一个「可以换内容的格子」。reload.rs 一共 394 行,提供的就是这个格子。Layer / Filter 两个 trait 见 Layer 与 Layered 和 过滤:全局与每层,Interest 缓存和重算见 Callsite 与 Interest 缓存。

本页的实验都在 M2 Max、rustc 1.99.0 上跑,依赖:

tracing = "=0.1.44"
tracing-subscriber = { version = "=0.3.23", features = ["env-filter"] }

env-filter 只有第一个实验要用。

reload::Layer 与 Handle

两个类型,共享同一个 RwLock(Layer、Handle):

pub struct Layer<L, S> {
    // TODO(eliza): this once used a `crossbeam_util::ShardedRwLock`. We may
    // eventually wish to replace it with a sharded lock implementation on top
    // of our internal `RwLock` wrapper type. If possible, we should profile
    // this first to determine if it's necessary.
    inner: Arc<RwLock<L>>,
    _s: PhantomData<fn(S)>,
}

pub struct Handle<L, S> {
    inner: Weak<RwLock<L>>,
    _s: PhantomData<fn(S)>,
}

Layer::new 把 inner 放进 Arc<RwLock<L>>,同时 Arc::downgrade 出一个 Handle 一起返回。reload::Layer 进了 subscriber,Handle 留在你手里,可以 clone 后交给别的线程(比如一个管理接口的 handler)。Handle 拿的是 Weak,不让 subscriber 的生命周期被它延长。

RwLock 默认是 std::sync::RwLock;开了 parking_lot feature 则换成 parking_lot 的实现,由 sync.rs 包成同样的接口。S 只出现在 PhantomData<fn(S)> 里,它是这层所在的 subscriber 类型,Layer<S> 的实现需要它。

reload::Layer 实现了两个 trait,每个方法都是「拿读锁,转给里面的值」:

转发时用的是 try_lock! 宏:

macro_rules! try_lock {
    ($lock:expr) => {
        try_lock!($lock, else return)
    };
    ($lock:expr, else $els:expr) => {
        if let ::core::result::Result::Ok(l) = $lock {
            l
        } else if std::thread::panicking() {
            $els
        } else {
            panic!("lock poisoned")
        }
    };
}

锁被毒化(某个线程拿着写锁时 panic 了)时,如果当前线程本身也在 panic,就退回一个保守的默认值,避免 panic 中再 panic;否则直接 panic。各方法的退路:register_callsite 退回 Interest::sometimes(),enabled 和 event_enabled 退回 false,max_level_hint 退回 None,on_event 这类回调直接 return。

每个回调都要拿一次读锁,这就是模块文档说的「a (relatively small) amount of overhead, and should thus only be used as needed」。不改级别的程序不必套 reload。

运行时把 info 调成 debug

use tracing::{debug, info, level_filters::LevelFilter};
use tracing_subscriber::{EnvFilter, fmt, prelude::*, reload};

fn tick(round: u32) {
  debug!(round, "debug event");
  info!(round, "info event");
}

fn levels() {
  println!("tracing MAX_LEVEL = {}, log::max_level() = {}", LevelFilter::current(), log::max_level());
}

fn main() {
  let (filter, handle) = reload::Layer::new(EnvFilter::new("info"));
  tracing_subscriber::registry()
    .with(filter)
    .with(fmt::layer().with_ansi(false).without_time())
    .init();

  tick(1);
  levels();

  handle.reload(EnvFilter::new("debug")).unwrap();
  levels();
  tick(2);
}

(另加依赖 log = "0.4",只为打印 log::max_level()。)

 INFO reload_level: info event round=1
tracing MAX_LEVEL = info, log::max_level() = INFO
tracing MAX_LEVEL = debug, log::max_level() = DEBUG
DEBUG reload_level: debug event round=2
 INFO reload_level: info event round=2

reload 之后,全局 MAX_LEVEL 和 log 的最大级别都变成了 debug,第一轮被挡掉的 debug! 调用点在第二轮输出了。reload::Layer 放在 .with(filter) 的位置,是全局过滤:它在 fmt::Layer 内侧,Layered 问到它说「不」,外面的 fmt 就看不到。

fmt() builder 也有现成的写法:with_filter_reloading 把 builder 里的 EnvFilter 包进 reload::Layer,init 之前用 reload_handle 取出 Handle:

    pub fn with_filter_reloading(
        self,
    ) -> SubscriberBuilder<N, E, crate::reload::Layer<crate::EnvFilter, Formatter<N, E, W>>, W>
    {
        let (filter, _) = crate::reload::Layer::new(self.filter);
        SubscriberBuilder {
            filter,
            inner: self.inner,
        }
    }

new 返回的 Handle 被 _ 丢掉了,没关系:reload_handle 随时可以用 self.filter.handle() 再降级出一个。

modify:改完一定重建缓存

Handle::reload 只是 modify(|layer| *layer = new_value.into())。全部逻辑在 modify:

    pub fn modify(&self, f: impl FnOnce(&mut L)) -> Result<(), Error> {
        let inner = self.inner.upgrade().ok_or(Error {
            kind: ErrorKind::SubscriberGone,
        })?;

        let mut lock = try_lock!(inner.write(), else return Err(Error::poisoned()));
        f(&mut *lock);
        // Release the lock before rebuilding the interest cache, as that
        // function will lock the new layer.
        drop(lock);

        callsite::rebuild_interest_cache();

        // If the `log` crate compatibility feature is in use, set `log`'s max
        // level as well, in case the max `tracing` level changed. We do this
        // *after* rebuilding the interest cache, as that's when the `tracing`
        // max level filter is re-computed.
        #[cfg(feature = "tracing-log")]
        tracing_log::log::set_max_level(tracing_log::AsLog::as_log(
            &crate::filter::LevelFilter::current(),
        ));

        Ok(())
    }
  1. 升级弱引用。subscriber 已经被 drop,返回 ErrorKind::SubscriberGone。
  2. 拿写锁、改值。写锁期间所有线程上经过这一层的回调都在读锁上等着,f 应当很快。
  3. 先放锁,再重建。注释说得明白:rebuild_interest_cache 会遍历所有调用点,对每个调用点问 register_callsite,问到这一层时要拿读锁。std::sync::RwLock 不可重入,拿着写锁去重建,同一线程会自己等自己。
  4. 重建缓存。callsite::rebuild_interest_cache 走到 Callsites::rebuild_interest:重算每个调用点的 Interest,再用所有 dispatcher 的 max_level_hint 重算 MAX_LEVEL。它是全局的,进程里所有调用点、所有 dispatcher 都要过一遍,代价和调用点数量成正比。
  5. 同步 log 的最大级别。开了 tracing-log 时,把新的 LevelFilter::current() 写进 log::set_max_level。这一步来自 #1270(「If reloading the subscriber caused the max tracing level to change, this ensures that the change is propagated to the log crate as well」),它 2021 年合进 master,但 0.1 线上直到 tracing-subscriber 0.3.19 才带上,CHANGELOG 记作「Set log max level when reloading」。在那之前,调高级别后 log::debug! 这类记录会在 log 自己的级别检查处被挡掉。

Error 只有两种:is_dropped()(subscriber 没了)和 is_poisoned()(锁被毒化)。前者的样子:

use tracing_subscriber::{filter::LevelFilter, prelude::*, reload};

fn main() {
  let (filter, handle) = reload::Layer::new(LevelFilter::INFO);
  let subscriber = tracing_subscriber::registry().with(filter);
  drop(subscriber);
  let err = handle.reload(LevelFilter::DEBUG).unwrap_err();
  println!("{err} (is_dropped = {})", err.is_dropped());
}
subscriber no longer exists (is_dropped = true)

读当前值用 with_current(借用)或 clone_current(要求 L: Clone),它们只拿读锁,不触发重建。

为什么必须重建:两道门都记着旧答案

EnvFilter 的静态指令在 register_callsite 时就给出 always / never,max_level_hint 给出一个级别(见 EnvFilter)。这两个回答分别被缓存在调用点的原子变量和全局 MAX_LEVEL 里,之后的事件直接读缓存,不再问 filter(见 Callsite 与 Interest 缓存)。只改 filter 不重建,会发生什么?

reload 总是会重建,没法用它演示。下面写一个过滤层,级别放在一个原子变量里,回答方式和 LevelFilter 当 Layer 用时一样(register_callsite 给 always / never,max_level_hint 给自己的级别)。改原子变量不会触发任何重算,重建由我们手动调用:

use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};

use tracing::{Level, Metadata, Subscriber, debug, info, level_filters::LevelFilter};
use tracing_core::{Interest, callsite};
use tracing_subscriber::layer::{Context, Layer};
use tracing_subscriber::{fmt, prelude::*};

// 0 = WARN, 1 = INFO, 2 = DEBUG:改它不会触发任何重算
static LEVEL: AtomicUsize = AtomicUsize::new(1);

fn level() -> Level {
  [Level::WARN, Level::INFO, Level::DEBUG][LEVEL.load(Relaxed)]
}

// 和 LevelFilter 当全局过滤层时的回答方式一样,只是级别放在一个原子变量里
struct KnobFilter;

impl<S: Subscriber> Layer<S> for KnobFilter {
  fn register_callsite(&self, meta: &'static Metadata<'static>) -> Interest {
    if *meta.level() <= level() { Interest::always() } else { Interest::never() }
  }
  fn enabled(&self, meta: &Metadata<'_>, _: Context<'_, S>) -> bool {
    *meta.level() <= level()
  }
  fn max_level_hint(&self) -> Option<LevelFilter> {
    Some(LevelFilter::from_level(level()))
  }
}

fn tick(step: &str) {
  debug!("debug event");
  info!("info event");
  println!("-- {step}: knob={} MAX_LEVEL={}", level(), LevelFilter::current());
}

fn main() {
  tracing_subscriber::registry()
    .with(KnobFilter)
    .with(fmt::layer().with_ansi(false).without_time().with_target(false))
    .init();

  tick("A start");
  LEVEL.store(2, Relaxed);
  tick("B knob to DEBUG, no rebuild");
  callsite::rebuild_interest_cache();
  tick("C after rebuild");
  LEVEL.store(0, Relaxed);
  tick("D knob to WARN, no rebuild");
  callsite::rebuild_interest_cache();
  tick("E after rebuild");
}

(另加依赖 tracing-core = "=0.1.36",为了直接调 callsite::rebuild_interest_cache。)前三步的输出:

 INFO info event
-- A start: knob=INFO MAX_LEVEL=info
 INFO info event
-- B knob to DEBUG, no rebuild: knob=DEBUG MAX_LEVEL=info
DEBUG debug event
 INFO info event
-- C after rebuild: knob=DEBUG MAX_LEVEL=debug

B 步 filter 已经愿意放行 DEBUG,debug! 还是没有输出。它根本走不到 filter:

  • 第二道门:MAX_LEVEL 还是 info,debug! 展开后的 level_enabled! 比较一下就返回了。
  • 第三道门:即使绕过第二道门,debug! 调用点里缓存的 Interest 是 A 步之前注册时得到的 never,也会直接返回。

C 步手动重建以后,两道门都按新答案放行。Handle::modify 里那行 callsite::rebuild_interest_cache() 做的就是 C 步。

❗IMPORTANT
动脑筋:D 步把级别调到 WARN,但不重建,输出几行事件?E 步重建之后呢?

接着上面的程序跑。D 步之前 debug! 和 info! 两个调用点缓存的都是 C 步重建时得到的 always,MAX_LEVEL 是 debug。

方向反过来(调低级别)时,不重建的后果不是「看不到」而是「关不掉」,下面的答案里有实测。max_level_hint 也属于要转发的回答:reload::Layer 曾经没有转发它。

📝NOTE
冷知识:reload 曾经让第二道门失效

0.3.14 及以前,reload::Layer 没有实现 max_level_hint,用的是 Layer trait 的默认实现,返回 None。#2204 的说明:「When using a reload layer, the fast-path current level check doesn't work, as the max_level_hint is just None, which rebuild_interest interprets as TRACE」。也就是说,只要用了 reload 包过滤器,MAX_LEVEL 就被算成 TRACE,所有被关掉的 trace! / debug! 都得走到第三道门才被挡下。改法是「Pass through to the underlying layer/filter. On poisons, when already panicking, just return None」,就是现在的 max_level_hint 里那句 try_lock!(self.inner.read(), else return None).max_level_hint()。PR 合进的是 master,0.1 线上第一个带它的是 tracing-subscriber 0.3.15,这个版本只修了这一处,CHANGELOG 写的是「fixes a bug where the reload layer would fail to pass through max_level_hint to the underlying layer, potentially breaking filtering」。上面实验里 MAX_LEVEL=info 能随 reload 变成 debug,靠的就是它。

只能换值,不能换类型

Handle<L, S>::reload 接受 impl Into<L>,L 在 reload::Layer::new 时就定死了。想把文本输出换成 JSON:

use tracing_subscriber::{fmt, prelude::*, reload};

fn main() {
  let (layer, handle) = reload::Layer::new(fmt::layer());
  tracing_subscriber::registry().with(layer).init();
  handle.reload(fmt::layer().json()).unwrap();
}

rustc 1.99.0 拒绝(json feature 已开):

error[E0277]: the trait bound `Layer<Registry>: From<Layer<_, JsonFields, Format<Json>>>` is not satisfied
   --> src/bin/reload_type.rs:6:17
    |
  6 |   handle.reload(fmt::layer().json()).unwrap();
    |          ------ ^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound
    |          |
    |          required by a bound introduced by this call
    |
    = help: the trait `From<tracing_subscriber::fmt::Layer<_, JsonFields, Format<Json>>>` is not implemented for `tracing_subscriber::fmt::Layer<Registry>`
    = note: required for `tracing_subscriber::fmt::Layer<_, JsonFields, Format<Json>>` to implement `Into<tracing_subscriber::fmt::Layer<Registry>>`
note: required by a bound in `Handle::<L, S>::reload`
   --> ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tracing-subscriber-0.3.23/src/reload.rs:300:42
    |
300 |     pub fn reload(&self, new_value: impl Into<L>) -> Result<(), Error> {
    |                                          ^^^^^^^ required by this bound in `Handle::<L, S>::reload`
    = note: the full name for the type has been written to '.../reload_type-583941e9eeb06ed5.long-type-17110344460797409915.txt'
    = note: consider using `--verbose` to print the full type name to the console

fmt::layer() 和 fmt::layer().json() 的 N、E 两个类型参数都不同(见 fmt),是两个类型。同一个类型内部的配置也未必能改:fmt::Layer 的格式开关大多是消费 self 的 builder 方法,只有 set_ansi、set_span_events 等少数几个 &mut self 方法能在 modify 里用。

把 L 定成 Box<dyn Layer<Registry> + Send + Sync> 可以绕开类型限制,类型擦除以后什么 layer 都能换进去:

use tracing::info;
use tracing_subscriber::{Layer, Registry, fmt, prelude::*, reload};

type BoxedLayer = Box<dyn Layer<Registry> + Send + Sync>;

fn main() {
  let text: BoxedLayer = fmt::layer().with_ansi(false).without_time().boxed();
  let (layer, handle) = reload::Layer::new(text);
  tracing_subscriber::registry().with(layer).init();

  info!(n = 1, "as text");
  let json: BoxedLayer = fmt::layer().json().without_time().boxed();
  handle.reload(json).unwrap();
  info!(n = 2, "as json");
}
 INFO reload_boxed: as text n=1
{"level":"INFO","fields":{"message":"as json","n":2},"target":"reload_boxed"}

但新换进来的 layer 没有见过换之前创建的 span。fmt 的 JSON 格式要在 Extensions 里找 FormattedFields<JsonFields>,那是 JSON 层自己的 on_new_span 才会写的。在换之前就进入了的 span 里打一条日志:

  let span = info_span!("conn", peer = "10.0.0.7");
  let _g = span.enter();
  info!("as text");
  let json: BoxedLayer = fmt::layer().json().without_time().boxed();
  handle.reload(json).unwrap();
  info!("as json, inside a span created before reload");
 INFO conn{peer="10.0.0.7"}: reload_boxed_span: as text

thread 'main' (1419966) panicked at ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tracing-subscriber-0.3.23/src/fmt/format/json.rs:168:14:
Unable to find FormattedFields in extensions; this is a bug
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

release 构建也 panic,这里是 SerializableSpan::serialize 里的一个 expect。同一类问题还有 #3529(open):span 进入时开着 FmtSpan::FULL,用 modify 关掉 span 事件后退出、再打开后关闭,Timings 的进入计数没跟上,debug 构建在 on_close 的 debug_assert_eq!(entered_count, 0) 处 panic。共同的原因是:layer 把状态存在每个 span 的 Extensions 里,状态是旧配置写的,换了配置的 layer 读它时前提已经不成立。所以 reload 最适合换过滤器这类不往 span 里存东西的值;换输出层,要能接受已有 span 上的错乱。

和每层过滤一起用

把一个 Filtered(layer.with_filter(...) 的产物)整个包进 reload,再 reload 一个新的 Filtered,是 Handle::reload 文档明说不能做的事(「cannot be used with the Filtered layer; use Handle::modify instead」),对应 #1629,至今 open。

原因在 FilterId。每个 Filtered 在 on_layer 里向 Registry 要一个 FilterId,用来在每层过滤的位图里占一位(见 过滤:全局与每层)。on_layer 只在 .with(...) 组装 subscriber 时调用一次。reload 换进来的新 Filtered 没经过这一步,它的 id 还是构造时的 FilterId::disabled(),也就是 u64::MAX。

use tracing::info;
use tracing_subscriber::{Layer, filter::LevelFilter, fmt, prelude::*, reload};

fn main() {
  let make = || fmt::layer().with_ansi(false).without_time().with_filter(LevelFilter::TRACE);
  let (layer, handle) = reload::Layer::new(make());
  tracing_subscriber::registry().with(layer).init();

  info!("before reload");
  handle.reload(make()).unwrap();
  info!("after reload");
  println!("main returns");
}

debug 构建,panic 在 Filtered::id 的 debug_assert!:

 INFO reload_filtered: before reload

thread 'main' (1410933) panicked at ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tracing-subscriber-0.3.23/src/filter/layer_filters/mod.rs:633:9:
a `Filtered` layer was used, but it had no `FilterId`; was it registered with the subscriber?
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

release 构建没有这个断言,程序不 panic,但结果是错的。FilterMap::set 遇到 u64::MAX 直接跳过,is_enabled 拿 u64::MAX 当掩码,只有所有每层过滤器都放行时才为真。也就是说,这一层不再按自己的过滤器决定,而是跟着别的层走。两个输出层,console 被 reload 成 WARN,file 是 DEBUG:

  let (console_layer, handle) = reload::Layer::new(console(LevelFilter::INFO));
  let file_layer = fmt::layer()
    // ...
    .with_filter(LevelFilter::DEBUG);
  tracing_subscriber::registry().with(console_layer).with(file_layer).init();

  tick("console INFO, file DEBUG");
  handle.reload(console(LevelFilter::WARN)).unwrap();
  tick("reload console to WARN");

(console(level) 返回带 [console] 前缀 writer 的 fmt::layer().with_filter(level);tick 依次打 debug!、info!、warn!。)release 构建,跑 3 次输出相同:

[file]    DEBUG debug event
[console]  INFO info event
[file]     INFO info event
[console]  WARN warn event
[file]     WARN warn event
-- console INFO, file DEBUG
[console] DEBUG debug event
[file]    DEBUG debug event
[console]  INFO info event
[file]     INFO info event
[console]  WARN warn event
[file]     WARN warn event
-- reload console to WARN

本想把 console 调严,它反而开始输出 DEBUG:file 放行的它都放行。

还有一层原因让 reload 和每层过滤合不来:Layered 靠 downcast_raw 探测内层有没有每层过滤器(layer_has_plf),结果决定 pick_interest 怎么合并两层的 Interest。而 reload::Layer::downcast_raw 除了 NoneLayerMarker 一律返回 None,注释给的理由是「it is generally unsafe to downcast through a reload, because the pointer can be invalidated after the lock is dropped」。包在 reload 里的 Filtered 因此被当成普通 layer,Interest 的合并按全局过滤的规则走。open 的 #3345 报的「tracing::enabled! false positives when using Reload layer」就出现在这种组合下。

正确的用法有两种。一是只用 modify 改已有 Filtered 里面的过滤器,FilterId 不变:

handle.modify(|l| *l.filter_mut() = LevelFilter::WARN).unwrap();

同一个两层程序换成这一行,console 只剩 WARN:

[file]    DEBUG debug event
[file]     INFO info event
[console]  WARN warn event
[file]     WARN warn event
-- modify console filter to WARN

二是不包 Filtered,只包过滤器本身。reload::Layer 实现了 Filter(0.3.12 起,#2159),可以直接塞进 with_filter。模块文档也这么建议:「If you only want to be able to dynamically change the Filter on a layer, prefer wrapping that Filter in the reload::Layer.」

  // 只把 console 的 Filter 包进 reload,Filtered 本身不动
  let (console_filter, handle) = reload::Layer::new(LevelFilter::INFO);
  let console = fmt::layer()
    // ...
    .with_filter(console_filter);
  // file 层同上,LevelFilter::DEBUG
  tracing_subscriber::registry().with(console).with(file).init();

  tick("console INFO, file DEBUG");
  handle.reload(LevelFilter::WARN).unwrap();
  tick("reload console filter to WARN");

debug、release 两种构建输出相同:

[file]    DEBUG debug event
[console]  INFO info event
[file]     INFO info event
[console]  WARN warn event
[file]     WARN warn event
-- console INFO, file DEBUG
[file]    DEBUG debug event
[file]     INFO info event
[console]  WARN warn event
[file]     WARN warn event
-- reload console filter to WARN

Filtered 和它的 FilterId 从头到尾都是装上时那一个,被换掉的只是锁里的 LevelFilter。

动脑筋答案

no_rebuild 的完整输出,跑 3 次相同:

 INFO info event
-- A start: knob=INFO MAX_LEVEL=info
 INFO info event
-- B knob to DEBUG, no rebuild: knob=DEBUG MAX_LEVEL=info
DEBUG debug event
 INFO info event
-- C after rebuild: knob=DEBUG MAX_LEVEL=debug
DEBUG debug event
 INFO info event
-- D knob to WARN, no rebuild: knob=WARN MAX_LEVEL=debug
-- E after rebuild: knob=WARN MAX_LEVEL=warn
  • D 步输出 2 行,DEBUG 和 INFO 都出来了,尽管 filter 此时只想要 WARN。MAX_LEVEL 还是 debug,第二道门放行;两个调用点缓存的是 always,第三道门不但放行,还跳过了 enabled。KnobFilter::enabled 里那句按当前级别判断的代码根本没被调用。
  • E 步输出 0 行。重建时 register_callsite 按 WARN 给两个调用点都回答 never,MAX_LEVEL 变成 warn,debug! 和 info! 在第二道门就停了。

所以调低级别不重建,被关掉的日志照样输出,而且一直输出到下一次有人重建为止(比如新建一个 Dispatch)。

小结

  • reload::Layer<L, S> 是 Arc<RwLock<L>>,每个回调拿读锁转发;Handle 拿 Weak,subscriber 没了返回 is_dropped() 的错误。它同时实现 Layer 和 Filter。
  • Handle::modify 拿写锁改值、先放锁、再 rebuild_interest_cache,最后(开 tracing-log 时)同步 log::set_max_level;reload 就是 modify 整体赋值。
  • 必须重建,是因为调用点的 Interest 和全局 MAX_LEVEL 都缓存着旧 filter 的回答:调高级别不重建,新放行的日志过不了门;调低级别不重建,always 的调用点连 enabled 都不问,关不掉。
  • 只能换成同一个类型 L 的值。Box<dyn Layer> 能绕开,但换进来的 layer 没见过已有 span,读 Extensions 时可能 panic(JSON 格式的 FormattedFields、#3529 的 Timings)。
  • 不要 reload 一整个 Filtered:新值没有 FilterId,debug 构建 panic,release 构建跟着别的层放行(#1629)。用 modify 改 filter_mut(),或者只把 Filter 包进 reload::Layer。

💬 关联讨论

发起讨论
加载中...