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}