Skip to main content

debug/
ltrace.rs

1// Copyright 2026 The Fuchsia Authors
2//
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file or at
5// https://opensource.org/licenses/MIT
6
7//! Local trace (`LOCAL_TRACE`) logging mechanism for the Zircon kernel.
8//!
9//! # Overview
10//!
11//! This crate provides a compile-time-guarded debug tracing and logging
12//! mechanism (`LOCAL_TRACE`, `ltrace!`, `ltracef!`, etc.) for Zircon kernel
13//! development. It allows developers to maintain detailed, file-scoped debug
14//! logging inside source files with zero overhead in production builds, while
15//! enabling targeted high-verbosity logs when developing or debugging.
16//!
17//! # Defining Local Trace Verbosity
18//!
19//! Unlike unconditional trace macros (`trace!`, `tracef!`, etc.), the `ltrace*`
20//! family of macros requires a `u32` constant named `LOCAL_TRACE` to be defined
21//! in the caller's scope.
22//!
23//! To use local tracing in a Rust file or module (`foo.rs`), define a
24//! file-scoped or module-scoped `u32` constant at the top of the file:
25//!
26//! ```rust
27//! // Disable local tracing by default for this file/module:
28//! const LOCAL_TRACE: u32 = 0;
29//! ```
30//!
31//! # Enabling Trace Output Locally During Debugging
32//!
33//! To locally enable verbose trace logs in a specific file while developing
34//! or debugging, edit the local `const` definition:
35//!
36//! ```rust
37//! const LOCAL_TRACE: u32 = 1; // Or a higher verbosity level like 2
38//! ```
39//!
40//! Because Rust lexical scoping prefers constants defined in the current module
41//! over those in outer modules, crates or parent modules can define a default
42//! fallback `const LOCAL_TRACE: u32 = 0;` at their root, and any specific submodule
43//! or file can override that fallback locally by defining its own `LOCAL_TRACE`.
44//!
45//! # Zero Runtime Overhead When Disabled
46//!
47//! When `LOCAL_TRACE` is `0` at compile-time, trace macros expand to
48//! `if false { ... }`. The Rust compiler (`rustc`/LLVM) type-checks and
49//! syntax-checks the arguments inside the macro block, but eliminates the dead
50//! branch during compilation. Therefore, disabled trace statements incur zero
51//! runtime CPU cost and generate zero string data in `.rodata`.
52//!
53//! # Porting from C++ (`trace.h`)
54//!
55//! When porting C++ kernel code that uses `zircon/kernel/include/trace.h`
56//! (`#define LOCAL_TRACE 0`, `LTRACE_ENTRY`, `LTRACEF`, etc.), trace logging
57//! statements should be preserved using this crate.
58//!
59//! The table below maps C++ `trace.h` macros to their Rust `ltrace` equivalents:
60//!
61//! * `TRACE_ENTRY` -> [`trace_entry!`]
62//! * `TRACE_EXIT` -> [`trace_exit!`]
63//! * `TRACE_ENTRY_OBJ` -> [`trace_entry_obj!`]
64//! * `TRACE_EXIT_OBJ` -> [`trace_exit_obj!`]
65//! * `TRACE` -> [`trace!`]
66//! * `TRACEF(str, x...)` -> [`tracef!`]
67//! * `LTRACE_ENTRY` -> [`ltrace_entry!`]
68//! * `LTRACE_EXIT` -> [`ltrace_exit!`]
69//! * `LTRACE_ENTRY_OBJ` -> [`ltrace_entry_obj!`]
70//! * `LTRACE_EXIT_OBJ` -> [`ltrace_exit_obj!`]
71//! * `LTRACE` -> [`ltrace!`]
72//! * `LTRACEF(x...)` -> [`ltracef!`]
73//! * `LTRACEF_LEVEL(lvl, x...)` -> [`ltracef_level!`]
74
75#[doc(hidden)]
76pub struct KernelConsoleWriter;
77
78impl core::fmt::Write for KernelConsoleWriter {
79    #[cfg(not(test))]
80    fn write_str(&mut self, s: &str) -> core::fmt::Result {
81        kprint::kprint!("{:s}", s);
82        Ok(())
83    }
84
85    #[cfg(test)]
86    fn write_str(&mut self, s: &str) -> core::fmt::Result {
87        std::print!("{}", s);
88        Ok(())
89    }
90}
91
92#[doc(hidden)]
93#[inline(always)]
94pub fn print_trace_args(module_path: &str, line: u32, args: core::fmt::Arguments<'_>) {
95    use core::fmt::Write;
96    let mut writer = KernelConsoleWriter;
97    let _ = write!(writer, "{}:{}: ", module_path, line);
98    let _ = writer.write_fmt(args);
99}
100
101#[doc(hidden)]
102#[inline(always)]
103pub fn print_trace_location(module_path: &str, line: u32) {
104    use core::fmt::Write;
105    let mut writer = KernelConsoleWriter;
106    let _ = writeln!(writer, "{}:{}", module_path, line);
107}
108
109#[doc(hidden)]
110#[inline(always)]
111pub fn print_trace_action(module_path: &str, line: u32, action: &str) {
112    use core::fmt::Write;
113    let mut writer = KernelConsoleWriter;
114    let _ = writeln!(writer, "{}:{}: {}", module_path, line, action);
115}
116
117#[doc(hidden)]
118#[inline(always)]
119pub fn print_trace_action_named(name: &str, action: &str) {
120    use core::fmt::Write;
121    let mut writer = KernelConsoleWriter;
122    let _ = writeln!(writer, "{}: {}", name, action);
123}
124
125#[doc(hidden)]
126#[inline(always)]
127pub fn print_trace_obj<T: ?Sized>(module_path: &str, line: u32, obj: &T, action: &str) {
128    use core::fmt::Write;
129    let ptr = obj as *const T as *const ();
130    let mut writer = KernelConsoleWriter;
131    let _ = writeln!(writer, "{}:{}: {} {:p}", module_path, line, action, ptr);
132}
133
134#[doc(hidden)]
135#[inline(always)]
136pub fn print_trace_obj_named<T: ?Sized>(name: &str, obj: &T, action: &str) {
137    use core::fmt::Write;
138    let ptr = obj as *const T as *const ();
139    let mut writer = KernelConsoleWriter;
140    let _ = writeln!(writer, "{}: {} {:p}", name, action, ptr);
141}
142
143/// Prints function/module entry information (`{module}:{line}: entry` or `{name}: entry`).
144///
145/// Equivalent to C++ `TRACE_ENTRY`.
146///
147/// # Examples
148/// ```rust
149/// trace_entry!();           // Prints "<module>:<line>: entry"
150/// trace_entry!("Foo::bar"); // Prints "Foo::bar: entry"
151/// ```
152#[macro_export]
153macro_rules! trace_entry {
154    () => {
155        $crate::ltrace::print_trace_action(core::module_path!(), core::line!(), "entry")
156    };
157    ($name:expr) => {
158        $crate::ltrace::print_trace_action_named($name, "entry")
159    };
160}
161
162/// Prints function/module exit information (`{module}:{line}: exit` or `{name}: exit`).
163///
164/// Equivalent to C++ `TRACE_EXIT`.
165#[macro_export]
166macro_rules! trace_exit {
167    () => {
168        $crate::ltrace::print_trace_action(core::module_path!(), core::line!(), "exit")
169    };
170    ($name:expr) => {
171        $crate::ltrace::print_trace_action_named($name, "exit")
172    };
173}
174
175/// Prints function entry along with the object pointer (`self` or reference).
176///
177/// Equivalent to C++ `TRACE_ENTRY_OBJ`.
178///
179/// # Examples
180/// ```rust
181/// trace_entry_obj!(self);             // Prints "<module>:<line>: entry obj <ptr>"
182/// trace_entry_obj!("Foo::bar", self); // Prints "Foo::bar: entry obj <ptr>"
183/// ```
184#[macro_export]
185macro_rules! trace_entry_obj {
186    ($obj:expr) => {
187        $crate::ltrace::print_trace_obj(core::module_path!(), core::line!(), $obj, "entry obj")
188    };
189    ($name:expr, $obj:expr) => {
190        $crate::ltrace::print_trace_obj_named($name, $obj, "entry obj")
191    };
192}
193
194/// Prints function exit along with the object pointer (`self` or reference).
195///
196/// Equivalent to C++ `TRACE_EXIT_OBJ`.
197#[macro_export]
198macro_rules! trace_exit_obj {
199    ($obj:expr) => {
200        $crate::ltrace::print_trace_obj(core::module_path!(), core::line!(), $obj, "exit obj")
201    };
202    ($name:expr, $obj:expr) => {
203        $crate::ltrace::print_trace_obj_named($name, $obj, "exit obj")
204    };
205}
206
207/// Prints the current module path and line (`{module}:{line}`).
208///
209/// Equivalent to C++ `TRACE`.
210#[macro_export]
211macro_rules! trace {
212    () => {
213        $crate::ltrace::print_trace_location(core::module_path!(), core::line!())
214    };
215}
216
217/// Formats and prints a trace message prefixed with module path and line number.
218///
219/// Equivalent to C++ `TRACEF`.
220///
221/// # Examples
222/// ```rust
223/// tracef!("initialized with value {}\n", val);
224/// ```
225#[macro_export]
226macro_rules! tracef {
227    ($($arg:tt)*) => {
228        $crate::ltrace::print_trace_args(core::module_path!(), core::line!(), core::format_args!($($arg)*))
229    };
230}
231
232/// Prints function entry (`TRACE_ENTRY`) if `LOCAL_TRACE >= 1`.
233///
234/// Equivalent to C++ `LTRACE_ENTRY`.
235#[macro_export]
236macro_rules! ltrace_entry {
237    ($($arg:tt)*) => {
238        if LOCAL_TRACE >= 1u32 {
239            $crate::trace_entry!($($arg)*);
240        }
241    };
242}
243
244/// Prints function exit (`TRACE_EXIT`) if `LOCAL_TRACE >= 1`.
245///
246/// Equivalent to C++ `LTRACE_EXIT`.
247#[macro_export]
248macro_rules! ltrace_exit {
249    ($($arg:tt)*) => {
250        if LOCAL_TRACE >= 1u32 {
251            $crate::trace_exit!($($arg)*);
252        }
253    };
254}
255
256/// Prints function entry with object pointer (`TRACE_ENTRY_OBJ`) if `LOCAL_TRACE >= 1`.
257///
258/// Equivalent to C++ `LTRACE_ENTRY_OBJ`.
259#[macro_export]
260macro_rules! ltrace_entry_obj {
261    ($($arg:tt)*) => {
262        if LOCAL_TRACE >= 1u32 {
263            $crate::trace_entry_obj!($($arg)*);
264        }
265    };
266}
267
268/// Prints function exit with object pointer (`TRACE_EXIT_OBJ`) if `LOCAL_TRACE >= 1`.
269///
270/// Equivalent to C++ `LTRACE_EXIT_OBJ`.
271#[macro_export]
272macro_rules! ltrace_exit_obj {
273    ($($arg:tt)*) => {
274        if LOCAL_TRACE >= 1u32 {
275            $crate::trace_exit_obj!($($arg)*);
276        }
277    };
278}
279
280/// Prints current module path and line (`TRACE`) if `LOCAL_TRACE >= 1`.
281///
282/// Equivalent to C++ `LTRACE`.
283#[macro_export]
284macro_rules! ltrace {
285    () => {
286        if LOCAL_TRACE >= 1u32 {
287            $crate::trace!();
288        }
289    };
290    ($($stmt:tt)+) => {
291        if LOCAL_TRACE >= 1u32 {
292            $($stmt)+
293        }
294    };
295}
296
297/// Formats and prints a trace message (`TRACEF`) if `LOCAL_TRACE >= 1`.
298///
299/// Equivalent to C++ `LTRACEF`.
300///
301/// # Examples
302/// ```rust
303/// const LOCAL_TRACE: u32 = 0; // Edit to 1 when locally debugging this file
304///
305/// fn my_func(x: i32) {
306///     ltracef!("my_func called with x = {}\n", x);
307/// }
308/// ```
309#[macro_export]
310macro_rules! ltracef {
311    ($($arg:tt)*) => {
312        if LOCAL_TRACE >= 1u32 {
313            $crate::tracef!($($arg)*);
314        }
315    };
316}
317
318/// Prints current module path and line (`TRACE`) if `LOCAL_TRACE >= level`.
319#[macro_export]
320macro_rules! ltrace_level {
321    ($level:expr) => {
322        if LOCAL_TRACE >= $level {
323            $crate::trace!();
324        }
325    };
326}
327
328/// Formats and prints a trace message (`TRACEF`) if `LOCAL_TRACE >= level`.
329///
330/// Equivalent to C++ `LTRACEF_LEVEL`.
331///
332/// # Examples
333/// ```rust
334/// const LOCAL_TRACE: u32 = 1;
335///
336/// // Printed when LOCAL_TRACE >= 1:
337/// ltracef_level!(1, "basic info: {}\n", val);
338/// // Optimized out unless LOCAL_TRACE >= 2:
339/// ltracef_level!(2, "detailed info: {}\n", val);
340/// ```
341#[macro_export]
342macro_rules! ltracef_level {
343    ($level:expr, $($arg:tt)*) => {
344        if LOCAL_TRACE >= $level {
345            $crate::tracef!($($arg)*);
346        }
347    };
348}
349
350#[cfg(test)]
351mod tests {
352
353    #[test]
354    fn test_unconditional_macros() {
355        trace!();
356        trace_entry!();
357        trace_entry!("Test::foo");
358        trace_exit!();
359        trace_exit!("Test::foo");
360        let obj = 42;
361        trace_entry_obj!(&obj);
362        trace_entry_obj!("Test::foo", &obj);
363        trace_exit_obj!(&obj);
364        trace_exit_obj!("Test::foo", &obj);
365        tracef!("hello {}\n", 123);
366    }
367
368    #[test]
369    fn test_disabled_local_trace() {
370        const LOCAL_TRACE: u32 = 0;
371        ltrace!();
372        ltrace_entry!();
373        ltrace_entry!("Test::disabled");
374        ltrace_exit!();
375        ltrace_exit!("Test::disabled");
376        let obj = 42;
377        ltrace_entry_obj!(&obj);
378        ltrace_exit_obj!(&obj);
379        ltracef!("should not print: {}\n", 123);
380        ltracef_level!(1, "should not print: {}\n", 456);
381        let mut executed = false;
382        ltrace!(executed = true);
383        assert!(!executed);
384    }
385
386    #[test]
387    fn test_enabled_local_trace() {
388        const LOCAL_TRACE: u32 = 2;
389        ltrace!();
390        ltrace_entry!();
391        ltrace_entry!("Test::enabled");
392        ltrace_exit!();
393        ltrace_exit!("Test::enabled");
394        let obj = 42;
395        ltrace_entry_obj!(&obj);
396        ltrace_exit_obj!(&obj);
397        ltracef!("enabled level 1: {}\n", 123);
398        ltracef_level!(1, "enabled level 1: {}\n", 123);
399        ltracef_level!(2, "enabled level 2: {}\n", 456);
400        ltracef_level!(3, "should not print level 3: {}\n", 789);
401        let mut executed = false;
402        ltrace!(executed = true);
403        assert!(executed);
404    }
405}