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}