├── .gitignore ├── .rustfmt.toml ├── .travis.yml ├── examples ├── timestamps.rs ├── fibonacci.rs └── result.rs ├── Cargo.toml ├── LICENSE-MIT ├── tests ├── test_async.rs ├── test_logger.rs └── first.rs ├── README.md ├── LICENSE-APACHE └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | .idea 5 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 135 2 | use_field_init_shorthand = true 3 | use_try_shorthand = true 4 | use_small_heuristics = "Max" 5 | newline_style = "unix" 6 | edition = "2018" -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: false 3 | rust: 4 | - stable 5 | - 1.32.0 6 | - 1.39.0 7 | - beta 8 | - nightly 9 | script: 10 | - cargo build --verbose 11 | - if [ ${TRAVIS_RUST_VERSION} != "1.32.0" ]; 12 | then 13 | cargo test --features async_test --verbose; 14 | else 15 | cargo test --verbose; 16 | fi 17 | - cargo test --examples 18 | 19 | before_script: 20 | - if [ ${TRAVIS_RUST_VERSION} == "stable" ]; then 21 | rustup component add clippy-preview rustfmt; 22 | cargo fmt --all -- --check; 23 | cargo clippy -- -D clippy::all; 24 | fi 25 | 26 | notifications: 27 | email: 28 | on_success: never 29 | -------------------------------------------------------------------------------- /examples/timestamps.rs: -------------------------------------------------------------------------------- 1 | use log::LevelFilter; 2 | use log_derive::logfn; 3 | use simplelog::{Config, TermLogger, TerminalMode}; 4 | use std::thread::sleep; 5 | use std::time::Duration; 6 | 7 | #[logfn(DEBUG)] 8 | fn func1() -> i32 { 9 | sleep(Duration::from_millis(10)); 10 | log::info!("func1"); 11 | 5 12 | } 13 | 14 | #[logfn(DEBUG, log_ts = true)] 15 | fn func2() { 16 | sleep(Duration::from_millis(1500)); 17 | log::info!("func2"); 18 | } 19 | 20 | #[logfn(DEBUG, log_ts = false)] 21 | fn func3() { 22 | log::info!("func3"); 23 | } 24 | 25 | fn main() { 26 | TermLogger::init(LevelFilter::Trace, Config::default(), TerminalMode::default()).unwrap(); 27 | func1(); 28 | func2(); 29 | func3(); 30 | } 31 | 32 | #[cfg(test)] 33 | #[test] 34 | fn test_main() { 35 | main(); 36 | } 37 | -------------------------------------------------------------------------------- /examples/fibonacci.rs: -------------------------------------------------------------------------------- 1 | use log::LevelFilter; 2 | use log_derive::*; 3 | use simplelog::{Config, TermLogger, TerminalMode}; 4 | 5 | // #[logfn(INFO, fmt = "fibonacci() -> {:?}", err = "Error", ok = "Trace", Warn)] 6 | // fn fibonacci(n: u32) -> std::result::Result { 7 | // match n { 8 | // 0 => Ok(1), 9 | // 1 => Ok(1), 10 | // 3 => Err(3), 11 | // _ => Ok(fibonacci(n - 1)? + fibonacci(n - 2)?), 12 | // } 13 | // } 14 | 15 | #[logfn(INFO, fmt = "fibonacci() -> {}")] 16 | #[logfn_inputs(Trace)] 17 | fn fibonacci(n: u32) -> u32 { 18 | match n { 19 | 0 => 1, 20 | 1 => 1, 21 | _ => fibonacci(n - 1) + fibonacci(n - 2), 22 | } 23 | } 24 | 25 | fn main() { 26 | TermLogger::init(LevelFilter::Trace, Config::default(), TerminalMode::default()).unwrap(); 27 | let _ = fibonacci(5); 28 | } 29 | 30 | #[cfg(test)] 31 | #[test] 32 | fn test_main() { 33 | main(); 34 | } 35 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "log-derive" 3 | version = "0.4.1" 4 | license = "MIT/Apache-2.0" 5 | authors = ["Elichai "] 6 | repository = "https://github.com/elichai/log-derive" 7 | readme = "README.md" 8 | edition = "2018" 9 | description = "Procedural Macros for logging the result and inputs of a function" 10 | categories = ["development-tools::debugging"] 11 | keywords = ["log", "macro", "derive", "logging", "function"] 12 | include = [ 13 | "src/*.rs", 14 | "Cargo.toml", 15 | ] 16 | 17 | [features] 18 | async_test = ["futures-executor"] 19 | 20 | [dependencies] 21 | darling = "0.10.0" 22 | proc-macro2 = "1.0.3" 23 | #syn = { version = "0.15", features = ["full", "extra-traits"] } # -> For development 24 | syn = { version = "1.0.5", features = ["full"] } 25 | quote = "1.0.2" 26 | futures-executor = { version = "0.3.5", optional = true } # Can't have optional dev-dependency 27 | 28 | [dev-dependencies] 29 | simplelog = "0.8" 30 | log = "0.4" 31 | 32 | [badges] 33 | travis-ci = { repository = "elichai/log-derive" } 34 | 35 | [lib] 36 | proc-macro = true -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Elichai Turkel 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 | -------------------------------------------------------------------------------- /tests/test_async.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "async_test")] 2 | 3 | mod test_logger; 4 | 5 | use crate::test_logger::THREAD_LOGGER; 6 | use log::Level; 7 | use log_derive::logfn; 8 | 9 | #[logfn(INFO)] 10 | async fn async_function(ok: bool) -> Result<&'static str, &'static str> { 11 | if ok { 12 | return Ok("async Ok"); 13 | } else { 14 | return Err("async Err"); 15 | } 16 | } 17 | 18 | #[test] 19 | fn async_works() { 20 | test_logger::init(); 21 | 22 | futures_executor::block_on(async { 23 | assert_eq!(async_function(true).await, Ok("async Ok")); 24 | THREAD_LOGGER.assert_last_log("async_function() => \"async Ok\"", Level::Info, 9); 25 | assert_eq!(async_function(false).await, Err("async Err")); 26 | THREAD_LOGGER.assert_last_log("async_function() => \"async Err\"", Level::Info, 9); 27 | assert!(THREAD_LOGGER.is_empty()) 28 | }) 29 | } 30 | 31 | #[test] 32 | fn async_works2() { 33 | test_logger::init(); 34 | let block = futures_executor::block_on; 35 | 36 | assert_eq!(block(async_function(true)), Ok("async Ok")); 37 | THREAD_LOGGER.assert_last_log("async_function() => \"async Ok\"", Level::Info, 9); 38 | assert_eq!(block(async_function(false)), Err("async Err")); 39 | THREAD_LOGGER.assert_last_log("async_function() => \"async Err\"", Level::Info, 9); 40 | assert!(THREAD_LOGGER.is_empty()) 41 | } 42 | -------------------------------------------------------------------------------- /examples/result.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use log::LevelFilter; 4 | use log_derive::logfn; 5 | use simplelog::{Config, TermLogger, TerminalMode}; 6 | 7 | struct DivisibleBy3Error(u32); 8 | struct DivisibleBy5Error(u32); 9 | struct DivisibleBy7Error(u32); 10 | 11 | type MyResult = Result; 12 | 13 | impl fmt::Display for DivisibleBy3Error { 14 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 15 | write!(f, "{} is divisible by 3", self.0) 16 | } 17 | } 18 | impl fmt::Display for DivisibleBy7Error { 19 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 20 | write!(f, "{} is divisible by 7", self.0) 21 | } 22 | } 23 | 24 | impl fmt::Debug for DivisibleBy5Error { 25 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 26 | write!(f, "{} is divisible by 5", self.0) 27 | } 28 | } 29 | 30 | #[logfn(fmt = "not_divisible_by_3() -> {}", ok = "info", err = "error")] 31 | fn not_divisible_by_3(n: u32) -> Result { 32 | match n % 3 { 33 | 0 => Err(DivisibleBy3Error(n)), 34 | _ => Ok(n), 35 | } 36 | } 37 | 38 | #[logfn(Info, fmt = "not_divisible_by_5() -> {:?}")] 39 | fn not_divisible_by_5_with_enum_wrap(n: u32) -> MyResult { 40 | match n % 5 { 41 | 0 => Err(DivisibleBy5Error(n)), 42 | _ => Ok(n), 43 | } 44 | } 45 | 46 | #[logfn(fmt = "not_divisible_by_7() -> {}", ok = "info", err = "error")] 47 | fn not_divisible_by_7(n: u32) -> MyResult { 48 | match n % 7 { 49 | 0 => Err(DivisibleBy7Error(n)), 50 | _ => Ok(n), 51 | } 52 | } 53 | 54 | fn main() { 55 | TermLogger::init(LevelFilter::Trace, Config::default(), TerminalMode::default()).unwrap(); 56 | for x in 0..25 { 57 | let _ = not_divisible_by_3(x); 58 | let _ = not_divisible_by_5_with_enum_wrap(x); 59 | let _ = not_divisible_by_7(x); 60 | } 61 | } 62 | 63 | #[cfg(test)] 64 | #[test] 65 | fn test_main() { 66 | main(); 67 | } 68 | -------------------------------------------------------------------------------- /tests/test_logger.rs: -------------------------------------------------------------------------------- 1 | use log::{Level, LevelFilter, Log, Metadata, Record}; 2 | use std::cell::RefCell; 3 | 4 | #[derive(Clone, Debug)] 5 | pub struct LogRecord { 6 | pub msg: String, 7 | pub level: Level, 8 | pub line: u32, 9 | } 10 | 11 | struct VecLooger(Vec); 12 | 13 | // Should assert this in any test because TLS doesn't always promise destructors are executed. 14 | // This is implemented as a sanity so we never forget to assert we emptied the Logger. 15 | impl Drop for VecLooger { 16 | fn drop(&mut self) { 17 | assert!(self.0.is_empty()); 18 | } 19 | } 20 | 21 | // The basic idea goes as follow: 22 | // We use a public ZST static logger, but in the `Log` impl it actually calls into the thread_local, 23 | // that way we don't need to worry about race conditions in the code because each test has it's own VecLogger. 24 | pub struct ThreadSingletonLogger; 25 | pub static THREAD_LOGGER: ThreadSingletonLogger = ThreadSingletonLogger; 26 | thread_local! {static LOGGER: RefCell = RefCell::new(VecLooger(Vec::with_capacity(4)));} 27 | 28 | impl Log for ThreadSingletonLogger { 29 | fn enabled(&self, _: &Metadata) -> bool { 30 | true 31 | } 32 | 33 | fn log(&self, record: &Record) { 34 | LOGGER.with(|cell| { 35 | let vec = &mut cell.borrow_mut().0; 36 | let new_log = LogRecord { msg: record.args().to_string(), level: record.level(), line: record.line().unwrap() }; 37 | vec.push(new_log); 38 | }) 39 | } 40 | 41 | fn flush(&self) {} 42 | } 43 | 44 | impl ThreadSingletonLogger { 45 | pub fn assert_last_log(&self, msg: &str, level: Level, line: u32) { 46 | LOGGER.with(|cell| { 47 | let last = cell.borrow_mut().0.pop().unwrap(); 48 | assert_eq!(last.msg, msg); 49 | assert_eq!(last.level, level); 50 | assert_eq!(last.line, line); 51 | }) 52 | } 53 | // Should almost never call this directly 54 | pub fn pop_log(&self) -> LogRecord { 55 | LOGGER.with(|cell| cell.borrow_mut().0.pop().unwrap()) 56 | } 57 | 58 | pub fn is_empty(&self) -> bool { 59 | LOGGER.with(|cell| cell.borrow().0.is_empty()) 60 | } 61 | } 62 | 63 | pub fn init() { 64 | // This is thread safe because `set_logger` is atomic. 65 | let _ = log::set_logger(&THREAD_LOGGER); 66 | log::set_max_level(LevelFilter::max()) 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # log-derive 2 | [![Build Status](https://travis-ci.org/elichai/log-derive.svg?branch=master)](https://travis-ci.org/elichai/log-derive) 3 | [![Latest version](https://img.shields.io/crates/v/log-derive.svg)](https://crates.io/crates/log-derive) 4 | [![Documentation](https://docs.rs/log-derive/badge.svg)](https://docs.rs/log-derive) 5 | ![License](https://img.shields.io/crates/l/log-derive.svg) 6 | [![dependency status](https://deps.rs/repo/github/elichai/log-derive/status.svg)](https://deps.rs/repo/github/elichai/log-derive) 7 | 8 | A Rust macro to part of the [log](https://crates.io/crates/log) facade that auto generates loggings for functions output. 9 | 10 | * [Documentation](https://docs.rs/log-derive) 11 | 12 | ## Usage 13 | 14 | Add this to your `Cargo.toml`: 15 | 16 | ```toml 17 | [dependencies] 18 | log-derive = "0.3" 19 | log = "0.4" 20 | 21 | ``` 22 | 23 | and for Rust Edition 2015 add this to your crate root: 24 | 25 | ```rust 26 | #[macro_use] 27 | extern crate log_derive; 28 | extern crate log; 29 | ``` 30 | In Rust Edition 2018 you can simply do: 31 | ```rust 32 | use log_derive::logfn; 33 | ``` 34 | 35 | After that all you need is to add the according macro above a function that,
36 | either returns an output or receive an input that implements the `Debug` trait. 37 | 38 | # Examples 39 | 40 | ```rust 41 | #[logfn(Err = "Error", fmt = "Failed Sending Packet: {:?}")] 42 | fn send_hi(addr: SocketAddr) -> Result<(), io::Error> { 43 | let mut stream = TcpStream::connect(addr)?; 44 | stream.write(b"Hi!")?; 45 | Ok( () ) 46 | } 47 | 48 | ``` 49 | 50 | ```rust 51 | #[logfn(Trace)] 52 | #[logfn_inputs(Info)] 53 | fn test_log(a: u8) -> String { 54 | (a*2).to_string() 55 | } 56 | 57 | ``` 58 | 59 | ```rust 60 | #[logfn(Trace, fmt = "testing the num: {:?}")] 61 | fn test_log(a: u8) -> String { 62 | (a*2).to_string() 63 | } 64 | 65 | ``` 66 | 67 | # Output 68 | The output of the [fibonacci](./examples/fibonacci.rs) example: 69 | ``` 70 | 17:15:24 [TRACE] (1) fibonacci: [examples/fibonacci.rs:16] fibonacci(n: 5) 71 | 17:15:24 [TRACE] (1) fibonacci: [examples/fibonacci.rs:16] fibonacci(n: 4) 72 | 17:15:24 [TRACE] (1) fibonacci: [examples/fibonacci.rs:16] fibonacci(n: 3) 73 | 17:15:24 [TRACE] (1) fibonacci: [examples/fibonacci.rs:16] fibonacci(n: 2) 74 | 17:15:24 [TRACE] (1) fibonacci: [examples/fibonacci.rs:16] fibonacci(n: 1) 75 | 17:15:24 [ INFO] fibonacci() -> 1 76 | 17:15:24 [TRACE] (1) fibonacci: [examples/fibonacci.rs:16] fibonacci(n: 0) 77 | 17:15:24 [ INFO] fibonacci() -> 1 78 | 17:15:24 [ INFO] fibonacci() -> 2 79 | 17:15:24 [TRACE] (1) fibonacci: [examples/fibonacci.rs:16] fibonacci(n: 1) 80 | 17:15:24 [ INFO] fibonacci() -> 1 81 | 17:15:24 [ INFO] fibonacci() -> 3 82 | 17:15:24 [TRACE] (1) fibonacci: [examples/fibonacci.rs:16] fibonacci(n: 2) 83 | 17:15:24 [TRACE] (1) fibonacci: [examples/fibonacci.rs:16] fibonacci(n: 1) 84 | 17:15:24 [ INFO] fibonacci() -> 1 85 | 17:15:24 [TRACE] (1) fibonacci: [examples/fibonacci.rs:16] fibonacci(n: 0) 86 | 17:15:24 [ INFO] fibonacci() -> 1 87 | 17:15:24 [ INFO] fibonacci() -> 2 88 | 17:15:24 [ INFO] fibonacci() -> 5 89 | 17:15:24 [TRACE] (1) fibonacci: [examples/fibonacci.rs:16] fibonacci(n: 3) 90 | 17:15:24 [TRACE] (1) fibonacci: [examples/fibonacci.rs:16] fibonacci(n: 2) 91 | 17:15:24 [TRACE] (1) fibonacci: [examples/fibonacci.rs:16] fibonacci(n: 1) 92 | 17:15:24 [ INFO] fibonacci() -> 1 93 | 17:15:24 [TRACE] (1) fibonacci: [examples/fibonacci.rs:16] fibonacci(n: 0) 94 | 17:15:24 [ INFO] fibonacci() -> 1 95 | 17:15:24 [ INFO] fibonacci() -> 2 96 | 17:15:24 [TRACE] (1) fibonacci: [examples/fibonacci.rs:16] fibonacci(n: 1) 97 | 17:15:24 [ INFO] fibonacci() -> 1 98 | 17:15:24 [ INFO] fibonacci() -> 3 99 | 17:15:24 [ INFO] fibonacci() -> 8 100 | ``` 101 | 102 | If you expand the output of the `#[logfn]` macro the resulting code will look something like this: 103 | ```rust 104 | fn fibonacci(n: u32) -> u32 { 105 | let result = (move || match n { 106 | 0 => 1, 107 | 1 => 1, 108 | _ => fibonacci(n - 1) + fibonacci(n - 2), 109 | })(); 110 | log::log!(log::Level::Info, "fibonacci() -> {}", result); 111 | result 112 | } 113 | ``` 114 | If the function returns a `Result` it will match through it to split between the `Ok` LogLevel and the `Err` LogLevel 115 | 116 | The expansion of the `#[logfn_inputs]` macro will look something like this: 117 | ```rust 118 | fn fibonacci(n: u32) -> u32 { 119 | log::log!(log::Level::Info, "fibonacci(n: {:?})", n); 120 | match n { 121 | 0 => 1, 122 | 1 => 1, 123 | _ => fibonacci(n - 1) + fibonacci(n - 2), 124 | } 125 | } 126 | ``` 127 | 128 | Of course the `log!` macro will be expanded too and it will be a bit more messy. 129 | 130 | ## Note 131 | The `log_ts` feature will fail your compilation in a `no-std` enviroment. 132 | it can only be used where `std` is available. (as it uses `std::time::Instant`) 133 | -------------------------------------------------------------------------------- /tests/first.rs: -------------------------------------------------------------------------------- 1 | mod test_logger; 2 | 3 | use crate::test_logger::THREAD_LOGGER; 4 | use log::Level; 5 | use log_derive::{logfn, logfn_inputs}; 6 | 7 | #[logfn(INFO, fmt = "wrapper_function returned {:?}")] 8 | fn wrapped_function(a: u8, b: &str) { 9 | let mut test1 = Vec::new(); 10 | let mut test2 = || { 11 | test1.push(5); 12 | }; 13 | test2(); 14 | println!("{} {}", b, a); 15 | } 16 | 17 | struct AAAAAA; 18 | impl AAAAAA { 19 | #[logfn(Info)] 20 | pub fn yoyoy(&self, _a: String, _b: u8, _c: Vec) -> Vec { 21 | vec![0u8; 8] 22 | } 23 | } 24 | 25 | #[derive(Debug, PartialEq)] 26 | struct E; 27 | 28 | trait Test { 29 | fn abc(&mut self, err: Tes) -> Result; 30 | fn third(&self, err: &Tes) -> Result; 31 | fn just_inputs(&self, err: &Tes) -> Result; 32 | fn both(&self, err: &Tes) -> Result; 33 | } 34 | 35 | #[derive(Debug)] 36 | struct Me(Option); 37 | #[derive(Debug)] 38 | struct Tes(pub bool); 39 | 40 | impl Test for Me { 41 | #[logfn(INFO, fmt = "DB: {:?}", ok = "debug", err = "trace")] 42 | fn abc(&mut self, err: Tes) -> Result { 43 | let mut clos = || { 44 | self.third(&err)?; 45 | if err.0 { 46 | return Err(E); 47 | } else { 48 | self.0 = Some(5); 49 | return Ok(String::from("Hi!")); 50 | } 51 | }; 52 | let result = clos(); 53 | result 54 | } 55 | 56 | #[logfn(Info)] 57 | fn third(&self, err: &Tes) -> Result { 58 | if err.0 { 59 | return Err(E); 60 | } else { 61 | return Ok(String::from("Hi!")); 62 | } 63 | } 64 | 65 | #[logfn_inputs(Debug)] 66 | fn just_inputs(&self, err: &Tes) -> Result { 67 | if err.0 { 68 | return Err(E); 69 | } else { 70 | return Ok(String::from("Hi!")); 71 | } 72 | } 73 | 74 | #[logfn_inputs(Trace)] 75 | #[logfn(Info)] 76 | fn both(&self, err: &Tes) -> Result { 77 | let clos = || { 78 | self.third(&err)?; 79 | if err.0 { 80 | return Err(E); 81 | } else { 82 | return Ok(String::from("Hi!")); 83 | } 84 | }; 85 | let result = clos(); 86 | result 87 | } 88 | } 89 | 90 | #[test] 91 | fn works() { 92 | test_logger::init(); 93 | wrapped_function(5, "cool!"); 94 | THREAD_LOGGER.assert_last_log("wrapper_function returned ()", Level::Info, 7); 95 | let a = AAAAAA; 96 | let _ = a.yoyoy(String::from("fds"), 55, vec![1u8; 12]); 97 | THREAD_LOGGER.assert_last_log("yoyoy() => [0, 0, 0, 0, 0, 0, 0, 0]", Level::Info, 19); 98 | let mut b = Me(None); 99 | let tes = Tes(false); 100 | b.abc(tes).unwrap(); 101 | THREAD_LOGGER.assert_last_log("DB: \"Hi!\"", Level::Debug, 41); 102 | // `b.abc` calls `third()` so we need to assert that log too. 103 | THREAD_LOGGER.assert_last_log("third() => \"Hi!\"", Level::Info, 56); 104 | assert!(THREAD_LOGGER.is_empty()) 105 | } 106 | 107 | #[test] 108 | fn test_inputs() { 109 | test_logger::init(); 110 | let b = Me(Some(5)); 111 | let tes = Tes(false); 112 | b.just_inputs(&tes).unwrap(); 113 | THREAD_LOGGER.assert_last_log("just_inputs(self: Me(Some(5)),err: Tes(false))", Level::Debug, 65); 114 | b.both(&tes).unwrap(); 115 | 116 | // Assert `b.both` input log 117 | THREAD_LOGGER.assert_last_log("both() => \"Hi!\"", Level::Info, 75); 118 | // `b.both` calls `third()` so wee need to assert that log too 119 | THREAD_LOGGER.assert_last_log("third() => \"Hi!\"", Level::Info, 56); 120 | // Assert `b.both` output log 121 | // Due to a bug in rust stable we can't test the line number here. (rust-lang/rust#74035) 122 | // THREAD_LOGGER.assert_last_log("both(self: Me(Some(5)),err: Tes(false))", Level::Trace, 74); 123 | let log = THREAD_LOGGER.pop_log(); 124 | assert_eq!(log.msg, "both(self: Me(Some(5)),err: Tes(false))"); 125 | assert_eq!(log.level, Level::Trace); 126 | assert!(THREAD_LOGGER.is_empty()) 127 | } 128 | 129 | #[test] 130 | fn fail() { 131 | test_logger::init(); 132 | wrapped_function(5, "cool!"); 133 | THREAD_LOGGER.assert_last_log("wrapper_function returned ()", Level::Info, 7); 134 | let a = AAAAAA; 135 | let _ = a.yoyoy(String::from("fds"), 55, vec![1u8; 12]); 136 | THREAD_LOGGER.assert_last_log("yoyoy() => [0, 0, 0, 0, 0, 0, 0, 0]", Level::Info, 19); 137 | 138 | let mut b = Me(None); 139 | let tes = Tes(true); 140 | assert_eq!(b.abc(tes), Err(E)); 141 | THREAD_LOGGER.assert_last_log("DB: E", Level::Trace, 41); 142 | // `b.abc` calls `third()` so wee need to assert that log too 143 | THREAD_LOGGER.assert_last_log("third() => E", Level::Info, 56); 144 | 145 | assert!(THREAD_LOGGER.is_empty()) 146 | } 147 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit = "128"] 2 | 3 | //! # Log Derive 4 | //! 5 | //! `log-derive` provides a simple attribute macro that facilitates logs as part of the [`log`] facade
6 | //! Right now it contains two macros [`logfn`], [`logfn_inputs`] these macros are only for functions but still have a lot of power. 7 | //! 8 | //! 9 | //! # Use 10 | //! The basic use of these macros is by putting one or both of them on top of the function like this: `#[logfn(INFO)]`
11 | //! 12 | //! The [`logfn`] macro is used to log the *output* of the function and [`logfn_inputs`] is used to log the *inputs*.
13 | //! Please notice, the arguments being logged **must** implement the [`Debug`] trait.
14 | //! (i.e. [`logfn`] requires the output to be [`Debug`] and [`logfn_inputs`] require the inputs to be [`Debug`])
15 | //! 16 | //! The macros will accept all log levels provided by the [`log`] facade.
17 | //! In [`logfn`] if the function returns a [`Result`] type the macro will accept the following additional attributes:
18 | //! `(ok = "LEVEL")` and `(err = "LEVEL")` this can provide different log levels if the function failed or not.
19 | //! 20 | //! By default the macro uses the following formatting to print the message:
21 | //! [`logfn`]: `("FUNCTION_NAME() => {:?}", return_val)`
22 | //! [`logfn_inputs`]: `"FUNCTION_NAME(a: {:?}, b: {:?})", a, b)`
23 | //! This can be easily changed using the `fmt` attribute: `#[logfn(LEVEL, fmt = "Important Result: {:}")`
24 | //! which will accept format strings similar to [`println!`]. 25 | //! 26 | //! [`logfn`]: ./attr.logfn.html 27 | //! [`logfn_inputs`]: ./attr.logfn_inputs.html 28 | //! [`log`]: https://docs.rs/log/latest/log/index.html 29 | //! [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html 30 | //! [`println!`]: https://doc.rust-lang.org/stable/std/macro.println.html 31 | //! [`Debug`]: https://doc.rust-lang.org/std/fmt/trait.Debug.html 32 | //! 33 | //! ## Examples 34 | //! ```rust 35 | //! use log_derive::{logfn, logfn_inputs}; 36 | //! 37 | //! # #[derive(Debug)] 38 | //! struct Error; 39 | //! # #[derive(Debug)] 40 | //! struct Success; 41 | //! # #[derive(Debug)] 42 | //! enum Status { Alive, Dead, Unknown } 43 | //! 44 | //! #[logfn(Warn)] 45 | //! #[logfn_inputs(Info, fmt = "Checking if {:?} is alive")] 46 | //! fn is_alive(person: &Person) -> Status { 47 | //! # use Response::*; 48 | //! # use Status::*; 49 | //! match person.ping() { 50 | //! Pong => Status::Alive, 51 | //! Timeout => if person.is_awake() { 52 | //! Unknown 53 | //! } else { 54 | //! Dead 55 | //! } 56 | //! } 57 | //!} 58 | //! 59 | //! #[logfn_inputs(Info)] 60 | //! #[logfn(ok = "TRACE", err = "ERROR")] 61 | //! fn call_isan(num: &str) -> Result { 62 | //! if num.len() >= 10 && num.len() <= 15 { 63 | //! Ok(Success) 64 | //! } else { 65 | //! Err(Error) 66 | //! } 67 | //! } 68 | //! 69 | //! #[logfn(INFO, fmt = "a + b = {}")] 70 | //! #[logfn_inputs(Trace, fmt = "adding a: {:?} and b: {:?}")] 71 | //! fn addition(a: usize, b: usize) -> usize { 72 | //! a + b 73 | //! } 74 | //! 75 | //! #[logfn_inputs(Info)] 76 | //! #[logfn(ok = "TRACE", log_ts = true)] 77 | //! fn time_this(num: &str) -> Result { 78 | //! if num.len() >= 10 && num.len() <= 15 { 79 | //! std::thread::sleep(Duration::from_secs(1)); 80 | //! Ok(Success) 81 | //! } else { 82 | //! Err(Error) 83 | //! } 84 | //! } 85 | //! 86 | //! # enum Response {Pong, Timeout} 87 | //! # #[derive(Debug)] 88 | //! # struct Person; 89 | //! # impl Person {fn ping(&self) -> Response {Response::Pong}fn is_awake(&self) -> bool {true}} 90 | //! # use std::time::Duration; 91 | //! ``` 92 | //! 93 | //! 94 | extern crate proc_macro; 95 | extern crate syn; 96 | use darling::{Error, FromMeta}; 97 | use proc_macro2::TokenStream; 98 | use quote::{quote, ToTokens}; 99 | 100 | use syn::punctuated::Punctuated; 101 | use syn::{ 102 | parse_macro_input, spanned::Spanned, token, AttributeArgs, Expr, ExprAsync, ExprAwait, ExprBlock, ExprCall, ExprClosure, 103 | ExprParen, FnArg, Ident, ItemFn, Meta, NestedMeta, Pat, Result, ReturnType, Stmt, Type, TypePath, 104 | }; 105 | 106 | struct FormattedAttributes { 107 | ok_expr: TokenStream, 108 | err_expr: TokenStream, 109 | log_ts: bool, 110 | contained_ok_or_err: bool, 111 | } 112 | 113 | impl FormattedAttributes { 114 | pub fn parse_attributes(attr: &[NestedMeta], fmt_default: String) -> darling::Result { 115 | OutputOptions::from_list(attr).map(|opts| Self::get_ok_err_streams(opts, fmt_default)) 116 | } 117 | 118 | fn get_ok_err_streams(att: OutputOptions, fmt_default: String) -> Self { 119 | let contained_ok_or_err = att.contains_ok_or_err(); 120 | let log_ts = att.log_ts(); 121 | let ok_log = att.ok_log(); 122 | let err_log = att.err_log(); 123 | let mut fmt = att.fmt().unwrap_or(fmt_default); 124 | if log_ts { 125 | fmt += ", ts={:#?}" 126 | }; 127 | 128 | let ok_expr = match ok_log { 129 | Some(loglevel) => { 130 | let log_token = get_logger_token(&loglevel); 131 | if log_ts { 132 | quote! {log::log!(#log_token, #fmt, result, ts); } 133 | } else { 134 | quote! {log::log!(#log_token, #fmt, result); } 135 | } 136 | } 137 | None => quote! {()}, 138 | }; 139 | 140 | let err_expr = match err_log { 141 | Some(loglevel) => { 142 | let log_token = get_logger_token(&loglevel); 143 | if log_ts { 144 | quote! {log::log!(#log_token, #fmt, err, ts); } 145 | } else { 146 | quote! {log::log!(#log_token, #fmt, err); } 147 | } 148 | } 149 | None => quote! {()}, 150 | }; 151 | FormattedAttributes { ok_expr, err_expr, log_ts, contained_ok_or_err } 152 | } 153 | } 154 | 155 | #[derive(Default, FromMeta)] 156 | #[darling(default)] 157 | struct OutputNamedOptions { 158 | ok: Option, 159 | err: Option, 160 | fmt: Option, 161 | log_ts: Option, 162 | } 163 | 164 | struct OutputOptions { 165 | /// The log level specified as the first word in the attribute. 166 | leading_level: Option, 167 | named: OutputNamedOptions, 168 | } 169 | 170 | struct InputOptions { 171 | level: Ident, 172 | fmt: Option, 173 | } 174 | 175 | impl FromMeta for InputOptions { 176 | fn from_list(items: &[NestedMeta]) -> darling::Result { 177 | let level; 178 | let mut fmt = None; 179 | if items.is_empty() { 180 | return Err(Error::too_few_items(1)); 181 | } 182 | 183 | match &items[0] { 184 | NestedMeta::Meta(first) => { 185 | if let Meta::Path(path) = first { 186 | if let Some(ident) = path.get_ident() { 187 | level = ident.clone(); 188 | } else { 189 | return Err(Error::unexpected_type("first item should be a log level")); 190 | } 191 | } else { 192 | return Err(Error::unexpected_type("first item should be a log level")); 193 | } 194 | } 195 | NestedMeta::Lit(lit) => return Err(Error::unexpected_lit_type(lit)), 196 | } 197 | 198 | if items.len() > 1 { 199 | fmt = String::from_nested_meta(&items[1]).ok(); 200 | } 201 | 202 | Ok(InputOptions { level, fmt }) 203 | } 204 | } 205 | 206 | impl OutputOptions { 207 | pub fn ok_log(&self) -> Option<&Ident> { 208 | self.named.ok.as_ref().or_else(|| self.leading_level.as_ref()) 209 | } 210 | 211 | pub fn err_log(&self) -> Option<&Ident> { 212 | self.named.err.as_ref().or_else(|| self.leading_level.as_ref()) 213 | } 214 | 215 | pub fn contains_ok_or_err(&self) -> bool { 216 | self.named.ok.is_some() || self.named.err.is_some() 217 | } 218 | 219 | pub fn log_ts(&self) -> bool { 220 | self.named.log_ts.unwrap_or(false) 221 | } 222 | 223 | pub fn fmt(&self) -> Option { 224 | self.named.fmt.clone() 225 | } 226 | } 227 | 228 | impl FromMeta for OutputOptions { 229 | fn from_list(items: &[NestedMeta]) -> darling::Result { 230 | if items.is_empty() { 231 | return Err(darling::Error::too_few_items(1)); 232 | } 233 | 234 | let mut leading_level = None; 235 | 236 | if let NestedMeta::Meta(first) = &items[0] { 237 | if let Meta::Path(path) = first { 238 | leading_level = path.get_ident().cloned(); 239 | } 240 | } 241 | 242 | let named = 243 | if leading_level.is_some() { OutputNamedOptions::from_list(&items[1..])? } else { OutputNamedOptions::from_list(items)? }; 244 | 245 | Ok(OutputOptions { leading_level, named }) 246 | } 247 | } 248 | 249 | /// Check if a return type is some form of `Result`. This assumes that all types named `Result` 250 | /// are in fact results, but is resilient to the possibility of `Result` types being referenced 251 | /// from specific modules. 252 | pub(crate) fn is_result_type(ty: &TypePath) -> bool { 253 | if let Some(segment) = ty.path.segments.iter().last() { 254 | segment.ident == "Result" 255 | } else { 256 | false 257 | } 258 | } 259 | 260 | fn check_if_return_result(f: &ItemFn) -> bool { 261 | if let ReturnType::Type(_, t) = &f.sig.output { 262 | return match t.as_ref() { 263 | Type::Path(path) => is_result_type(path), 264 | _ => false, 265 | }; 266 | } 267 | 268 | false 269 | } 270 | 271 | fn get_logger_token(att: &Ident) -> TokenStream { 272 | // Capitalize the first letter. 273 | let attr_str = att.to_string().to_lowercase(); 274 | let mut attr_char = attr_str.chars(); 275 | let attr_str = attr_char.next().unwrap().to_uppercase().to_string() + attr_char.as_str(); 276 | let att_str = Ident::new(&attr_str, att.span()); 277 | quote!(log::Level::#att_str) 278 | } 279 | 280 | fn make_closure(original: &ItemFn) -> Expr { 281 | match original.sig.asyncness { 282 | Some(asyncness) => Expr::Await(ExprAwait { 283 | attrs: Default::default(), 284 | await_token: Default::default(), 285 | dot_token: Default::default(), 286 | base: Box::new(syn::Expr::Async(ExprAsync { 287 | attrs: Default::default(), 288 | capture: Some(token::Move { span: original.span() }), 289 | block: *original.block.clone(), 290 | async_token: asyncness, 291 | })), 292 | }), 293 | None => Expr::Call(ExprCall { 294 | attrs: Default::default(), 295 | args: Default::default(), 296 | paren_token: Default::default(), 297 | func: Box::new(syn::Expr::Paren(ExprParen { 298 | attrs: Default::default(), 299 | paren_token: Default::default(), 300 | expr: Box::new(syn::Expr::Closure(ExprClosure { 301 | attrs: Default::default(), 302 | asyncness: Default::default(), 303 | movability: Default::default(), 304 | capture: Some(token::Move { span: original.span() }), 305 | or1_token: Default::default(), 306 | inputs: Default::default(), 307 | or2_token: Default::default(), 308 | output: ReturnType::Default, 309 | body: Box::new(Expr::Block(ExprBlock { 310 | attrs: Default::default(), 311 | label: Default::default(), 312 | block: *original.block.clone(), 313 | })), 314 | })), 315 | })), 316 | }), 317 | } 318 | } 319 | 320 | fn replace_function_headers(original: ItemFn, new: &mut ItemFn) { 321 | let block = new.block.clone(); 322 | *new = original; 323 | new.block = block; 324 | } 325 | 326 | fn generate_function(closure: &Expr, expressions: FormattedAttributes, result: bool) -> Result { 327 | let FormattedAttributes { ok_expr, err_expr, log_ts, contained_ok_or_err } = expressions; 328 | let result = result || contained_ok_or_err; 329 | let code = if log_ts { 330 | if result { 331 | quote! { 332 | fn temp() { 333 | let instant = std::time::Instant::now(); 334 | let result = #closure; 335 | let ts = instant.elapsed(); 336 | result.map(|result| { #ok_expr; result }) 337 | .map_err(|err| { #err_expr; err }) 338 | } 339 | } 340 | } else { 341 | quote! { 342 | fn temp() { 343 | let instant = std::time::Instant::now(); 344 | let result = #closure; 345 | let ts = instant.elapsed(); 346 | #ok_expr; 347 | result 348 | } 349 | } 350 | } 351 | } else if result { 352 | quote! { 353 | fn temp() { 354 | let result = #closure; 355 | result.map(|result| { #ok_expr; result }) 356 | .map_err(|err| { #err_expr; err }) 357 | } 358 | } 359 | } else { 360 | quote! { 361 | fn temp() { 362 | let result = #closure; 363 | #ok_expr; 364 | result 365 | } 366 | } 367 | }; 368 | 369 | syn::parse2(code) 370 | } 371 | 372 | /// Logs the result of the function it's above. 373 | /// # Examples 374 | /// ``` rust 375 | /// # #[macro_use] extern crate log_derive; 376 | /// # use std::{net::*, io::{self, Write}}; 377 | /// #[logfn(err = "Error", fmt = "Failed Sending Packet: {:?}")] 378 | /// fn send_hi(addr: SocketAddr) -> Result<(), io::Error> { 379 | /// let mut stream = TcpStream::connect(addr)?; 380 | /// stream.write(b"Hi!")?; 381 | /// Ok( () ) 382 | /// } 383 | /// 384 | /// 385 | /// ``` 386 | #[proc_macro_attribute] 387 | pub fn logfn(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { 388 | let attr = parse_macro_input!(attr as AttributeArgs); 389 | let original_fn: ItemFn = parse_macro_input!(item as ItemFn); 390 | let fmt_default = original_fn.sig.ident.to_string() + "() => {:?}"; 391 | let parsed_attributes: FormattedAttributes = match FormattedAttributes::parse_attributes(&attr, fmt_default) { 392 | Ok(val) => val, 393 | Err(err) => { 394 | return err.write_errors().into(); 395 | } 396 | }; 397 | let closure = make_closure(&original_fn); 398 | let is_result = check_if_return_result(&original_fn); 399 | let mut new_fn = generate_function(&closure, parsed_attributes, is_result).expect("Failed Generating Function"); 400 | replace_function_headers(original_fn, &mut new_fn); 401 | new_fn.into_token_stream().into() 402 | } 403 | 404 | /// Logs the inputs of the function 405 | /// # Examples 406 | /// ``` rust 407 | /// # #[macro_use] extern crate log_derive; 408 | /// # use std::{net::*, io::{self, Write}}; 409 | /// #[logfn_inputs(INFO, fmt = "Good morning: {:?}, to: {:?}")] 410 | /// fn good_morning(msg: &str, addr: SocketAddr) -> Result<(), io::Error> { 411 | /// let mut stream = TcpStream::connect(addr)?; 412 | /// stream.write(msg.as_bytes())?; 413 | /// Ok( () ) 414 | /// } 415 | /// 416 | /// 417 | /// ``` 418 | #[proc_macro_attribute] 419 | pub fn logfn_inputs(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { 420 | let mut original_fn: ItemFn = parse_macro_input!(item as ItemFn); 421 | 422 | let attr = parse_macro_input!(attr as AttributeArgs); 423 | let parsed_attributes = match InputOptions::from_list(&attr) { 424 | Ok(val) => val, 425 | Err(err) => { 426 | return err.write_errors().into(); 427 | } 428 | }; 429 | 430 | let mut stmts = match log_fn_inputs(&original_fn, parsed_attributes) { 431 | Ok(input_log) => vec![input_log], 432 | Err(e) => return e.to_compile_error().into(), 433 | }; 434 | 435 | stmts.extend(original_fn.block.stmts); 436 | original_fn.block.stmts = stmts; 437 | original_fn.into_token_stream().into() 438 | } 439 | 440 | fn log_fn_inputs(func: &ItemFn, attr: InputOptions) -> syn::Result { 441 | let fn_name = func.sig.ident.to_string(); 442 | let inputs: Vec = func 443 | .sig 444 | .inputs 445 | .iter() 446 | .cloned() 447 | .map(|arg| match arg { 448 | FnArg::Receiver(arg) => arg.self_token.into(), 449 | FnArg::Typed(pat_type) => { 450 | if let Pat::Ident(ident) = *pat_type.pat { 451 | ident.ident 452 | } else { 453 | unimplemented!() 454 | } 455 | } 456 | }) 457 | .collect(); 458 | 459 | let items: Punctuated<_, token::Comma> = inputs.iter().cloned().collect(); 460 | 461 | let level = get_logger_token(&attr.level); 462 | let fmt = attr.fmt.unwrap_or_else(|| { 463 | let mut fmt = String::with_capacity(inputs.len() * 9); 464 | fmt.push_str(&fn_name); 465 | fmt.push('('); 466 | 467 | for input in inputs { 468 | fmt.push_str(&input.to_string()); 469 | fmt.push_str(": {:?},"); 470 | } 471 | fmt.pop(); // Remove the extra comma. 472 | fmt.push(')'); 473 | fmt 474 | }); 475 | 476 | let res = quote! { 477 | log::log!(#level, #fmt, #items); 478 | }; 479 | syn::parse2(res) 480 | } 481 | 482 | #[cfg(test)] 483 | mod tests { 484 | use syn::parse_quote; 485 | 486 | use super::is_result_type; 487 | 488 | #[test] 489 | fn result_type() { 490 | assert!(is_result_type(&parse_quote!(Result))); 491 | assert!(is_result_type(&parse_quote!(std::result::Result))); 492 | assert!(is_result_type(&parse_quote!(fmt::Result))); 493 | } 494 | } 495 | --------------------------------------------------------------------------------