Skip to main content

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}