├── .gitignore ├── examples ├── expected_test_outputs │ ├── test_logging.expected │ ├── test_impl_method.expected │ ├── test_custom_format.expected │ ├── test_pretty.expected │ ├── test_impl.expected │ ├── test_mod.expected │ ├── test_prefix.expected │ ├── test_thread.expected │ ├── test_mut_ref.expected │ ├── test_enable_disable.expected │ └── test_async.expected ├── example_pause.rs ├── example_pretty.rs ├── example_custom_format.rs ├── example_logging.rs ├── example_impl.rs ├── example_mod.rs ├── example_impl_method.rs ├── example_mut_ref.rs ├── example_prefix.rs ├── example_thread.rs ├── example_mod_inner_attribute_nightly.rs ├── trace_test │ └── mod.rs ├── example_enable_disable.rs └── example_async.rs ├── .cargo └── config.toml ├── .github └── workflows │ ├── lint.yml │ └── tests.yml ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── args.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /examples/expected_test_outputs/test_logging.expected: -------------------------------------------------------------------------------- 1 | I'm in foo! 2 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | t = "test --all-targets -- --nocapture" 3 | -------------------------------------------------------------------------------- /examples/expected_test_outputs/test_impl_method.expected: -------------------------------------------------------------------------------- 1 | [+] Entering bar(a = 7) 2 | [-] Exiting bar = 7 3 | -------------------------------------------------------------------------------- /examples/expected_test_outputs/test_custom_format.expected: -------------------------------------------------------------------------------- 1 | [+] Entering foo(4 and 5 {7}) 2 | [-] Exiting foo = 5 * 5 3 | -------------------------------------------------------------------------------- /examples/expected_test_outputs/test_pretty.expected: -------------------------------------------------------------------------------- 1 | [+] Entering foo(a = Foo("Foo")) 2 | [-] Exiting foo = Foo( 3 | "Foo", 4 | ) 5 | -------------------------------------------------------------------------------- /examples/expected_test_outputs/test_impl.expected: -------------------------------------------------------------------------------- 1 | [+] Entering foo(b = 2) 2 | [-] Exiting foo = 2 3 | [+] Entering bar(a = 7) 4 | [-] Exiting bar = 7 5 | -------------------------------------------------------------------------------- /examples/expected_test_outputs/test_mod.expected: -------------------------------------------------------------------------------- 1 | [+] Entering foo() 2 | I'm in foo! 3 | [-] Exiting foo = () 4 | [+] Entering bar() 5 | [-] Exiting bar = () 6 | -------------------------------------------------------------------------------- /examples/example_pause.rs: -------------------------------------------------------------------------------- 1 | use trace::trace; 2 | 3 | trace::init_depth_var!(); 4 | 5 | fn main() { 6 | foo(1); 7 | } 8 | 9 | #[trace(pause)] 10 | fn foo(a: i32) -> i32 { 11 | a 12 | } 13 | -------------------------------------------------------------------------------- /examples/expected_test_outputs/test_prefix.expected: -------------------------------------------------------------------------------- 1 | [+] Entering foo(a = 1, b = 2) 2 | I'm in foo! 3 | [ENTER] Entering bar(a = 1, b = 2) 4 | I'm in bar! 5 | [EXIT] Exiting bar = 2 6 | [-] Exiting foo = () 7 | -------------------------------------------------------------------------------- /examples/expected_test_outputs/test_thread.expected: -------------------------------------------------------------------------------- 1 | [+] Entering bar(x = 20) 2 | [+] Entering foo(x = 10) 3 | [+] Entering bar(x = 12) 4 | [-] Exiting bar = 30 5 | [-] Exiting bar = 22 6 | [-] Exiting foo = 18 7 | -------------------------------------------------------------------------------- /examples/expected_test_outputs/test_mut_ref.expected: -------------------------------------------------------------------------------- 1 | [+] Entering foo(a = 10, b = 20) 2 | [+] Entering bar(x = 30) 3 | [-] Exiting bar = () 4 | [+] Entering bar(x = 60) 5 | [-] Exiting bar = () 6 | [-] Exiting foo = () 7 | -------------------------------------------------------------------------------- /examples/expected_test_outputs/test_enable_disable.expected: -------------------------------------------------------------------------------- 1 | [+] Entering bar(a = 7) 2 | [-] Exiting bar = 7 3 | [+] Entering bar(a = 7) 4 | [-] Exiting bar = 7 5 | [+] Entering enabled_arg(a = 2) 6 | [-] Exiting enabled_arg = 5 7 | [+] Entering disabled_arg(a = 3) 8 | [-] Exiting disabled_arg = 5 9 | -------------------------------------------------------------------------------- /examples/example_pretty.rs: -------------------------------------------------------------------------------- 1 | use trace::trace; 2 | 3 | trace::init_depth_var!(); 4 | 5 | fn main() { 6 | foo(Foo("Foo".to_string())); 7 | } 8 | 9 | #[derive(Debug)] 10 | struct Foo(String); 11 | 12 | #[trace(pretty)] 13 | fn foo(a: Foo) -> Foo { 14 | a 15 | } 16 | 17 | #[cfg(test)] 18 | #[macro_use] 19 | mod trace_test; 20 | 21 | #[cfg(test)] 22 | trace_test!(test_pretty, main()); 23 | -------------------------------------------------------------------------------- /examples/example_custom_format.rs: -------------------------------------------------------------------------------- 1 | use trace::trace; 2 | 3 | trace::init_depth_var!(); 4 | 5 | fn main() { 6 | foo(5, 4); 7 | } 8 | 9 | #[trace(format_enter = "{y} and {z} {{7}}", format_exit = "{r} * {r}")] 10 | fn foo(z: u32, y: u32) -> u32 { 11 | z 12 | } 13 | 14 | #[cfg(test)] 15 | #[macro_use] 16 | mod trace_test; 17 | 18 | #[cfg(test)] 19 | trace_test!(test_custom_format, main()); 20 | -------------------------------------------------------------------------------- /examples/expected_test_outputs/test_async.expected: -------------------------------------------------------------------------------- 1 | [+] Entering squared(x = 64) 2 | [-] Exiting squared = 4096 3 | [+] Entering log(message = "something happened") 4 | [-] Exiting log = "[DEBUG] something happened" 5 | [+] Entering cubed(x = 32) 6 | [+] Entering squared(x = 32) 7 | [-] Exiting squared = 1024 8 | [+] Entering squared(x = 1024) 9 | [-] Exiting squared = 1048576 10 | [-] Exiting cubed = 1048576 11 | -------------------------------------------------------------------------------- /examples/example_logging.rs: -------------------------------------------------------------------------------- 1 | extern crate env_logger; 2 | extern crate log; 3 | 4 | use trace::trace; 5 | 6 | trace::init_depth_var!(); 7 | 8 | fn main() { 9 | env_logger::init(); 10 | foo(1, 2); 11 | } 12 | 13 | #[trace(logging)] 14 | fn foo(a: i32, b: i32) { 15 | println!("I'm in foo!"); 16 | } 17 | 18 | #[cfg(test)] 19 | #[macro_use] 20 | mod trace_test; 21 | 22 | #[cfg(test)] 23 | trace_test!(test_logging, main()); 24 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Linter 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | # Make sure CI fails on all warnings, including Clippy lints 12 | env: 13 | RUSTFLAGS: "-Dwarnings" 14 | 15 | jobs: 16 | clippy_check: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Run Clippy 21 | run: cargo clippy 22 | -------------------------------------------------------------------------------- /examples/example_impl.rs: -------------------------------------------------------------------------------- 1 | use trace::trace; 2 | 3 | trace::init_depth_var!(); 4 | 5 | fn main() { 6 | let foo = Foo; 7 | Foo::foo(2); 8 | foo.bar(7); 9 | } 10 | 11 | struct Foo; 12 | 13 | #[trace] 14 | impl Foo { 15 | fn foo(b: i32) -> i32 { 16 | b 17 | } 18 | 19 | fn bar(&self, a: i32) -> i32 { 20 | a 21 | } 22 | } 23 | 24 | #[cfg(test)] 25 | #[macro_use] 26 | mod trace_test; 27 | 28 | #[cfg(test)] 29 | trace_test!(test_impl, main()); 30 | -------------------------------------------------------------------------------- /examples/example_mod.rs: -------------------------------------------------------------------------------- 1 | use trace::trace; 2 | 3 | fn main() { 4 | foo::foo(); 5 | let foo = foo::Foo; 6 | foo.bar(); 7 | } 8 | 9 | #[trace] 10 | mod foo { 11 | pub(super) fn foo() { 12 | println!("I'm in foo!"); 13 | } 14 | 15 | pub(super) struct Foo; 16 | impl Foo { 17 | pub(super) fn bar(&self) {} 18 | } 19 | } 20 | 21 | #[cfg(test)] 22 | #[macro_use] 23 | mod trace_test; 24 | 25 | #[cfg(test)] 26 | trace_test!(test_mod, main()); 27 | -------------------------------------------------------------------------------- /examples/example_impl_method.rs: -------------------------------------------------------------------------------- 1 | use trace::trace; 2 | 3 | trace::init_depth_var!(); 4 | 5 | fn main() { 6 | let foo = Foo; 7 | Foo::foo(2); 8 | foo.bar(7); 9 | } 10 | 11 | struct Foo; 12 | impl Foo { 13 | fn foo(b: i32) -> i32 { 14 | b 15 | } 16 | 17 | #[trace] 18 | fn bar(&self, a: i32) -> i32 { 19 | a 20 | } 21 | } 22 | 23 | #[cfg(test)] 24 | #[macro_use] 25 | mod trace_test; 26 | 27 | #[cfg(test)] 28 | trace_test!(test_impl_method, main()); 29 | -------------------------------------------------------------------------------- /examples/example_mut_ref.rs: -------------------------------------------------------------------------------- 1 | use trace::trace; 2 | 3 | trace::init_depth_var!(); 4 | 5 | fn main() { 6 | let mut a = 10; 7 | let mut b = 20; 8 | foo(&mut a, &mut b); 9 | } 10 | 11 | #[trace] 12 | fn foo(a: &mut u32, b: &mut u32) { 13 | *a += 20; 14 | *b += 40; 15 | bar(a); 16 | bar(b); 17 | } 18 | 19 | #[trace] 20 | fn bar(x: &mut u32) { 21 | *x -= 5; 22 | } 23 | 24 | #[cfg(test)] 25 | #[macro_use] 26 | mod trace_test; 27 | 28 | #[cfg(test)] 29 | trace_test!(test_mut_ref, main()); 30 | -------------------------------------------------------------------------------- /examples/example_prefix.rs: -------------------------------------------------------------------------------- 1 | use trace::trace; 2 | 3 | trace::init_depth_var!(); 4 | 5 | fn main() { 6 | foo(1, 2); 7 | } 8 | 9 | #[trace] 10 | fn foo(a: i32, b: i32) { 11 | println!("I'm in foo!"); 12 | bar((a, b)); 13 | } 14 | 15 | #[trace(prefix_enter = "[ENTER]", prefix_exit = "[EXIT]")] 16 | fn bar((a, b): (i32, i32)) -> i32 { 17 | println!("I'm in bar!"); 18 | if a == 1 { 19 | 2 20 | } else { 21 | b 22 | } 23 | } 24 | 25 | #[cfg(test)] 26 | #[macro_use] 27 | mod trace_test; 28 | 29 | #[cfg(test)] 30 | trace_test!(test_prefix, main()); 31 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trace" 3 | version = "0.1.7" 4 | authors = ["Gulshan Singh "] 5 | repository = "https://github.com/gsingh93/trace" 6 | license = "MIT" 7 | readme = "README.md" 8 | description = "A procedural macro for tracing the execution of functions" 9 | edition = "2021" 10 | 11 | [lib] 12 | proc-macro = true 13 | 14 | [dependencies] 15 | proc-macro2 = "1.0" 16 | quote = "1.0" 17 | syn = { version = "1.0", features = ["full"] } 18 | 19 | [dev-dependencies] 20 | log = "0.4.17" 21 | env_logger = "0.10" 22 | gag = "1.0.0" 23 | async-trait = { version = "0.1.60" } 24 | async-std = { version = "1.12.0", features = ["attributes"]} 25 | -------------------------------------------------------------------------------- /examples/example_thread.rs: -------------------------------------------------------------------------------- 1 | use std::thread; 2 | use std::time::Duration; 3 | use trace::trace; 4 | 5 | trace::init_depth_var!(); 6 | 7 | fn main() { 8 | let handle = thread::spawn(|| { 9 | foo(10); 10 | }); 11 | 12 | bar(20); 13 | 14 | handle.join().unwrap(); 15 | } 16 | 17 | #[trace] 18 | fn foo(x: u32) -> u32 { 19 | thread::sleep(Duration::from_millis(100)); 20 | bar(x + 2) - 4 21 | } 22 | 23 | #[trace] 24 | fn bar(x: u32) -> u32 { 25 | thread::sleep(Duration::from_millis(200)); 26 | x + 10 27 | } 28 | 29 | #[cfg(test)] 30 | #[macro_use] 31 | mod trace_test; 32 | 33 | #[cfg(test)] 34 | trace_test!(test_thread, main()); 35 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | tests: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout sources 16 | uses: actions/checkout@v3 17 | 18 | - name: Install stable toolchain 19 | uses: actions-rs/toolchain@v1 20 | with: 21 | toolchain: stable 22 | 23 | - name: Build 24 | uses: actions-rs/cargo@v1 25 | with: 26 | command: build 27 | args: --verbose 28 | 29 | - name: Test 30 | uses: actions-rs/cargo@v1 31 | with: 32 | command: test 33 | args: --all-targets --verbose -- --nocapture 34 | -------------------------------------------------------------------------------- /examples/example_mod_inner_attribute_nightly.rs: -------------------------------------------------------------------------------- 1 | // This example is disabled due to the following error: 2 | // error: cannot determine resolution for the attribute macro `trace` 3 | // --> examples/example_mod_inner_attribute_nightly.rs:10:4 4 | // | 5 | // 11 | #![trace] 6 | // | ^^^^^ 7 | // | 8 | // = note: import resolution is stuck, try simplifying macro imports 9 | 10 | // #![feature(custom_inner_attributes)] 11 | // #![trace] 12 | 13 | // use trace::trace; 14 | 15 | // fn main() { 16 | // foo::foo(); 17 | // let foo = foo::Foo; 18 | // foo.bar(); 19 | // } 20 | 21 | // mod foo { 22 | // pub(super) fn foo() { 23 | // println!("I'm in foo!"); 24 | // } 25 | 26 | // pub(super) struct Foo; 27 | // impl Foo { 28 | // pub(super) fn bar(&self) {} 29 | // } 30 | // } 31 | 32 | fn main() {} 33 | -------------------------------------------------------------------------------- /examples/trace_test/mod.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! trace_test { 3 | ($test_name:ident, $expression:expr) => { 4 | #[test] 5 | fn $test_name() { 6 | use std::io::Read; 7 | 8 | let mut actual_output = String::new(); 9 | { 10 | let mut buf = gag::BufferRedirect::stdout().unwrap(); 11 | $expression; 12 | buf.read_to_string(&mut actual_output).unwrap(); 13 | } 14 | 15 | let test_filename = concat!( 16 | "examples/expected_test_outputs/", 17 | stringify!($test_name), 18 | ".expected" 19 | ); 20 | let expected_output = std::fs::read_to_string(test_filename).unwrap(); 21 | assert_eq!(actual_output, expected_output); 22 | } 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /examples/example_enable_disable.rs: -------------------------------------------------------------------------------- 1 | use std::cell::Cell; 2 | use trace::trace; 3 | 4 | thread_local! { 5 | static DEPTH: Cell = Cell::new(0); 6 | } 7 | 8 | fn main() { 9 | let foo = Foo; 10 | Foo::foo(2); 11 | foo.bar(7); 12 | 13 | let bar = Bar; 14 | Bar::foo(2); 15 | bar.bar(7); 16 | 17 | enabled_arg(2, 3); 18 | disabled_arg(3, 2); 19 | } 20 | 21 | struct Foo; 22 | 23 | #[trace(enable(bar))] 24 | impl Foo { 25 | fn foo(b: i32) -> i32 { 26 | b 27 | } 28 | 29 | fn bar(&self, a: i32) -> i32 { 30 | a 31 | } 32 | } 33 | 34 | struct Bar; 35 | 36 | #[trace(disable(foo))] 37 | impl Bar { 38 | fn foo(b: i32) -> i32 { 39 | b 40 | } 41 | 42 | fn bar(&self, a: i32) -> i32 { 43 | a 44 | } 45 | } 46 | 47 | #[trace(enable(a))] 48 | fn enabled_arg(a: i32, b: i32) -> i32 { 49 | a + b 50 | } 51 | 52 | #[trace(disable(b))] 53 | fn disabled_arg(a: i32, b: i32) -> i32 { 54 | a + b 55 | } 56 | 57 | #[cfg(test)] 58 | #[macro_use] 59 | mod trace_test; 60 | 61 | #[cfg(test)] 62 | trace_test!(test_enable_disable, main()); 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Gulshan Singh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/example_async.rs: -------------------------------------------------------------------------------- 1 | use async_std::task; 2 | use trace::trace; 3 | 4 | trace::init_depth_var!(); 5 | 6 | #[trace] 7 | async fn squared(x: i32) -> i32 { 8 | x * x 9 | } 10 | 11 | #[async_trait::async_trait] 12 | trait Log { 13 | async fn log(&self, message: &str) -> String; 14 | } 15 | 16 | #[async_trait::async_trait] 17 | trait Cubed { 18 | async fn cubed(x: i32) -> i32; 19 | } 20 | 21 | struct Logger { 22 | level: String, 23 | } 24 | 25 | struct Math {} 26 | 27 | #[trace] 28 | #[async_trait::async_trait] 29 | impl Log for Logger { 30 | async fn log(&self, message: &str) -> String { 31 | format!("[{}] {message}", self.level) 32 | } 33 | } 34 | 35 | #[trace] 36 | #[async_trait::async_trait] 37 | impl Cubed for Math { 38 | async fn cubed(x: i32) -> i32 { 39 | squared(squared(x).await).await 40 | } 41 | } 42 | 43 | fn main() { 44 | task::block_on(async { 45 | squared(64).await; 46 | let logger = Logger { 47 | level: "DEBUG".to_string(), 48 | }; 49 | logger.log("something happened").await; 50 | Math::cubed(32).await; 51 | }); 52 | } 53 | 54 | #[cfg(test)] 55 | #[macro_use] 56 | mod trace_test; 57 | 58 | #[cfg(test)] 59 | trace_test!(test_async, main()); 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | trace 2 | ----- 3 | [![Unit tests](https://github.com/gsingh93/trace/actions/workflows/tests.yml/badge.svg)](https://github.com/gsingh93/trace/actions/workflows/tests.yml) 4 | [![Latest Version](https://img.shields.io/crates/v/trace.svg)](https://crates.io/crates/trace) 5 | [![Documentation](https://docs.rs/trace/badge.svg)](https://docs.rs/trace) 6 | [![License](https://img.shields.io/github/license/gsingh93/trace)](/LICENSE) 7 | 8 | A procedural macro for tracing the execution of functions. 9 | 10 | Adding `#[trace]` to the top of functions, `mod`s, or `impl`s will insert `println!` statements at the beginning and the end of the affected functions, notifying you of when that function was entered and exited and printing the argument and return values. Useful for quickly debugging whether functions that are supposed to be called are actually called without manually inserting print statements. 11 | 12 | See the [`examples`](examples/) directory and the [documentation](https://docs.rs/trace) for more detail on how to use and configure this library. 13 | 14 | ## Installation 15 | 16 | Add it as a dependency in your `Cargo.toml` file: 17 | ```toml 18 | [dependencies] 19 | trace = "*" 20 | ``` 21 | 22 | ## Example 23 | 24 | ```rust 25 | use trace::trace; 26 | 27 | trace::init_depth_var!(); 28 | 29 | fn main() { 30 | foo(1, 2); 31 | } 32 | 33 | #[trace] 34 | fn foo(a: i32, b: i32) { 35 | println!("I'm in foo!"); 36 | bar((a, b)); 37 | } 38 | 39 | #[trace(prefix_enter="[ENTER]", prefix_exit="[EXIT]")] 40 | fn bar((a, b): (i32, i32)) -> i32 { 41 | println!("I'm in bar!"); 42 | if a == 1 { 43 | 2 44 | } else { 45 | b 46 | } 47 | } 48 | ``` 49 | 50 | Output: 51 | ``` 52 | [+] Entering foo(a = 1, b = 2) 53 | I'm in foo! 54 | [ENTER] Entering bar(a = 1, b = 2) 55 | I'm in bar! 56 | [EXIT] Exiting bar = 2 57 | [-] Exiting foo = () 58 | ``` 59 | -------------------------------------------------------------------------------- /src/args.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use syn::{self, spanned::Spanned}; 4 | 5 | pub(crate) struct Args { 6 | pub(crate) prefix_enter: String, 7 | pub(crate) prefix_exit: String, 8 | pub(crate) format_enter: Option, 9 | pub(crate) format_exit: Option, 10 | pub(crate) filter: Filter, 11 | pub(crate) pause: bool, 12 | pub(crate) pretty: bool, 13 | pub(crate) logging: bool, 14 | } 15 | 16 | pub(crate) enum Filter { 17 | None, 18 | Enable(HashSet), 19 | Disable(HashSet), 20 | } 21 | 22 | const DEFAULT_PREFIX_ENTER: &str = "[+]"; 23 | const DEFAULT_PREFIX_EXIT: &str = "[-]"; 24 | const DEFAULT_PAUSE: bool = false; 25 | const DEFAULT_PRETTY: bool = false; 26 | const DEFAULT_LOGGING: bool = false; 27 | 28 | macro_rules! try_extract_str { 29 | ($lit:expr, $meta:expr, $arg_ty:ident) => {{ 30 | match *$lit { 31 | syn::Lit::Str(ref lit_str) => Ok(Arg::$arg_ty($meta.span(), lit_str.value())), 32 | _ => Err(vec![syn::Error::new_spanned( 33 | $lit, 34 | format!("`{}` must have a string value", stringify!($arg_ty)), 35 | )]), 36 | } 37 | }}; 38 | } 39 | impl Args { 40 | pub(crate) fn from_raw_args(raw_args: syn::AttributeArgs) -> Result> { 41 | // Different types of arguments accepted by `#[trace]`; 42 | // spans are needed for friendly error reporting of duplicate arguments 43 | enum Arg { 44 | PrefixEnter(proc_macro2::Span, String), 45 | PrefixExit(proc_macro2::Span, String), 46 | Enable(proc_macro2::Span, HashSet), 47 | Disable(proc_macro2::Span, HashSet), 48 | Pause(proc_macro2::Span, bool), 49 | Pretty(proc_macro2::Span, bool), 50 | Logging(proc_macro2::Span, bool), 51 | FormatEnter(proc_macro2::Span, String), 52 | FormatExit(proc_macro2::Span, String), 53 | } 54 | 55 | // Parse arguments 56 | let args_res = raw_args.into_iter().map(|nested_meta| match nested_meta { 57 | syn::NestedMeta::Meta(ref meta) => { 58 | enum ArgName { 59 | PrefixEnter, 60 | PrefixExit, 61 | FormatEnter, 62 | FormatExit, 63 | Enable, 64 | Disable, 65 | Pause, 66 | Pretty, 67 | Logging, 68 | } 69 | 70 | let ident = &meta.path().segments.first().unwrap().ident; 71 | let arg_name = match ident.to_string().as_str() { 72 | "prefix_enter" => ArgName::PrefixEnter, 73 | "prefix_exit" => ArgName::PrefixExit, 74 | "format_enter" => ArgName::FormatEnter, 75 | "format_exit" => ArgName::FormatExit, 76 | "enable" => ArgName::Enable, 77 | "disable" => ArgName::Disable, 78 | "pause" => ArgName::Pause, 79 | "pretty" => ArgName::Pretty, 80 | "logging" => ArgName::Logging, 81 | _ => { 82 | return Err(vec![syn::Error::new_spanned( 83 | ident.clone(), 84 | format_args!("unknown attribute argument `{}`", ident), 85 | )]) 86 | } 87 | }; 88 | 89 | let prefix_enter_type_error = || { 90 | vec![syn::Error::new_spanned( 91 | ident.clone(), 92 | "`prefix_enter` requires a string value", 93 | )] 94 | }; 95 | let prefix_exit_type_error = || { 96 | vec![syn::Error::new_spanned( 97 | ident.clone(), 98 | "`prefix_exit` requires a string value", 99 | )] 100 | }; 101 | let format_enter_type_error = || { 102 | vec![syn::Error::new_spanned( 103 | ident.clone(), 104 | "`format_enter` requires a string value", 105 | )] 106 | }; 107 | let format_exit_type_error = || { 108 | vec![syn::Error::new_spanned( 109 | ident.clone(), 110 | "`format_exit` requires a string value", 111 | )] 112 | }; 113 | let enable_type_error = || { 114 | vec![syn::Error::new_spanned( 115 | ident.clone(), 116 | "`enable` requires a list of meta words", 117 | )] 118 | }; 119 | let disable_type_error = || { 120 | vec![syn::Error::new_spanned( 121 | ident.clone(), 122 | "`disable` requires a list of meta words", 123 | )] 124 | }; 125 | let pause_type_error = || { 126 | vec![syn::Error::new_spanned( 127 | ident.clone(), 128 | "`pause` must be a meta word", 129 | )] 130 | }; 131 | let pretty_type_error = || { 132 | vec![syn::Error::new_spanned( 133 | ident.clone(), 134 | "`pretty` must be a meta word", 135 | )] 136 | }; 137 | let logging_type_error = || { 138 | vec![syn::Error::new_spanned( 139 | ident.clone(), 140 | "`logging` must be a meta word", 141 | )] 142 | }; 143 | 144 | match *meta { 145 | syn::Meta::Path(_) => match arg_name { 146 | ArgName::Pause => Ok(Arg::Pause(meta.span(), true)), 147 | ArgName::Pretty => Ok(Arg::Pretty(meta.span(), true)), 148 | ArgName::Logging => Ok(Arg::Logging(meta.span(), true)), 149 | ArgName::PrefixEnter => Err(prefix_enter_type_error()), 150 | ArgName::PrefixExit => Err(prefix_exit_type_error()), 151 | ArgName::Enable => Err(enable_type_error()), 152 | ArgName::Disable => Err(disable_type_error()), 153 | ArgName::FormatEnter => Err(format_enter_type_error()), 154 | ArgName::FormatExit => Err(format_exit_type_error()), 155 | }, 156 | syn::Meta::List(syn::MetaList { ref nested, .. }) => match arg_name { 157 | ArgName::Enable => { 158 | let mut idents = HashSet::new(); 159 | let mut other_nested_meta_errors = Vec::new(); 160 | 161 | nested.iter().for_each(|nested_meta| match *nested_meta { 162 | syn::NestedMeta::Meta(syn::Meta::Path(ref path)) 163 | if path.segments.len() == 1 => 164 | { 165 | idents.insert(path.segments.first().unwrap().ident.clone()); 166 | } 167 | _ => other_nested_meta_errors.push(syn::Error::new_spanned( 168 | nested_meta, 169 | "`enable` must contain single ident paths only", 170 | )), 171 | }); 172 | 173 | if other_nested_meta_errors.is_empty() { 174 | Ok(Arg::Enable(meta.span(), idents)) 175 | } else { 176 | Err(other_nested_meta_errors) 177 | } 178 | } 179 | ArgName::Disable => { 180 | let mut idents = HashSet::new(); 181 | let mut other_nested_meta_errors = Vec::new(); 182 | 183 | nested.iter().for_each(|nested_meta| match *nested_meta { 184 | syn::NestedMeta::Meta(syn::Meta::Path(ref path)) 185 | if path.segments.len() == 1 => 186 | { 187 | idents.insert(path.segments.first().unwrap().ident.clone()); 188 | } 189 | _ => other_nested_meta_errors.push(syn::Error::new_spanned( 190 | nested_meta, 191 | "`disable` must contain single ident paths only", 192 | )), 193 | }); 194 | 195 | if other_nested_meta_errors.is_empty() { 196 | Ok(Arg::Disable(meta.span(), idents)) 197 | } else { 198 | Err(other_nested_meta_errors) 199 | } 200 | } 201 | 202 | ArgName::PrefixEnter => Err(prefix_enter_type_error()), 203 | ArgName::PrefixExit => Err(prefix_exit_type_error()), 204 | ArgName::Pause => Err(pause_type_error()), 205 | ArgName::Pretty => Err(pretty_type_error()), 206 | ArgName::Logging => Err(logging_type_error()), 207 | ArgName::FormatEnter => Err(format_enter_type_error()), 208 | ArgName::FormatExit => Err(format_exit_type_error()), 209 | }, 210 | syn::Meta::NameValue(syn::MetaNameValue { ref lit, .. }) => match arg_name { 211 | ArgName::PrefixEnter => try_extract_str!(lit, meta, PrefixEnter), 212 | ArgName::PrefixExit => try_extract_str!(lit, meta, PrefixExit), 213 | ArgName::FormatEnter => try_extract_str!(lit, meta, FormatEnter), 214 | ArgName::FormatExit => try_extract_str!(lit, meta, FormatExit), 215 | ArgName::Enable => Err(enable_type_error()), 216 | ArgName::Disable => Err(disable_type_error()), 217 | ArgName::Pause => Err(pause_type_error()), 218 | ArgName::Pretty => Err(pretty_type_error()), 219 | ArgName::Logging => Err(logging_type_error()), 220 | }, 221 | } 222 | } 223 | syn::NestedMeta::Lit(_) => Err(vec![syn::Error::new_spanned( 224 | nested_meta, 225 | "literal attribute not allowed", 226 | )]), 227 | }); 228 | 229 | let mut prefix_enter_args = vec![]; 230 | let mut prefix_exit_args = vec![]; 231 | let mut format_enter_args = vec![]; 232 | let mut format_exit_args = vec![]; 233 | let mut enable_args = vec![]; 234 | let mut disable_args = vec![]; 235 | let mut pause_args = vec![]; 236 | let mut pretty_args = vec![]; 237 | let mut logging_args = vec![]; 238 | let mut errors = vec![]; 239 | 240 | // Group arguments of the same type and errors 241 | for arg_res in args_res { 242 | match arg_res { 243 | Ok(arg) => match arg { 244 | Arg::PrefixEnter(span, s) => prefix_enter_args.push((span, s)), 245 | Arg::PrefixExit(span, s) => prefix_exit_args.push((span, s)), 246 | Arg::Enable(span, idents) => enable_args.push((span, idents)), 247 | Arg::Disable(span, idents) => disable_args.push((span, idents)), 248 | Arg::Pause(span, b) => pause_args.push((span, b)), 249 | Arg::Pretty(span, b) => pretty_args.push((span, b)), 250 | Arg::Logging(span, b) => logging_args.push((span, b)), 251 | Arg::FormatEnter(span, s) => format_enter_args.push((span, s)), 252 | Arg::FormatExit(span, s) => format_exit_args.push((span, s)), 253 | }, 254 | Err(es) => errors.extend(es), 255 | } 256 | } 257 | 258 | // Report duplicates 259 | if prefix_enter_args.len() >= 2 { 260 | errors.extend( 261 | prefix_enter_args 262 | .iter() 263 | .map(|(span, _)| syn::Error::new(*span, "duplicate `prefix_enter`")), 264 | ); 265 | } 266 | if prefix_exit_args.len() >= 2 { 267 | errors.extend( 268 | prefix_exit_args 269 | .iter() 270 | .map(|(span, _)| syn::Error::new(*span, "duplicate `prefix_exit`")), 271 | ); 272 | } 273 | if format_enter_args.len() >= 2 { 274 | errors.extend( 275 | format_enter_args 276 | .iter() 277 | .map(|(span, _)| syn::Error::new(*span, "duplicate `format_enter`")), 278 | ); 279 | } 280 | if format_exit_args.len() >= 2 { 281 | errors.extend( 282 | format_exit_args 283 | .iter() 284 | .map(|(span, _)| syn::Error::new(*span, "duplicate `format_exit`")), 285 | ); 286 | } 287 | if enable_args.len() >= 2 { 288 | errors.extend( 289 | enable_args 290 | .iter() 291 | .map(|(span, _)| syn::Error::new(*span, "duplicate `enable`")), 292 | ); 293 | } 294 | if disable_args.len() >= 2 { 295 | errors.extend( 296 | disable_args 297 | .iter() 298 | .map(|(span, _)| syn::Error::new(*span, "duplicate `disable`")), 299 | ); 300 | } 301 | if pause_args.len() >= 2 { 302 | errors.extend( 303 | pause_args 304 | .iter() 305 | .map(|(span, _)| syn::Error::new(*span, "duplicate `pause`")), 306 | ); 307 | } 308 | if pretty_args.len() >= 2 { 309 | errors.extend( 310 | pretty_args 311 | .iter() 312 | .map(|(span, _)| syn::Error::new(*span, "duplicate `pretty`")), 313 | ); 314 | } 315 | if logging_args.len() >= 2 { 316 | errors.extend( 317 | logging_args 318 | .iter() 319 | .map(|(span, _)| syn::Error::new(*span, "duplicate `logging`")), 320 | ); 321 | } 322 | 323 | // Report the presence of mutually exclusive arguments 324 | if enable_args.len() == 1 && disable_args.len() == 1 { 325 | errors.push(syn::Error::new( 326 | enable_args[0].0, 327 | "cannot have both `enable` and `disable`", 328 | )); 329 | errors.push(syn::Error::new( 330 | disable_args[0].0, 331 | "cannot have both `enable` and `disable`", 332 | )); 333 | } 334 | if pretty_args.len() == 1 && format_enter_args.len() == 1 { 335 | errors.push(syn::Error::new( 336 | pretty_args[0].0, 337 | "cannot have both `pretty` and `format_enter`", 338 | )); 339 | errors.push(syn::Error::new( 340 | format_enter_args[0].0, 341 | "cannot have both `pretty` and `format_enter`", 342 | )); 343 | } 344 | if pretty_args.len() == 1 && format_exit_args.len() == 1 { 345 | errors.push(syn::Error::new( 346 | pretty_args[0].0, 347 | "cannot have both `pretty` and `format_exit`", 348 | )); 349 | errors.push(syn::Error::new( 350 | format_exit_args[0].0, 351 | "cannot have both `pretty` and `format_exit`", 352 | )); 353 | } 354 | 355 | if errors.is_empty() { 356 | macro_rules! first_no_span { 357 | ($iterable:expr) => { 358 | $iterable.into_iter().next().map(|(_, elem)| elem) 359 | }; 360 | } 361 | 362 | let prefix_enter = first_no_span!(prefix_enter_args) 363 | .unwrap_or_else(|| DEFAULT_PREFIX_ENTER.to_owned()); 364 | let prefix_exit = 365 | first_no_span!(prefix_exit_args).unwrap_or_else(|| DEFAULT_PREFIX_EXIT.to_owned()); 366 | let format_enter = first_no_span!(format_enter_args); 367 | let format_exit = first_no_span!(format_exit_args); 368 | let filter = match (first_no_span!(enable_args), first_no_span!(disable_args)) { 369 | (None, None) => Filter::None, 370 | (Some(idents), None) => Filter::Enable(idents), 371 | (None, Some(idents)) => Filter::Disable(idents), 372 | (Some(_), Some(_)) => unreachable!(), 373 | }; 374 | let pause = first_no_span!(pause_args).unwrap_or(DEFAULT_PAUSE); 375 | let pretty = first_no_span!(pretty_args).unwrap_or(DEFAULT_PRETTY); 376 | let logging = first_no_span!(logging_args).unwrap_or(DEFAULT_LOGGING); 377 | 378 | Ok(Self { 379 | prefix_enter, 380 | prefix_exit, 381 | filter, 382 | pause, 383 | pretty, 384 | logging, 385 | format_enter, 386 | format_exit, 387 | }) 388 | } else { 389 | Err(errors) 390 | } 391 | } 392 | } 393 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A procedural macro for tracing the execution of functions. 2 | //! 3 | //! Adding `#[trace]` to the top of any function will insert `println!` statements at the beginning 4 | //! and the end of that function, notifying you of when that function was entered and exited and 5 | //! printing the argument and return values. This is useful for quickly debugging whether functions 6 | //! that are supposed to be called are actually called without manually inserting print statements. 7 | //! 8 | //! Note that this macro requires all arguments to the function and the return value to have types 9 | //! that implement `Debug`. You can disable the printing of certain arguments if necessary. 10 | //! 11 | //! You can also add `#[trace]` to `impl`s and `mod`s to enable tracing for all functions in the 12 | //! `impl` or `mod`. If you use `#[trace]` on a `mod` or `impl` as well as on a method or function 13 | //! inside one of those elements, then only the outermost `#[trace]` is used. 14 | //! 15 | //! `#[trace]` takes a few optional arguments that configure things like the prefixes to use, 16 | //! enabling/disabling particular arguments or functions, and more. See the 17 | //! [documentation](macro@trace) for details. 18 | //! 19 | //! ## Example 20 | //! 21 | //! See the examples in `examples/`. You can run the following example with 22 | //! `cargo run --example example_prefix`. 23 | //! ``` 24 | //! use trace::trace; 25 | //! 26 | //! trace::init_depth_var!(); 27 | //! 28 | //! fn main() { 29 | //! foo(1, 2); 30 | //! } 31 | //! 32 | //! #[trace] 33 | //! fn foo(a: i32, b: i32) { 34 | //! println!("I'm in foo!"); 35 | //! bar((a, b)); 36 | //! } 37 | //! 38 | //! #[trace(prefix_enter="[ENTER]", prefix_exit="[EXIT]")] 39 | //! fn bar((a, b): (i32, i32)) -> i32 { 40 | //! println!("I'm in bar!"); 41 | //! if a == 1 { 42 | //! 2 43 | //! } else { 44 | //! b 45 | //! } 46 | //! } 47 | //! ``` 48 | //! 49 | //! Output: 50 | //! ```text 51 | //! [+] Entering foo(a = 1, b = 2) 52 | //! I'm in foo! 53 | //! [ENTER] Entering bar(a = 1, b = 2) 54 | //! I'm in bar! 55 | //! [EXIT] Exiting bar = 2 56 | //! [-] Exiting foo = () 57 | //! ``` 58 | //! 59 | //! Note the convenience [`trace::init_depth_var!()`](macro@init_depth_var) macro which declares and 60 | //! initializes the thread-local `DEPTH` variable that is used for indenting the output. Calling 61 | //! `trace::init_depth_var!()` is equivalent to writing: 62 | //! ``` 63 | //! use std::cell::Cell; 64 | //! 65 | //! thread_local! { 66 | //! static DEPTH: Cell = Cell::new(0); 67 | //! } 68 | //! ``` 69 | //! 70 | //! The only time it can be omitted is when `#[trace]` is applied to `mod`s as it's defined for you 71 | //! automatically (see `examples/example_mod.rs`). Note that the `DEPTH` variable isn't shared 72 | //! between `mod`s, so indentation won't be perfect when tracing functions in multiple `mod`s. Also 73 | //! note that using trace as an inner attribute (`#![trace]`) is not supported at this time. 74 | 75 | mod args; 76 | 77 | use std::{iter::Peekable, str::Chars}; 78 | 79 | use proc_macro2::{Span, TokenStream}; 80 | use quote::{quote, ToTokens}; 81 | use syn::{ 82 | parse::{Parse, Parser}, 83 | parse_quote, 84 | }; 85 | 86 | /// A convenience macro for declaring the `DEPTH` variable used for indenting the output 87 | /// 88 | /// Calling this macro is equivalent to: 89 | /// ``` 90 | /// use std::cell::Cell; 91 | /// 92 | /// thread_local! { 93 | /// static DEPTH: Cell = Cell::new(0); 94 | /// } 95 | /// ``` 96 | /// 97 | /// It is required to declare a `DEPTH` variable unless using `#[trace]` on a `mod`, in which case 98 | /// the variable is declared for you. 99 | #[proc_macro] 100 | pub fn init_depth_var(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 101 | let output = if input.is_empty() { 102 | quote! { 103 | ::std::thread_local! { 104 | static DEPTH: ::std::cell::Cell = ::std::cell::Cell::new(0); 105 | } 106 | } 107 | } else { 108 | let input2 = proc_macro2::TokenStream::from(input); 109 | syn::Error::new_spanned(input2, "`init_depth_var` takes no arguments").to_compile_error() 110 | }; 111 | 112 | output.into() 113 | } 114 | 115 | /// Enables tracing the execution of functions 116 | /// 117 | /// It supports the following optional arguments (see the `examples` folder for examples of using 118 | /// each of these): 119 | /// 120 | /// - `prefix_enter` - The prefix of the `println!` statement when a function is entered. Defaults 121 | /// to `[+]`. 122 | /// 123 | /// - `prefix_exit` - The prefix of the `println!` statement when a function is exited. Defaults to 124 | /// `[-]`. 125 | /// 126 | /// - `enable` - When applied to a `mod` or `impl`, `enable` takes a list of function names to 127 | /// print, not printing any functions that are not part of this list. All functions are enabled by 128 | /// default. When applied to an `impl` method or a function, `enable` takes a list of arguments to 129 | /// print, not printing any arguments that are not part of the list. All arguments are enabled by 130 | /// default. 131 | /// 132 | /// - `disable` - When applied to a `mod` or `impl`, `disable` takes a list of function names to not 133 | /// print, printing all other functions in the `mod` or `impl`. No functions are disabled by 134 | /// default. When applied to an `impl` method or a function, `disable` takes a list of arguments to 135 | /// not print, printing all other arguments. No arguments are disabled by default. 136 | /// 137 | /// - `pause` - When given as an argument to `#[trace]`, execution is paused after each line of 138 | /// tracing output until enter is pressed. This allows you to trace through a program step by 139 | /// step. Disabled by default. 140 | /// 141 | /// - `pretty` - Pretty print the output (use `{:#?}` instead of `{:?}`). Disabled by default. 142 | /// 143 | /// - `logging` - Use `log::trace!` from the `log` crate instead of `println`. Disabled by default. 144 | /// 145 | /// - `format_enter` - The format (anything after the prefix) of `println!` statements when a function 146 | /// is entered. Allows parameter interpolation like: 147 | /// ```rust 148 | /// #[trace(format_enter = "i is {i}")] 149 | /// fn foo(i: i32) { 150 | /// println!("foo") 151 | /// } 152 | /// ``` 153 | /// Interpolation follows the same rules as `format!()` besides for the fact that there is no pretty printing, 154 | /// that is anything interpolated will be debug formatted. Disabled by default. 155 | 156 | /// - `format_exit` - The format (anything after the prefix) of `println!` statements when a function 157 | /// is exited. To interpolate the return value use `{r}`: 158 | /// ```rust 159 | /// #[trace(format_exit = "returning {r}")] 160 | /// fn foo() -> i32 { 161 | /// 1 162 | /// } 163 | /// ``` 164 | /// Otherwise formatting follows the same rules as `format_enter`. Disabled by default. 165 | /// 166 | /// Note that `enable` and `disable` cannot be used together, and doing so will result in an error. 167 | /// 168 | /// Further note that `format_enter` or `format_exit` cannot be used together with with `pretty`, and doing so will result in an error. 169 | #[proc_macro_attribute] 170 | pub fn trace( 171 | args: proc_macro::TokenStream, 172 | input: proc_macro::TokenStream, 173 | ) -> proc_macro::TokenStream { 174 | let raw_args = syn::parse_macro_input!(args as syn::AttributeArgs); 175 | let args = match args::Args::from_raw_args(raw_args) { 176 | Ok(args) => args, 177 | Err(errors) => { 178 | return errors 179 | .iter() 180 | .map(syn::Error::to_compile_error) 181 | .collect::() 182 | .into() 183 | } 184 | }; 185 | 186 | let output = if let Ok(item) = syn::Item::parse.parse(input.clone()) { 187 | expand_item(&args, item) 188 | } else if let Ok(impl_item) = syn::ImplItem::parse.parse(input.clone()) { 189 | expand_impl_item(&args, impl_item) 190 | } else { 191 | let input2 = proc_macro2::TokenStream::from(input); 192 | syn::Error::new_spanned(input2, "expected one of: `fn`, `impl`, `mod`").to_compile_error() 193 | }; 194 | 195 | output.into() 196 | } 197 | 198 | #[derive(Clone, Copy)] 199 | enum AttrApplied { 200 | Directly, 201 | Indirectly, 202 | } 203 | 204 | fn expand_item(args: &args::Args, mut item: syn::Item) -> proc_macro2::TokenStream { 205 | transform_item(args, AttrApplied::Directly, &mut item); 206 | 207 | match item { 208 | syn::Item::Fn(_) | syn::Item::Mod(_) | syn::Item::Impl(_) => item.into_token_stream(), 209 | _ => syn::Error::new_spanned(item, "#[trace] is not supported for this item") 210 | .to_compile_error(), 211 | } 212 | } 213 | 214 | fn expand_impl_item(args: &args::Args, mut impl_item: syn::ImplItem) -> proc_macro2::TokenStream { 215 | transform_impl_item(args, AttrApplied::Directly, &mut impl_item); 216 | 217 | match impl_item { 218 | syn::ImplItem::Method(_) => impl_item.into_token_stream(), 219 | _ => syn::Error::new_spanned(impl_item, "#[trace] is not supported for this impl item") 220 | .to_compile_error(), 221 | } 222 | } 223 | 224 | fn transform_item(args: &args::Args, attr_applied: AttrApplied, item: &mut syn::Item) { 225 | match *item { 226 | syn::Item::Fn(ref mut item_fn) => transform_fn(args, attr_applied, item_fn), 227 | syn::Item::Mod(ref mut item_mod) => transform_mod(args, attr_applied, item_mod), 228 | syn::Item::Impl(ref mut item_impl) => transform_impl(args, attr_applied, item_impl), 229 | _ => (), 230 | } 231 | } 232 | 233 | fn transform_fn(args: &args::Args, attr_applied: AttrApplied, item_fn: &mut syn::ItemFn) { 234 | item_fn.block = Box::new(construct_traced_block( 235 | args, 236 | attr_applied, 237 | &item_fn.sig, 238 | &item_fn.block, 239 | )); 240 | } 241 | 242 | fn transform_mod(args: &args::Args, attr_applied: AttrApplied, item_mod: &mut syn::ItemMod) { 243 | assert!( 244 | (item_mod.content.is_some() && item_mod.semi.is_none()) 245 | || (item_mod.content.is_none() && item_mod.semi.is_some()) 246 | ); 247 | 248 | if item_mod.semi.is_some() { 249 | unimplemented!(); 250 | } 251 | 252 | if let Some((_, items)) = item_mod.content.as_mut() { 253 | items.iter_mut().for_each(|item| { 254 | if let AttrApplied::Directly = attr_applied { 255 | match *item { 256 | syn::Item::Fn(syn::ItemFn { 257 | sig: syn::Signature { ref ident, .. }, 258 | .. 259 | }) 260 | | syn::Item::Mod(syn::ItemMod { ref ident, .. }) => match args.filter { 261 | args::Filter::Enable(ref idents) if !idents.contains(ident) => { 262 | return; 263 | } 264 | args::Filter::Disable(ref idents) if idents.contains(ident) => { 265 | return; 266 | } 267 | _ => (), 268 | }, 269 | _ => (), 270 | } 271 | } 272 | 273 | transform_item(args, AttrApplied::Indirectly, item); 274 | }); 275 | 276 | items.insert( 277 | 0, 278 | parse_quote! { 279 | ::std::thread_local! { 280 | static DEPTH: ::std::cell::Cell = ::std::cell::Cell::new(0); 281 | } 282 | }, 283 | ); 284 | } 285 | } 286 | 287 | fn transform_impl(args: &args::Args, attr_applied: AttrApplied, item_impl: &mut syn::ItemImpl) { 288 | item_impl.items.iter_mut().for_each(|impl_item| { 289 | if let syn::ImplItem::Method(ref mut impl_item_method) = *impl_item { 290 | if let AttrApplied::Directly = attr_applied { 291 | let ident = &impl_item_method.sig.ident; 292 | 293 | match args.filter { 294 | args::Filter::Enable(ref idents) if !idents.contains(ident) => { 295 | return; 296 | } 297 | args::Filter::Disable(ref idents) if idents.contains(ident) => { 298 | return; 299 | } 300 | _ => (), 301 | } 302 | } 303 | 304 | impl_item_method.block = construct_traced_block( 305 | args, 306 | AttrApplied::Indirectly, 307 | &impl_item_method.sig, 308 | &impl_item_method.block, 309 | ); 310 | } 311 | }); 312 | } 313 | 314 | fn transform_impl_item( 315 | args: &args::Args, 316 | attr_applied: AttrApplied, 317 | impl_item: &mut syn::ImplItem, 318 | ) { 319 | // Will probably add more cases in the future 320 | #[allow(clippy::single_match)] 321 | match *impl_item { 322 | syn::ImplItem::Method(ref mut impl_item_method) => { 323 | transform_method(args, attr_applied, impl_item_method) 324 | } 325 | _ => (), 326 | } 327 | } 328 | 329 | fn transform_method( 330 | args: &args::Args, 331 | attr_applied: AttrApplied, 332 | impl_item_method: &mut syn::ImplItemMethod, 333 | ) { 334 | impl_item_method.block = construct_traced_block( 335 | args, 336 | attr_applied, 337 | &impl_item_method.sig, 338 | &impl_item_method.block, 339 | ); 340 | } 341 | 342 | fn construct_traced_block( 343 | args: &args::Args, 344 | attr_applied: AttrApplied, 345 | sig: &syn::Signature, 346 | original_block: &syn::Block, 347 | ) -> syn::Block { 348 | let arg_idents = extract_arg_idents(args, attr_applied, sig) 349 | .iter() 350 | .map(|ident| ident.to_token_stream()) 351 | .collect(); 352 | let (enter_format, arg_idents) = if let Some(fmt_str) = &args.format_enter { 353 | parse_fmt_str(fmt_str, arg_idents) 354 | } else { 355 | ( 356 | Ok(arg_idents 357 | .iter() 358 | .map(|arg_ident| format!("{} = {{:?}}", arg_ident)) 359 | .collect::>() 360 | .join(", ")), 361 | arg_idents, 362 | ) 363 | }; 364 | // we set set exit val to be a vector with one element which is Ident called r 365 | // this means that the format parser can indentify when then return value should be interprolated 366 | // so if we want to use a different symbol to denote return value interpolation we just need to change the symbol in the following quote 367 | // ie: `let exit_val = vec![quote!(return_value)];` if we wanted to use return_value to denote return value interpolation 368 | let exit_val = vec![quote!(r)]; 369 | let (exit_format, exit_val) = if let Some(fmt_str) = &args.format_exit { 370 | parse_fmt_str(fmt_str, exit_val) 371 | } else if args.pretty { 372 | (Ok("{:#?}".to_string()), exit_val) 373 | } else { 374 | (Ok("{:?}".to_string()), exit_val) 375 | }; 376 | let should_interpolate = !exit_val.is_empty(); 377 | let entering_format = format!( 378 | "{{:depth$}}{} Entering {}({})", 379 | args.prefix_enter, 380 | sig.ident, 381 | match enter_format { 382 | Ok(ok) => ok, 383 | Err(e) => { 384 | let error = e.into_compile_error(); 385 | return parse_quote! {{#error}}; 386 | } 387 | } 388 | ); 389 | let exiting_format = format!( 390 | "{{:depth$}}{} Exiting {} = {}", 391 | args.prefix_exit, 392 | sig.ident, 393 | match exit_format { 394 | Ok(ok) => ok, 395 | Err(e) => { 396 | let error = e.into_compile_error(); 397 | return parse_quote! {{#error}}; 398 | } 399 | } 400 | ); 401 | 402 | let pause_stmt = if args.pause { 403 | quote! {{ 404 | use std::io::{self, BufRead}; 405 | let stdin = io::stdin(); 406 | stdin.lock().lines().next(); 407 | }} 408 | } else { 409 | quote!() 410 | }; 411 | 412 | let printer = if args.logging { 413 | quote! { log::trace! } 414 | } else { 415 | quote! { println! } 416 | }; 417 | let print_exit = if should_interpolate { 418 | quote! {{#printer(#exiting_format, "",fn_return_value, depth = DEPTH.with(|d| d.get()));}} 419 | } else { 420 | quote!(#printer(#exiting_format, "", depth = DEPTH.with(|d| d.get()));) 421 | }; 422 | parse_quote! {{ 423 | #printer(#entering_format, "", #(#arg_idents,)* depth = DEPTH.with(|d| d.get())); 424 | #pause_stmt 425 | DEPTH.with(|d| d.set(d.get() + 1)); 426 | let fn_return_value = #original_block; 427 | DEPTH.with(|d| d.set(d.get() - 1)); 428 | #print_exit 429 | #pause_stmt 430 | fn_return_value 431 | }} 432 | } 433 | 434 | fn parse_fmt_str( 435 | fmt_str: &str, 436 | mut arg_idents: Vec, 437 | ) -> (Result, Vec) { 438 | let mut fixed_format_str = String::new(); 439 | let mut kept_arg_idents = Vec::new(); 440 | let mut fmt_iter = fmt_str.chars().peekable(); 441 | while let Some(fmt_char) = fmt_iter.next() { 442 | match fmt_char { 443 | '{' => { 444 | if let Some('{') = fmt_iter.peek() { 445 | fixed_format_str.push_str("{{"); 446 | fmt_iter.next(); 447 | } else { 448 | match parse_interpolated(&mut fmt_iter, &mut arg_idents, &mut kept_arg_idents) { 449 | Ok(interpolated) => fixed_format_str.push_str(&interpolated), 450 | Err(e) => return (Err(e), kept_arg_idents), 451 | } 452 | } 453 | } 454 | '}' => { 455 | if fmt_iter.next() != Some('}') { 456 | return (Err(syn::Error::new( 457 | Span::call_site(), 458 | "invalid format string: unmatched `}` found\nif you intended to print `}`, you can escape it using `}}`" 459 | )), kept_arg_idents); 460 | } 461 | 462 | fixed_format_str.push_str("}}") 463 | } 464 | _ => fixed_format_str.push(fmt_char), 465 | } 466 | } 467 | (Ok(fixed_format_str), kept_arg_idents) 468 | } 469 | 470 | fn fix_interpolated( 471 | last_char: char, 472 | ident: String, 473 | arg_idents: &mut Vec, 474 | kept_arg_idents: &mut Vec, 475 | ) -> Result { 476 | if last_char != '}' { 477 | return Err(syn::Error::new( 478 | Span::call_site(), 479 | "invalid format string: expected `'}}'` but string was terminated\nif you intended to print `{{`, you can escape it using `{{`.", 480 | )); 481 | } 482 | let predicate = |arg_ident: &TokenStream| arg_ident.to_string() == ident; 483 | if let Some(index) = kept_arg_idents.iter().position(predicate) { 484 | Ok(format!("{{{}}}", index + 1)) 485 | } else if let Some(index) = arg_idents.iter().position(predicate) { 486 | kept_arg_idents.push(arg_idents.remove(index)); 487 | Ok(format!("{{{}}}", kept_arg_idents.len())) 488 | } else { 489 | Err(syn::Error::new( 490 | Span::call_site(), 491 | // TODO: better error message 492 | format!("cannot find `{ident}` in this scope."), 493 | )) 494 | } 495 | } 496 | 497 | fn parse_interpolated( 498 | fmt_iter: &mut Peekable, 499 | arg_idents: &mut Vec, 500 | kept_arg_idents: &mut Vec, 501 | ) -> Result { 502 | let mut last_char = ' '; 503 | let mut ident = String::new(); 504 | while let Some(ident_char) = fmt_iter.next() { 505 | match ident_char { 506 | '}' => { 507 | last_char = '}'; 508 | break; 509 | } 510 | _ => { 511 | last_char = ident_char; 512 | if !ident_char.is_whitespace() { 513 | ident.push(ident_char); 514 | } else { 515 | skip_whitespace_and_check(fmt_iter, &mut last_char, ident_char)?; 516 | } 517 | } 518 | } 519 | } 520 | fix_interpolated(last_char, ident, arg_idents, kept_arg_idents) 521 | } 522 | 523 | fn skip_whitespace_and_check( 524 | fmt_iter: &mut Peekable, 525 | last_char: &mut char, 526 | ident_char: char, 527 | ) -> Result<(), syn::Error> { 528 | for blank_char in fmt_iter.by_ref() { 529 | match blank_char { 530 | '}' => { 531 | *last_char = '}'; 532 | break; 533 | } 534 | c if c.is_whitespace() => { 535 | *last_char = ident_char; 536 | } 537 | _ => { 538 | return Err(syn::Error::new( 539 | Span::call_site(), 540 | format!("invalid format string: expected `'}}'`, found `'{blank_char}'`\nif you intended to print `{{`, you can escape it using `{{`."), 541 | )) 542 | } 543 | } 544 | } 545 | Ok(()) 546 | } 547 | 548 | fn extract_arg_idents( 549 | args: &args::Args, 550 | attr_applied: AttrApplied, 551 | sig: &syn::Signature, 552 | ) -> Vec { 553 | fn process_pat( 554 | args: &args::Args, 555 | attr_applied: AttrApplied, 556 | pat: &syn::Pat, 557 | arg_idents: &mut Vec, 558 | ) { 559 | match *pat { 560 | syn::Pat::Ident(ref pat_ident) => { 561 | let ident = &pat_ident.ident; 562 | 563 | if let AttrApplied::Directly = attr_applied { 564 | match args.filter { 565 | args::Filter::Enable(ref idents) if !idents.contains(ident) => { 566 | return; 567 | } 568 | args::Filter::Disable(ref idents) if idents.contains(ident) => { 569 | return; 570 | } 571 | _ => (), 572 | } 573 | } 574 | 575 | arg_idents.push(ident.clone()); 576 | } 577 | syn::Pat::Tuple(ref pat_tuple) => { 578 | pat_tuple.elems.iter().for_each(|pat| { 579 | process_pat(args, attr_applied, pat, arg_idents); 580 | }); 581 | } 582 | _ => unimplemented!(), 583 | } 584 | } 585 | 586 | let mut arg_idents = vec![]; 587 | 588 | for input in &sig.inputs { 589 | match input { 590 | syn::FnArg::Receiver(_) => (), // ignore `self` 591 | syn::FnArg::Typed(arg_typed) => { 592 | process_pat(args, attr_applied, &arg_typed.pat, &mut arg_idents); 593 | } 594 | } 595 | } 596 | 597 | arg_idents 598 | } 599 | --------------------------------------------------------------------------------