Logcall provides a #[logcall] attribute that logs function inputs and return values through the log facade. It keeps boilerplate low while making debugging and observability easy.
This is a reimplementation of log-derive with async functions, async-trait, Result, and Option support.
Logcall emits through log, so applications also need a logger. This example uses logforth:
[dependencies]
log = "0.4"
logcall = "0.2.0"
logforth = { version = "0.30", features = ["starter-log"] }Annotate functions with #[logcall] and configure logging with logforth:
use logcall::logcall;
use logforth::record::LevelFilter;
/// Logs the function call at the default `debug` level.
#[logcall]
fn add(a: i32, b: i32) -> i32 {
a + b
}
/// Logs the function call at the `info` level.
#[logcall("info")]
fn multiply(a: i32, b: i32) -> i32 {
a * b
}
/// Logs `Ok` results at the `info` level and `Err` results at the `error` level.
#[logcall(ok = "info", err = "error")]
fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err("Division by zero".to_string())
} else {
Ok(a / b)
}
}
/// Logs errors at the `error` level. No log output for `Ok` variant.
#[logcall(err = "error")]
fn divide2(a: usize, b: usize) -> Result<usize, String> {
a.checked_div(b).ok_or("divide by zero".into())
}
/// Logs `Some` values at the `info` level and `None` values at the `warn` level.
#[logcall(some = "info", none = "warn")]
fn find_even(value: i32) -> Option<i32> {
(value % 2 == 0).then_some(value)
}
/// Logs the function call with custom input logging format.
#[logcall(input = "a = {a:?}, ..")]
fn subtract(a: i32, b: i32) -> i32 {
a - b
}
/// Logs the function call with custom output logging format.
#[logcall(output = ": {:?}")]
fn negate(a: i32) -> i32 {
-a
}
/// Omits the return value from the log output.
#[logcall(output = "")]
fn ping(a: i32) -> i32 {
a
}
fn main() {
logforth::starter_log::stdout()
.filter(LevelFilter::All)
.apply();
add(2, 3);
multiply(2, 3);
divide(2, 0).ok();
divide2(2, 0).ok();
find_even(4);
find_even(3);
subtract(3, 2);
negate(5);
ping(42);
}cargo run --example mainSample output:
2026-06-12T18:18:14.935453+08:00 DEBUG main: main.rs:5 main::add(a = 2, b = 3) => 5
2026-06-12T18:18:14.935505+08:00 INFO main: main.rs:11 main::multiply(a = 2, b = 3) => 6
2026-06-12T18:18:14.935514+08:00 ERROR main: main.rs:17 main::divide(a = 2, b = 0) => Err("Division by zero")
2026-06-12T18:18:14.935520+08:00 ERROR main: main.rs:27 main::divide2(a = 2, b = 0) => Err("divide by zero")
2026-06-12T18:18:14.935525+08:00 INFO main: main.rs:33 main::find_even(value = 4) => Some(4)
2026-06-12T18:18:14.935529+08:00 WARN main: main.rs:33 main::find_even(value = 3) => None
2026-06-12T18:18:14.935533+08:00 DEBUG main: main.rs:39 main::subtract(a = 3, ..) => 1
2026-06-12T18:18:14.935537+08:00 DEBUG main: main.rs:45 main::negate(a = 5): -5
2026-06-12T18:18:14.935542+08:00 DEBUG main: main.rs:51 main::ping(a = 42)
Generated input and output formatting uses Debug by default. Supported log levels are error, warn, info, debug, and trace.
#[logcall]logs every call at thedebuglevel.#[logcall("info")]logs every call at the selected level.#[logcall(ok = "info", err = "error")]logsResultvariants independently. Omitokorerrto skip that variant.#[logcall(some = "info", none = "warn")]logsOptionvariants independently. Omitsomeornoneto skip that variant.#[logcall(input = "...")]replaces the argument portion of the message. The format string can reference arguments by name.#[logcall(output = "...")]replaces the return value portion of the message. Useoutput = ""to log only the call.
The macro supports sync functions, async functions, and async functions generated by async-trait.
This crate's minimum supported rustc version is 1.91.0.
The minimum Rust version can increase in minor releases. Patch releases keep the same minimum Rust version.
Contributions are welcome! Please submit pull requests or open issues to improve the crate.
This project is licensed under the MIT License. See the LICENSE file for details.