Skip to main content

tuwunel_core/
debug.rs

1//! Provides diagnostics, debugger traps, and configurable debug logging.
2//!
3//! Tracing macros retain their requested levels when extra diagnostics are
4//! enabled and otherwise demote events to `DEBUG`. The module also re-exports
5//! conditional result inspection and bounded formatting helpers for diagnostic
6//! paths.
7
8use std::{any::Any, env, panic, sync::LazyLock};
9
10use tracing::Level;
11/// Reports an annotated item's parsed syntax-tree depth and length during
12/// compilation.
13///
14/// Expansion prints the greatest indentation depth and formatted tree line
15/// count. The annotated item is returned unchanged.
16pub use tuwunel_macros::recursion_depth;
17
18/// Provides conditional inspection methods for `Result` values.
19///
20/// Debug-assertion builds invoke a closure on the contained success or error
21/// value. Other builds return the result unchanged without invoking the
22/// closure.
23pub use crate::result::DebugInspect;
24/// Provides diagnostic formatting adapters.
25///
26/// These adapters limit slice or string output for tracing fields and other
27/// debug-oriented diagnostics.
28pub use crate::utils::debug::*;
29
30/// Emits a tracing event with debug-aware level control.
31///
32/// When [`logging`] is true, the requested level is retained and `_debug =
33/// true` is attached. Otherwise the event is emitted at `DEBUG`, allowing
34/// compile-time filters to remove it.
35#[macro_export]
36#[collapse_debuginfo(yes)]
37macro_rules! debug_event {
38	( $level:expr_2021, $($x:tt)+ ) => {
39		if $crate::debug::logging() {
40			::tracing::event!( $level, _debug = true, $($x)+ )
41		} else {
42			::tracing::debug!( $($x)+ )
43		}
44	}
45}
46
47/// Emits an error event through debug-aware level control.
48///
49/// When extra debug logging is disabled, the event is demoted to `DEBUG` and
50/// may be removed by compile-time filtering.
51#[macro_export]
52macro_rules! debug_error {
53	( $($x:tt)+ ) => {
54		$crate::debug_event!(::tracing::Level::ERROR, $($x)+ )
55	}
56}
57
58/// Emits a warning event through debug-aware level control.
59///
60/// When extra debug logging is disabled, the event is demoted to `DEBUG` and
61/// may be removed by compile-time filtering.
62#[macro_export]
63macro_rules! debug_warn {
64	( $($x:tt)+ ) => {
65		$crate::debug_event!(::tracing::Level::WARN, $($x)+ )
66	}
67}
68
69/// Emits an informational event through debug-aware level control.
70///
71/// When extra debug logging is disabled, the event is demoted to `DEBUG` and
72/// may be removed by compile-time filtering.
73#[macro_export]
74macro_rules! debug_info {
75	( $($x:tt)+ ) => {
76		$crate::debug_event!(::tracing::Level::INFO, $($x)+ )
77	}
78}
79
80/// Selects `INFO` or `DEBUG` for diagnostic tracing spans.
81///
82/// The level is `INFO` when extra diagnostics retain their requested levels and
83/// `DEBUG` otherwise. Release filters can therefore elide these spans alongside
84/// the debug event macros.
85pub const INFO_SPAN_LEVEL: Level = if logging() { Level::INFO } else { Level::DEBUG };
86
87/// Reports whether the process environment suggests a `gdb` launch.
88///
89/// An `_` environment value ending in `gdb` is treated as a debugger launch.
90/// Missing or non-Unicode values produce `false`.
91pub static DEBUGGER: LazyLock<bool> =
92	LazyLock::new(|| env::var("_").unwrap_or_default().ends_with("gdb"));
93
94#[cfg_attr(debug_assertions, crate::ctor(unsafe))]
95#[cfg_attr(not(debug_assertions), expect(dead_code))]
96fn set_panic_trap() {
97	if !*DEBUGGER {
98		return;
99	}
100
101	let next = panic::take_hook();
102	panic::set_hook(Box::new(move |info| {
103		panic_handler(info, &next);
104	}));
105}
106
107/// Invokes a debugger trap before forwarding a panic to another hook.
108///
109/// If the trap returns, the supplied hook receives the original panic
110/// information. Supported targets can therefore stop in a debugger before
111/// normal panic reporting continues.
112#[cold]
113#[inline(never)]
114pub fn panic_handler(info: &panic::PanicHookInfo<'_>, next: &dyn Fn(&panic::PanicHookInfo<'_>)) {
115	trap();
116	next(info);
117}
118
119/// Raises a debugger breakpoint on supported build targets.
120///
121/// Builds with `core_intrinsics` use the compiler breakpoint intrinsic, while
122/// `x86_64` builds use `int3` as a fallback. Other targets perform no operation
123/// without intrinsic support.
124#[inline(always)]
125pub fn trap() {
126	#[cfg(core_intrinsics)]
127	//SAFETY: embeds llvm intrinsic for hardware breakpoint
128	unsafe {
129		std::intrinsics::breakpoint();
130	}
131
132	#[cfg(all(not(core_intrinsics), target_arch = "x86_64"))]
133	//SAFETY: embeds instruction for hardware breakpoint
134	unsafe {
135		std::arch::asm!("int3");
136	}
137}
138
139/// Extracts a static string slice from a boxed panic payload.
140///
141/// Payloads of other types, including owned `String` values, produce the empty
142/// string. The returned slice does not borrow storage from the box.
143#[must_use]
144pub fn panic_str(p: &Box<dyn Any + Send + 'static>) -> &'static str {
145	(**p)
146		.downcast_ref::<&str>()
147		.copied()
148		.unwrap_or_default()
149}
150
151/// Returns the compiler-generated name of an argument's statically inferred
152/// type.
153///
154/// The value is used only to infer the generic type and is not inspected. The
155/// returned name is intended for diagnostics and its format is not stable.
156#[inline(always)]
157#[must_use]
158pub fn rttype_name<T: ?Sized>(_: &T) -> &'static str { type_name::<T>() }
159
160/// Returns the compiler-generated name of a generic type.
161///
162/// The name is intended for diagnostics rather than program logic. Its exact
163/// format can change between compiler versions.
164#[inline(always)]
165#[must_use]
166pub fn type_name<T: ?Sized>() -> &'static str { std::any::type_name::<T>() }
167
168/// Returns whether extra logging calls retain their requested levels.
169///
170/// Debug-assertion builds, `tuwunel_debug_logging`, or an absent
171/// `release_max_log_level` feature enable the extra levels. When disabled,
172/// callers demote these events to `DEBUG` so compile-time filtering can remove
173/// them.
174#[must_use]
175#[inline]
176pub const fn logging() -> bool {
177	cfg!(debug_assertions)
178		|| cfg!(tuwunel_debug_logging)
179		|| !cfg!(feature = "release_max_log_level")
180}