tuwunel_core/log/reload.rs
1use std::{
2 collections::HashMap,
3 sync::{Arc, Mutex},
4};
5
6use tracing_subscriber::{EnvFilter, reload};
7
8use crate::{Result, error};
9
10/// Type-erased interface to a tracing subscriber reload handle.
11///
12/// The subscriber type in `reload::Handle<L, S>` depends on preceding layers
13/// and can include unnameable `impl Trait` types. This interface hides `S` so
14/// handles can be stored as trait objects.
15pub trait ReloadHandle<L> {
16 /// Clones the filter currently installed through this handle.
17 ///
18 /// A missing value indicates that the subscriber or reload layer is no
19 /// longer available. The type-erased interface preserves the concrete
20 /// layer value.
21 fn current(&self) -> Option<L>;
22
23 /// Replaces the layer value controlled by this handle.
24 ///
25 /// Reloading affects future subscriber decisions without reconstructing the
26 /// subscriber stack. The underlying reload layer reports unavailable state.
27 fn reload(&self, new_value: L) -> Result<(), reload::Error>;
28}
29
30impl<L: Clone, S> ReloadHandle<L> for reload::Handle<L, S> {
31 fn current(&self) -> Option<L> { Self::clone_current(self) }
32
33 fn reload(&self, new_value: L) -> Result<(), reload::Error> { Self::reload(self, new_value) }
34}
35
36/// Named collection of type-erased log-filter reload handles.
37///
38/// Clones share the same synchronized handle map. Names let administrative and
39/// scoped operations target individual subscriber layers.
40#[derive(Clone)]
41pub struct LogLevelReloadHandles {
42 handles: Arc<Mutex<HandleMap>>,
43}
44
45type HandleMap = HashMap<String, Handle>;
46type Handle = Box<dyn ReloadHandle<EnvFilter> + Send + Sync>;
47
48impl LogLevelReloadHandles {
49 /// Registers or replaces a reload handle under a name.
50 ///
51 /// Later calls to `reload` and `current` address the handle by this name.
52 /// The handle remains owned by the shared collection.
53 ///
54 /// # Panics
55 ///
56 /// Panics if the shared handle map mutex is poisoned.
57 pub fn add(&self, name: &str, handle: Handle) {
58 self.handles
59 .lock()
60 .expect("locked")
61 .insert(name.into(), handle);
62 }
63
64 /// Applies a log filter to the selected named handles.
65 ///
66 /// Only handles whose names occur in the supplied slice are changed; `None`
67 /// selects no handles. Individual reload failures are logged and do not
68 /// stop other handles.
69 ///
70 /// # Panics
71 ///
72 /// Panics if the shared handle map mutex is poisoned.
73 pub fn reload(&self, new_value: &EnvFilter, names: Option<&[&str]>) -> Result {
74 self.handles
75 .lock()
76 .expect("locked")
77 .iter()
78 .filter(|(name, _)| names.is_some_and(|names| names.contains(&name.as_str())))
79 .for_each(|(_, handle)| {
80 _ = handle
81 .reload(new_value.clone())
82 .or_else(error::else_log);
83 });
84
85 Ok(())
86 }
87
88 /// Returns the current filter for a named handle.
89 ///
90 /// Missing names and unavailable reload layers both produce `None`. The
91 /// returned filter is cloned from the layer.
92 ///
93 /// # Panics
94 ///
95 /// Panics if the shared handle map mutex is poisoned.
96 #[must_use]
97 pub fn current(&self, name: &str) -> Option<EnvFilter> {
98 self.handles
99 .lock()
100 .expect("locked")
101 .get(name)
102 .map(|handle| handle.current())?
103 }
104}
105
106impl Default for LogLevelReloadHandles {
107 fn default() -> Self {
108 Self {
109 handles: Arc::new(HandleMap::new().into()),
110 }
111 }
112}