Skip to main content

tracing_reload/
lib.rs

1//! Wrapper for a [`Layer`](tracing_subscriber::layer::Layer) to allow it to be dynamically modified
2//! at runtime. This is based on [`tracing_subscriber::reload`].
3//!
4//! This crate provides a [`Layer`] type implementing the [`Layer`
5//! trait](tracing_subscriber::layer::Layer) which wraps another type implementing the same trait.
6//! The inner value is pinned on construction so its memory address is stable. Reloads publish a new
7//! value and retire the old one instead of mutating in place, which together with pinning keeps
8//! pointers returned from [`downcast_raw`] valid. The one-time [`on_layer`] setup hook is forwarded
9//! through a transient mutable reference during construction, before any such pointer is handed
10//! out.
11//!
12//! The inner value is reloaded by *replacing* it through [`Handle::reload_with`] (or
13//! [`Handle::reload`]); it is never mutated in place. Each reload publishes a new value and retires
14//! the previous one, which is kept alive so that pointers previously handed out by [`downcast_raw`]
15//! stay valid and all events in a given span can be routed to the same layer. Retired versions are
16//! reclaimed when the subscriber is dropped.
17//!
18//! ## Why to use this instead of `tracing_subscriber::reload::Layer`
19//!
20//! This layer is built so that [`downcast_raw`] is possible through this layer.  The price is
21//! leaked memory (and possibly other resources) on every reload.
22//!
23//! That happens because [`downcast_raw`] returns a raw pointer and if we return a pointer to the
24//! inner layer, we need to guarantee that the pointer is valid for as long as someone has a
25//! reference to the layer. The method has no safety doc comment in [`tracing-subscriber`] and from
26//! other usage it is only clear that it should be valid for as long as the `&self` argument. But we
27//! can never know when that reference goes out of scope, we can only be sure it is safe to clean up
28//! once this [`Layer`] is dropped. Until then we have to keep around all layers we have downcasted
29//! to.
30//!
31//! Furthermore, this layer tracks which version of the inner layer created a given span and routes
32//! `on_event`, `on_enter`, `on_exit`, and `on_close` to that layer even if a newer one is
33//! available. This is because some layers depend on information stored in span extensions which is
34//! added on span creation and may panic when reload happens at a wrong time.
35//!
36//! # Examples
37//!
38//! ```rust
39//! # use tracing::info;
40//! use tracing_reload::{Layer, Handle};
41//! use tracing_subscriber::{filter, fmt, prelude::*};
42//! let filter = filter::LevelFilter::WARN;
43//! let (filter, reload_handle) = Layer::new(filter);
44//! tracing_subscriber::registry()
45//!   .with(filter)
46//!   .with(fmt::Layer::default())
47//!   .init();
48//! #
49//! # // specifying the Registry type is required
50//! # let _: &Handle<filter::LevelFilter, tracing_subscriber::Registry> = &reload_handle;
51//! #
52//! info!("This will be ignored");
53//! reload_handle.reload(filter::LevelFilter::INFO);
54//! info!("This will be logged");
55//! ```
56//!
57//! Reloading a [`Filtered`](tracing_subscriber::filter::Filtered) layer:
58//!
59//! ```rust
60//! # use tracing::info;
61//! use tracing_subscriber::{filter, fmt, reload, prelude::*};
62//! let filtered_layer = fmt::Layer::default().with_filter(filter::LevelFilter::WARN);
63//! let (filtered_layer, reload_handle) = reload::Layer::new(filtered_layer);
64//! #
65//! # // specifying the Registry type is required
66//! # let _: &reload::Handle<filter::Filtered<fmt::Layer<tracing_subscriber::Registry>,
67//! # filter::LevelFilter, tracing_subscriber::Registry>,tracing_subscriber::Registry>
68//! # = &reload_handle;
69//! #
70//! tracing_subscriber::registry()
71//!   .with(filtered_layer)
72//!   .init();
73//! info!("This will be ignored");
74//! reload_handle.modify(|layer| *layer.filter_mut() = filter::LevelFilter::INFO);
75//! info!("This will be logged");
76//! ```
77//!
78//! [`on_layer`]: tracing_subscriber::layer::Layer::on_layer
79//! [`downcast_raw`]: tracing_subscriber::layer::Layer::downcast_raw
80//! [`Filter` trait]: tracing_subscriber::layer::Filter
81//! [`Layer` trait]: tracing_subscriber::layer::Layer
82//! [`tracing-subscriber`]: tracing_subscriber
83
84#![cfg_attr(docsrs, feature(doc_cfg))]
85#![warn(clippy::pedantic)]
86#![warn(
87    clippy::allow_attributes,
88    clippy::allow_attributes_without_reason,
89    clippy::as_conversions,
90    clippy::expect_used,
91    clippy::future_not_send,
92    clippy::indexing_slicing,
93    clippy::panic,
94    clippy::panic_in_result_fn,
95    clippy::string_slice,
96    clippy::todo,
97    clippy::unreachable,
98    clippy::unwrap_used
99)]
100#![expect(
101    clippy::missing_panics_doc,
102    clippy::missing_errors_doc,
103    reason = "These need some more cleanup and documenting."
104)]
105
106use std::{
107    cell::Cell,
108    collections::HashMap,
109    marker::PhantomData,
110    num::NonZeroUsize,
111    pin::Pin,
112    sync::{Arc, Mutex, OnceLock, RwLock, Weak, atomic::AtomicBool},
113};
114
115use subscriber_cache::SubscriberCache;
116use tracing::{Subscriber, dispatcher::WeakDispatch};
117use tracing_core::{callsite, span};
118
119macro_rules! try_lock {
120    ($lock:expr) => {
121        try_lock!($lock, else return)
122    };
123    ($lock:expr, else $els:expr) => {
124        if let ::core::result::Result::Ok(l) = $lock {
125            l
126        } else if std::thread::panicking() {
127            $els
128        } else {
129            panic!("lock poisoned")
130        }
131    };
132}
133
134mod error;
135mod layer;
136mod subscriber;
137mod subscriber_cache;
138
139pub use error::Error;
140pub use subscriber::ReloadSubscriber;
141use tracing_subscriber::{
142    filter::FilterId,
143    layer::{Context, SubscriberExt},
144    registry::LookupSpan,
145};
146
147const MAX_FILTER_COUNT: NonZeroUsize = NonZeroUsize::new(16).unwrap();
148
149/// Determines how new spans are routed when layers are reloaded.
150#[derive(Debug, Clone, Copy)]
151pub enum ReloadRouting {
152    /// Route new spans to the most recently reloaded layer.
153    NewSpanToLatestLayer,
154
155    /// Route new spans to the same layer that handled their parent span.
156    NewSpanToParentLayer,
157}
158
159/// Wraps another type implementing [`tracing_subscriber::layer::Layer`] or
160/// [`tracing_subscriber::layer::Filter`], allowing it to be modified dynamically at runtime.
161///
162/// The inner value is pinned at construction time so its address is stable.  This makes it safe to
163/// return raw pointers from
164/// [`Layer::downcast_raw`](tracing_subscriber::layer::Layer::downcast_raw).
165#[derive(Debug)]
166pub struct Layer<L, S> {
167    inner: Arc<Shared<L>>,
168    span_routing: ReloadRouting,
169    subscriber_cache: Mutex<SubscriberCache>,
170    _s: PhantomData<fn(S)>,
171}
172
173/// Allows modifying the state of an associated reloadable layer.
174#[derive(Debug)]
175pub struct Handle<L, S> {
176    inner: Weak<Shared<L>>,
177    _s: PhantomData<fn(S)>,
178}
179
180/// Shared storage behind a [`Layer`] and its [`Handle`].
181#[derive(Debug)]
182struct Shared<L> {
183    dispatch: OnceLock<WeakDispatch>,
184    dispatch_leaked: AtomicBool,
185    layers: RwLock<Vec<Pin<Box<L>>>>,
186    span_map: RwLock<HashMap<span::Id, usize>>,
187    filters: Mutex<ReloadableFilters>,
188}
189
190#[derive(Debug, Clone)]
191struct ReloadableFilters {
192    used: Vec<FilterId>,
193    free: Vec<FilterId>,
194    backup: FilterId,
195}
196impl ReloadableFilters {
197    fn new() -> Self {
198        // We'll get another later. For now, we just get any filter ID we can.
199        let backup = tracing_subscriber::registry().register_filter();
200        Self {
201            used: Vec::new(),
202            free: Vec::new(),
203            backup,
204        }
205    }
206}
207
208// ===== impl Layer =====
209
210impl<L, S> Layer<L, S> {
211    /// Wraps the given value, returning a [`Layer`] and a [`Handle`] that allows the inner value to
212    /// be replaced at runtime.
213    pub fn new(inner: L) -> (Self, Handle<L, S>) {
214        Self::new_with_routing(inner, ReloadRouting::NewSpanToLatestLayer)
215    }
216
217    /// Wraps the given value with a specific span routing strategy, returning a [`Layer`] and a
218    /// [`Handle`] that allows the inner value to be replaced at runtime.
219    pub fn new_with_routing(inner: L, span_routing: ReloadRouting) -> (Self, Handle<L, S>) {
220        let this = Self {
221            inner: Arc::new(Shared {
222                dispatch: OnceLock::new(),
223                dispatch_leaked: AtomicBool::new(false),
224                layers: RwLock::new(vec![Box::pin(inner)]),
225                span_map: RwLock::new(HashMap::new()),
226                filters: Mutex::new(ReloadableFilters::new()),
227            }),
228            span_routing,
229            subscriber_cache: Mutex::new(SubscriberCache::default()),
230            _s: PhantomData,
231        };
232        let handle = this.handle();
233        (this, handle)
234    }
235
236    /// Returns a [`Handle`] that can be used to replace the wrapped [`Layer`].
237    pub fn handle(&self) -> Handle<L, S> {
238        Handle {
239            inner: Arc::downgrade(&self.inner),
240            _s: PhantomData,
241        }
242    }
243
244    fn with_span<T>(&self, span: &span::Id, mapper: impl FnOnce(&L) -> T) -> Option<T> {
245        let index = *try_lock!(self.inner.span_map.read(), else return None).get(span)?;
246        let layers = try_lock!(self.inner.layers.read(), else return None);
247        let layer = layers.get(index)?;
248        Some(mapper(layer))
249    }
250
251    fn with_ctx<F, T>(&self, f: F) -> Option<T>
252    where
253        S: Subscriber,
254        F: FnOnce(Context<'_, subscriber::ReloadSubscriber<S>>) -> T,
255        T: 'static,
256    {
257        type DynFnOnce<'a, S, T> =
258            dyn FnOnce(Context<'_, subscriber::ReloadSubscriber<S>>) -> T + 'a;
259        struct ContextStealer<S: 'static, T>(
260            Cell<Option<Box<DynFnOnce<'static, S, T>>>>,
261            Cell<Option<T>>,
262        );
263
264        impl<S: Subscriber, T> tracing_subscriber::Layer<subscriber::ReloadSubscriber<S>>
265            for ContextStealer<S, T>
266        where
267            T: 'static,
268        {
269            // There's no reason we use this method specifically, we just need one that uses
270            // `Context` and preferably one which takes arguments that are easy to construct.
271            fn on_follows_from(
272                &self,
273                _span: &span::Id,
274                _follows: &span::Id,
275                ctx: Context<'_, subscriber::ReloadSubscriber<S>>,
276            ) {
277                #[expect(
278                    clippy::unwrap_used,
279                    reason = "We set `self.0` before calling this in this function."
280                )]
281                self.1.set(Some(self.0.replace(None).unwrap()(ctx)));
282            }
283        }
284
285        self.inner.with_subscriber(|subscriber| {
286            // SAFETY
287            // We do this just so that `ReloadSubscriber: 'static`. We need to make sure that no one
288            // stashes the static reference to use it after this method finishes.
289            let inner_subscriber = unsafe { std::mem::transmute::<&'_ S, &'static S>(subscriber) };
290            let subscriber = subscriber::ReloadSubscriber::new(
291                inner_subscriber,
292                try_lock!(self.inner.filters.lock(), else return None).clone(),
293            );
294
295            // TODO is there a better way to make `F` into `F + 'static` without boxing?
296            let boxed: Box<DynFnOnce<'_, S, T>> = Box::new(f);
297
298            // SAFETY
299            // The whole `'static` bound exists just so we can use the function inside the `Layer`
300            // which has to be `'static` same as the `Subscriber` itself. We call the
301            // closure will outlive this function and is called inside this function and
302            // then it's gone afterwards so this is safe to do and call.
303            let transmuted = unsafe {
304                std::mem::transmute::<Box<DynFnOnce<'_, S, T>>, Box<DynFnOnce<'static, S, T>>>(
305                    boxed,
306                )
307            };
308
309            let layered = subscriber.with(ContextStealer::<S, T>(
310                Cell::new(Some(transmuted)),
311                Cell::new(None),
312            ));
313            // The span IDs are ignored, just pass anything inside. `Registry` doesn't do anything
314            // with this and the only layer is the one that we use to steal the context. We make
315            // sure not to propagate this first call to the inner subscriber.
316            layered
317                .record_follows_from(&span::Id::from_u64(u64::MAX), &span::Id::from_u64(u64::MAX));
318
319            #[expect(
320                clippy::unwrap_used,
321                reason = "We registered tis layer and its second field is set unconditionally in \
322                          the `on_follows_from` implementation."
323            )]
324            let result = layered
325                .downcast_ref::<ContextStealer<S, T>>()
326                .unwrap()
327                .1
328                .replace(None)
329                .unwrap();
330
331            // Check invariants after the unsafe usage. The reverse drop order at the end of the
332            // function would handle this the same way, but let's be explicit.
333            drop(layered);
334
335            Some(result)
336        })?
337    }
338}
339
340// ===== impl Handle =====
341
342impl<L, S> Handle<L, S> {
343    /// Replaces the active layer with a new one.
344    ///
345    /// The closure provides a reference to the active layer so if the layer implements `Clone`, it
346    /// can be constructed that way and then modified in place.
347    pub fn reload_with(&self, f: impl FnOnce(&L) -> L) -> Result<(), Error>
348    where
349        L: tracing_subscriber::Layer<ReloadSubscriber<S>>,
350        S: Subscriber,
351    {
352        let inner = self.inner.upgrade().ok_or_else(Error::subscriber_gone)?;
353
354        inner
355            .with_subscriber(|subscriber| {
356                // SAFETY
357                // We do this just so that `ReloadSubscriber: 'static`. We need to make sure that no
358                // one stashes the static reference to use it after this method finishes.
359                let inner_subscriber =
360                    unsafe { std::mem::transmute::<&'_ S, &'static S>(subscriber) };
361
362                let mut layers =
363                    try_lock!(inner.layers.write(), else return Err(Error::poisoned()));
364                #[expect(clippy::unwrap_used, reason = "There is always at least one layer.")]
365                let mut next = f(layers.last().unwrap());
366
367                let mut filters =
368                    try_lock!(inner.filters.lock(), else return Err(Error::poisoned()));
369
370                let mut subscriber = ReloadSubscriber::new(inner_subscriber, filters.clone());
371
372                next.on_layer(&mut subscriber);
373                next.on_register_dispatch(
374                    &inner
375                        .dispatch
376                        .get()
377                        .ok_or(Error::subscriber_not_initialized())?
378                        .upgrade()
379                        .ok_or(Error::subscriber_gone())?,
380                );
381
382                *filters = subscriber.into_filters();
383
384                layers.push(Box::pin(next));
385                drop(layers);
386
387                callsite::rebuild_interest_cache();
388
389                #[cfg(feature = "tracing-log")]
390                tracing_log::log::set_max_level(tracing_log::AsLog::as_log(
391                    &tracing_subscriber::filter::LevelFilter::current(),
392                ));
393
394                Ok(())
395            })
396            .ok_or(Error::subscriber_not_initialized())?
397    }
398
399    /// Replaces the active layer with a new one.
400    ///
401    /// Depending on configuration, new spans and events may still be routed to the older layer.
402    pub fn reload(&self, layer: impl Into<L>) -> Result<(), Error>
403    where
404        L: tracing_subscriber::Layer<ReloadSubscriber<S>>,
405        S: Subscriber,
406    {
407        self.reload_with(|_| layer.into())
408    }
409
410    /// Invokes a closure with a borrowed reference to the current layer slice, returning the
411    /// result.
412    pub fn with_current<T>(&self, f: impl FnOnce(&L) -> T) -> Result<T, Error> {
413        let inner = self.inner.upgrade().ok_or_else(Error::subscriber_gone)?;
414        let layers = try_lock!(inner.layers.read(), else return Err(Error::poisoned()));
415        #[expect(clippy::unwrap_used, reason = "There is always at least one layer.")]
416        Ok(f(layers.last().unwrap()))
417    }
418}
419
420impl<L, S> Clone for Handle<L, S> {
421    fn clone(&self) -> Self {
422        Handle {
423            inner: self.inner.clone(),
424            _s: PhantomData,
425        }
426    }
427}
428
429impl<L> Shared<L> {
430    fn with_subscriber<S, T, F>(&self, fun: F) -> Option<T>
431    where
432        F: FnOnce(&S) -> T,
433        S: 'static,
434    {
435        let dispatch = self.dispatch.get()?.upgrade()?;
436
437        let subscriber = dispatch.downcast_ref::<S>()?;
438        let result = fun(subscriber);
439
440        Some(result)
441    }
442}
443
444#[cfg(test)]
445mod tests {
446    use tracing::Level;
447    use tracing_mock::{expect, layer as mock_layer};
448    use tracing_subscriber::{filter::LevelFilter, registry::Registry};
449
450    use super::*;
451
452    #[test]
453    fn modify_while_subscriber_active() {
454        fn emit_event(level: tracing::Level, id: u8) {
455            match level {
456                tracing::Level::ERROR => tracing::error!("event {}", id),
457                tracing::Level::WARN => tracing::warn!("event {}", id),
458                tracing::Level::INFO => tracing::info!("event {}", id),
459                tracing::Level::DEBUG => tracing::debug!("event {}", id),
460                tracing::Level::TRACE => tracing::trace!("event {}", id),
461            }
462        }
463
464        let (mock, handle) = mock_layer::mock()
465            .event(
466                expect::event()
467                    .at_level(Level::ERROR)
468                    .with_fields(expect::msg("event 0")),
469            )
470            .event(
471                expect::event()
472                    .at_level(Level::WARN)
473                    .with_fields(expect::msg("event 1")),
474            )
475            .event(
476                expect::event()
477                    .at_level(Level::ERROR)
478                    .with_fields(expect::msg("event 3")),
479            )
480            .only()
481            .run_with_handle();
482
483        let (reload, reload_handle) = Layer::new(LevelFilter::TRACE);
484        let subscriber = Registry::default().with(reload).with(mock);
485
486        tracing::subscriber::with_default(subscriber, || {
487            emit_event(tracing::Level::ERROR, 0);
488            emit_event(tracing::Level::WARN, 1);
489
490            reload_handle.reload_with(|_| LevelFilter::ERROR).unwrap();
491
492            emit_event(tracing::Level::INFO, 2);
493            emit_event(tracing::Level::ERROR, 3);
494        });
495
496        handle.assert_finished();
497    }
498
499    #[test]
500    fn with_current_borrows() {
501        let (reload, handle) = Layer::new(LevelFilter::INFO);
502        let subscriber = Registry::default().with(reload);
503        tracing::subscriber::with_default(subscriber, || {
504            let val = handle
505                .with_current(|layer| layer.into_level().unwrap())
506                .unwrap();
507            assert_eq!(val, Level::INFO);
508
509            handle.reload(LevelFilter::ERROR).unwrap();
510
511            let val = handle
512                .with_current(|layer| layer.into_level().unwrap())
513                .unwrap();
514            assert_eq!(val, Level::ERROR);
515        });
516    }
517
518    #[test]
519    fn handle_errors_after_subscriber_dropped() {
520        let (reload, handle) = Layer::new(LevelFilter::OFF);
521        let subscriber = Registry::default().with(reload);
522        tracing::subscriber::with_default(subscriber, || {});
523
524        assert!(handle.with_current(|_| ()).is_err());
525        assert!(handle.reload_with(|_current| LevelFilter::OFF).is_err());
526    }
527
528    #[test]
529    fn downcast_survives_reload_with_snapshot_semantics() {
530        let (layer, handle) = Layer::new(LevelFilter::TRACE);
531
532        // We need to do initialization of the layer
533        let dispatch = tracing::Dispatch::new(tracing_subscriber::registry());
534        <Layer<LevelFilter, _> as tracing_subscriber::Layer<Registry>>::on_register_dispatch(
535            &layer, &dispatch,
536        );
537
538        let before = unsafe {
539            <Layer<LevelFilter, _> as tracing_subscriber::Layer<Registry>>::downcast_raw(
540                &layer,
541                core::any::TypeId::of::<LevelFilter>(),
542            )
543        }
544        .unwrap()
545        .cast::<LevelFilter>();
546        assert_eq!(unsafe { *before }, LevelFilter::TRACE);
547
548        handle.reload(LevelFilter::DEBUG).unwrap();
549
550        assert_eq!(unsafe { *before }, LevelFilter::TRACE);
551
552        let after = unsafe {
553            <Layer<LevelFilter, _> as tracing_subscriber::Layer<Registry>>::downcast_raw(
554                &layer,
555                core::any::TypeId::of::<LevelFilter>(),
556            )
557        }
558        .unwrap()
559        .cast::<LevelFilter>();
560        assert_eq!(unsafe { *after }, LevelFilter::DEBUG);
561
562        // This should still be valid even after getting the new pointer.
563        assert_eq!(unsafe { *before }, LevelFilter::TRACE);
564    }
565
566    #[test]
567    fn parent_layer_routing() {
568        let parent = expect::span()
569            .named("parent")
570            .at_level(tracing::Level::INFO);
571        let child = expect::span().named("child").at_level(tracing::Level::INFO);
572
573        let (mock1, handle1) = mock_layer::mock()
574            .new_span(parent.clone())
575            .enter(&parent)
576            .new_span(child.clone())
577            .enter(&child)
578            .exit(&child)
579            .exit(&parent)
580            .only()
581            .run_with_handle();
582
583        let (mock2, handle2) = mock_layer::mock().only().run_with_handle();
584
585        let (reload, reload_handle) =
586            Layer::new_with_routing(mock1, ReloadRouting::NewSpanToParentLayer);
587        let subscriber = Registry::default().with(reload);
588
589        tracing::subscriber::with_default(subscriber, || {
590            let _parent = tracing::info_span!("parent").entered();
591
592            reload_handle.reload(mock2).unwrap();
593
594            _ = tracing::info_span!("child").entered();
595        });
596
597        handle2.assert_finished();
598        handle1.assert_finished();
599    }
600
601    #[test]
602    fn current_layer_routing() {
603        let parent = expect::span()
604            .named("parent")
605            .at_level(tracing::Level::INFO);
606        let child = expect::span().named("child").at_level(tracing::Level::INFO);
607
608        let (mock1, handle1) = mock_layer::mock()
609            .new_span(parent.clone())
610            .enter(&parent)
611            .exit(&parent)
612            .only()
613            .run_with_handle();
614
615        let (mock2, handle2) = mock_layer::mock()
616            .new_span(child.clone())
617            .enter(&child)
618            .exit(&child)
619            .only()
620            .run_with_handle();
621
622        let (reload, reload_handle) =
623            Layer::new_with_routing(mock1, ReloadRouting::NewSpanToLatestLayer);
624        let subscriber = Registry::default().with(reload);
625
626        tracing::subscriber::with_default(subscriber, || {
627            let _parent = tracing::info_span!("parent").entered();
628
629            reload_handle.reload(mock2).unwrap();
630
631            _ = tracing::info_span!("child").entered();
632        });
633
634        handle2.assert_finished();
635        handle1.assert_finished();
636    }
637}