logcall 0.2.0

An attribute macro that logs the function return value.
Documentation
# Logcall

[![Crates.io](https://img.shields.io/crates/v/logcall?style=flat-square&logo=rust)](https://crates.io/crates/logcall)
[![Downloads](https://img.shields.io/crates/d/logcall?style=flat-square&logo=rust)](https://crates.io/crates/logcall)
[![Documentation](https://img.shields.io/docsrs/logcall?style=flat-square&logo=rust)](https://docs.rs/logcall/)
[![CI Status](https://img.shields.io/github/actions/workflow/status/fast/logcall/ci.yml?style=flat-square&logo=github)](https://github.com/fast/logcall/actions)
[![License](https://img.shields.io/crates/l/logcall?style=flat-square&logo=)](https://crates.io/crates/logcall)

Logcall provides a `#[logcall]` attribute that logs function inputs and return values through the [`log`](https://crates.io/crates/log) facade. It keeps boilerplate low while making debugging and observability easy.

This is a reimplementation of [`log-derive`](https://crates.io/crates/log-derive) with async functions, [`async-trait`](https://crates.io/crates/async-trait), `Result`, and `Option` support.

## Installation

Logcall emits through `log`, so applications also need a logger. This example uses `logforth`:

```toml
[dependencies]
log = "0.4"
logcall = "0.2.0"
logforth = { version = "0.30", features = ["starter-log"] }
```

## Quick Start

Annotate functions with `#[logcall]` and configure logging with `logforth`:

```rust
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);
}
```

### Example Run

```bash
cargo run --example main
```

Sample output:

```plaintext
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)
```

## Attribute Arguments

Generated input and output formatting uses `Debug` by default. Supported log levels are `error`, `warn`, `info`, `debug`, and `trace`.

- `#[logcall]` logs every call at the `debug` level.
- `#[logcall("info")]` logs every call at the selected level.
- `#[logcall(ok = "info", err = "error")]` logs `Result` variants independently. Omit `ok` or `err` to skip that variant.
- `#[logcall(some = "info", none = "warn")]` logs `Option` variants independently. Omit `some` or `none` to 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. Use `output = ""` to log only the call.

The macro supports sync functions, async functions, and async functions generated by `async-trait`.

## Minimum Supported Rust Version (MSRV)

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.

## Contributing

Contributions are welcome! Please submit pull requests or open issues to improve the crate.

## License

This project is licensed under the MIT License. See the `LICENSE` file for details.