├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── examples └── main.rs ├── rust-toolchain.toml ├── src └── lib.rs └── tests ├── ui.rs └── ui ├── err ├── error-passthrough.rs ├── error-passthrough.stderr ├── has-too-many-arguments.rs ├── has-too-many-arguments.stderr ├── item-is-not-a-function.rs ├── item-is-not-a-function.stderr ├── multiple-args.rs ├── multiple-args.stderr ├── wrong-level.rs └── wrong-level.stderr └── ok ├── async-in-trait.rs ├── async-mut.rs ├── async-trait.rs ├── async.rs ├── fastrace.rs ├── has-no-argument.rs ├── sync-mut.rs ├── sync.rs └── unreachable.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | check: 14 | runs-on: ubuntu-22.04 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Install toolchain 18 | uses: dtolnay/rust-toolchain@nightly 19 | with: 20 | components: rustfmt,clippy 21 | - uses: Swatinem/rust-cache@v2 22 | - name: Check clippy 23 | run: cargo +nightly clippy --all-targets --all-features -- -D warnings 24 | - name: Check format 25 | run: cargo +nightly fmt --all -- --check 26 | 27 | test: 28 | strategy: 29 | matrix: 30 | rust-version: [ "1.80.0", "stable" ] 31 | runs-on: ubuntu-22.04 32 | steps: 33 | - uses: actions/checkout@v4 34 | - uses: Swatinem/rust-cache@v2 35 | - name: Delete rust-toolchain.toml 36 | run: rm rust-toolchain.toml 37 | - name: Install toolchain 38 | uses: dtolnay/rust-toolchain@master 39 | with: 40 | toolchain: ${{ matrix.rust-version }} 41 | - name: Build 42 | run: cargo build --all-features --tests --examples --lib 43 | - name: Run unit tests 44 | run: cargo test --all-features -- --nocapture 45 | - name: Run examples 46 | run: cargo run --example main 47 | 48 | required: 49 | name: Required 50 | runs-on: ubuntu-22.04 51 | if: ${{ always() }} 52 | needs: 53 | - check 54 | - test 55 | steps: 56 | - name: Guardian 57 | run: | 58 | if [[ ! ( \ 59 | "${{ needs.check.result }}" == "success" \ 60 | && "${{ needs.test.result }}" == "success" \ 61 | ) ]]; then 62 | echo "Required jobs haven't been completed successfully." 63 | exit -1 64 | fi 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "anyhow" 31 | version = "1.0.94" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" 34 | 35 | [[package]] 36 | name = "async-trait" 37 | version = "0.1.83" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" 40 | dependencies = [ 41 | "proc-macro2", 42 | "quote", 43 | "syn 2.0.90", 44 | ] 45 | 46 | [[package]] 47 | name = "autocfg" 48 | version = "1.4.0" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 51 | 52 | [[package]] 53 | name = "backtrace" 54 | version = "0.3.74" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 57 | dependencies = [ 58 | "addr2line", 59 | "cfg-if", 60 | "libc", 61 | "miniz_oxide", 62 | "object", 63 | "rustc-demangle", 64 | "windows-targets", 65 | ] 66 | 67 | [[package]] 68 | name = "bitflags" 69 | version = "2.6.0" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 72 | 73 | [[package]] 74 | name = "bumpalo" 75 | version = "3.16.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 78 | 79 | [[package]] 80 | name = "byteorder" 81 | version = "1.5.0" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 84 | 85 | [[package]] 86 | name = "cfg-if" 87 | version = "1.0.0" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 90 | 91 | [[package]] 92 | name = "colored" 93 | version = "2.2.0" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" 96 | dependencies = [ 97 | "lazy_static", 98 | "windows-sys", 99 | ] 100 | 101 | [[package]] 102 | name = "ctor" 103 | version = "0.1.26" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" 106 | dependencies = [ 107 | "quote", 108 | "syn 1.0.109", 109 | ] 110 | 111 | [[package]] 112 | name = "env_filter" 113 | version = "0.1.3" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" 116 | dependencies = [ 117 | "log", 118 | "regex", 119 | ] 120 | 121 | [[package]] 122 | name = "equivalent" 123 | version = "1.0.1" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 126 | 127 | [[package]] 128 | name = "fastrace" 129 | version = "0.7.4" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "5242121a4de2ca29db07d2ab6ed5988ddbc3cf1ca19e52c80fde10cf498efde8" 132 | dependencies = [ 133 | "fastrace-macro", 134 | "minstant", 135 | "once_cell", 136 | "parking_lot", 137 | "pin-project", 138 | "rand", 139 | "rtrb", 140 | ] 141 | 142 | [[package]] 143 | name = "fastrace-macro" 144 | version = "0.7.4" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "76b7af4a705e4ba8104724d083ae2bca76e4964f637d9d35375eb99672d45fb6" 147 | dependencies = [ 148 | "proc-macro-error2", 149 | "proc-macro2", 150 | "quote", 151 | "syn 2.0.90", 152 | ] 153 | 154 | [[package]] 155 | name = "getrandom" 156 | version = "0.2.15" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 159 | dependencies = [ 160 | "cfg-if", 161 | "libc", 162 | "wasi", 163 | ] 164 | 165 | [[package]] 166 | name = "gimli" 167 | version = "0.31.1" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 170 | 171 | [[package]] 172 | name = "glob" 173 | version = "0.3.1" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 176 | 177 | [[package]] 178 | name = "hashbrown" 179 | version = "0.15.2" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 182 | 183 | [[package]] 184 | name = "indexmap" 185 | version = "2.7.0" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" 188 | dependencies = [ 189 | "equivalent", 190 | "hashbrown", 191 | ] 192 | 193 | [[package]] 194 | name = "itoa" 195 | version = "1.0.14" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 198 | 199 | [[package]] 200 | name = "jiff" 201 | version = "0.1.15" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "db69f08d4fb10524cacdb074c10b296299d71274ddbc830a8ee65666867002e9" 204 | dependencies = [ 205 | "jiff-tzdb-platform", 206 | "windows-sys", 207 | ] 208 | 209 | [[package]] 210 | name = "jiff-tzdb" 211 | version = "0.1.1" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "91335e575850c5c4c673b9bd467b0e025f164ca59d0564f69d0c2ee0ffad4653" 214 | 215 | [[package]] 216 | name = "jiff-tzdb-platform" 217 | version = "0.1.1" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "9835f0060a626fe59f160437bc725491a6af23133ea906500027d1bd2f8f4329" 220 | dependencies = [ 221 | "jiff-tzdb", 222 | ] 223 | 224 | [[package]] 225 | name = "js-sys" 226 | version = "0.3.76" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" 229 | dependencies = [ 230 | "once_cell", 231 | "wasm-bindgen", 232 | ] 233 | 234 | [[package]] 235 | name = "lazy_static" 236 | version = "1.5.0" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 239 | 240 | [[package]] 241 | name = "libc" 242 | version = "0.2.169" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" 245 | 246 | [[package]] 247 | name = "lock_api" 248 | version = "0.4.12" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 251 | dependencies = [ 252 | "autocfg", 253 | "scopeguard", 254 | ] 255 | 256 | [[package]] 257 | name = "log" 258 | version = "0.4.22" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 261 | 262 | [[package]] 263 | name = "logcall" 264 | version = "0.1.11" 265 | dependencies = [ 266 | "async-trait", 267 | "fastrace", 268 | "log", 269 | "logforth", 270 | "pollster", 271 | "proc-macro-error2", 272 | "proc-macro2", 273 | "quote", 274 | "syn 2.0.90", 275 | "tokio", 276 | "trybuild", 277 | ] 278 | 279 | [[package]] 280 | name = "logforth" 281 | version = "0.19.1" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "259905c31349678ab17e196b0ca240a729d3d64756fa34e589b9b5debca3b6d6" 284 | dependencies = [ 285 | "anyhow", 286 | "colored", 287 | "env_filter", 288 | "jiff", 289 | "log", 290 | ] 291 | 292 | [[package]] 293 | name = "memchr" 294 | version = "2.7.4" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 297 | 298 | [[package]] 299 | name = "miniz_oxide" 300 | version = "0.8.2" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" 303 | dependencies = [ 304 | "adler2", 305 | ] 306 | 307 | [[package]] 308 | name = "minstant" 309 | version = "0.1.7" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "1fb9b5c752f145ac5046bccc3c4f62892e3c950c1d1eab80c5949cd68a2078db" 312 | dependencies = [ 313 | "ctor", 314 | "web-time", 315 | ] 316 | 317 | [[package]] 318 | name = "object" 319 | version = "0.36.7" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 322 | dependencies = [ 323 | "memchr", 324 | ] 325 | 326 | [[package]] 327 | name = "once_cell" 328 | version = "1.20.2" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 331 | 332 | [[package]] 333 | name = "parking_lot" 334 | version = "0.12.3" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 337 | dependencies = [ 338 | "lock_api", 339 | "parking_lot_core", 340 | ] 341 | 342 | [[package]] 343 | name = "parking_lot_core" 344 | version = "0.9.10" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 347 | dependencies = [ 348 | "cfg-if", 349 | "libc", 350 | "redox_syscall", 351 | "smallvec", 352 | "windows-targets", 353 | ] 354 | 355 | [[package]] 356 | name = "pin-project" 357 | version = "1.1.7" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" 360 | dependencies = [ 361 | "pin-project-internal", 362 | ] 363 | 364 | [[package]] 365 | name = "pin-project-internal" 366 | version = "1.1.7" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" 369 | dependencies = [ 370 | "proc-macro2", 371 | "quote", 372 | "syn 2.0.90", 373 | ] 374 | 375 | [[package]] 376 | name = "pin-project-lite" 377 | version = "0.2.15" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" 380 | 381 | [[package]] 382 | name = "pollster" 383 | version = "0.4.0" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" 386 | 387 | [[package]] 388 | name = "ppv-lite86" 389 | version = "0.2.20" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 392 | dependencies = [ 393 | "zerocopy", 394 | ] 395 | 396 | [[package]] 397 | name = "proc-macro-error-attr2" 398 | version = "2.0.0" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" 401 | dependencies = [ 402 | "proc-macro2", 403 | "quote", 404 | ] 405 | 406 | [[package]] 407 | name = "proc-macro-error2" 408 | version = "2.0.1" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" 411 | dependencies = [ 412 | "proc-macro-error-attr2", 413 | "proc-macro2", 414 | "quote", 415 | "syn 2.0.90", 416 | ] 417 | 418 | [[package]] 419 | name = "proc-macro2" 420 | version = "1.0.92" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 423 | dependencies = [ 424 | "unicode-ident", 425 | ] 426 | 427 | [[package]] 428 | name = "quote" 429 | version = "1.0.37" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 432 | dependencies = [ 433 | "proc-macro2", 434 | ] 435 | 436 | [[package]] 437 | name = "rand" 438 | version = "0.8.5" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 441 | dependencies = [ 442 | "libc", 443 | "rand_chacha", 444 | "rand_core", 445 | ] 446 | 447 | [[package]] 448 | name = "rand_chacha" 449 | version = "0.3.1" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 452 | dependencies = [ 453 | "ppv-lite86", 454 | "rand_core", 455 | ] 456 | 457 | [[package]] 458 | name = "rand_core" 459 | version = "0.6.4" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 462 | dependencies = [ 463 | "getrandom", 464 | ] 465 | 466 | [[package]] 467 | name = "redox_syscall" 468 | version = "0.5.8" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" 471 | dependencies = [ 472 | "bitflags", 473 | ] 474 | 475 | [[package]] 476 | name = "regex" 477 | version = "1.11.1" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 480 | dependencies = [ 481 | "aho-corasick", 482 | "memchr", 483 | "regex-automata", 484 | "regex-syntax", 485 | ] 486 | 487 | [[package]] 488 | name = "regex-automata" 489 | version = "0.4.9" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 492 | dependencies = [ 493 | "aho-corasick", 494 | "memchr", 495 | "regex-syntax", 496 | ] 497 | 498 | [[package]] 499 | name = "regex-syntax" 500 | version = "0.8.5" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 503 | 504 | [[package]] 505 | name = "rtrb" 506 | version = "0.3.1" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "f3f94e84c073f3b85d4012b44722fa8842b9986d741590d4f2636ad0a5b14143" 509 | 510 | [[package]] 511 | name = "rustc-demangle" 512 | version = "0.1.24" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 515 | 516 | [[package]] 517 | name = "ryu" 518 | version = "1.0.18" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 521 | 522 | [[package]] 523 | name = "scopeguard" 524 | version = "1.2.0" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 527 | 528 | [[package]] 529 | name = "serde" 530 | version = "1.0.216" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" 533 | dependencies = [ 534 | "serde_derive", 535 | ] 536 | 537 | [[package]] 538 | name = "serde_derive" 539 | version = "1.0.216" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" 542 | dependencies = [ 543 | "proc-macro2", 544 | "quote", 545 | "syn 2.0.90", 546 | ] 547 | 548 | [[package]] 549 | name = "serde_json" 550 | version = "1.0.133" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" 553 | dependencies = [ 554 | "itoa", 555 | "memchr", 556 | "ryu", 557 | "serde", 558 | ] 559 | 560 | [[package]] 561 | name = "serde_spanned" 562 | version = "0.6.8" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 565 | dependencies = [ 566 | "serde", 567 | ] 568 | 569 | [[package]] 570 | name = "smallvec" 571 | version = "1.13.2" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 574 | 575 | [[package]] 576 | name = "syn" 577 | version = "1.0.109" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 580 | dependencies = [ 581 | "proc-macro2", 582 | "quote", 583 | "unicode-ident", 584 | ] 585 | 586 | [[package]] 587 | name = "syn" 588 | version = "2.0.90" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" 591 | dependencies = [ 592 | "proc-macro2", 593 | "quote", 594 | "unicode-ident", 595 | ] 596 | 597 | [[package]] 598 | name = "target-triple" 599 | version = "0.1.3" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "42a4d50cdb458045afc8131fd91b64904da29548bcb63c7236e0844936c13078" 602 | 603 | [[package]] 604 | name = "termcolor" 605 | version = "1.4.1" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 608 | dependencies = [ 609 | "winapi-util", 610 | ] 611 | 612 | [[package]] 613 | name = "tokio" 614 | version = "1.42.0" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" 617 | dependencies = [ 618 | "backtrace", 619 | "pin-project-lite", 620 | "tokio-macros", 621 | ] 622 | 623 | [[package]] 624 | name = "tokio-macros" 625 | version = "2.4.0" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" 628 | dependencies = [ 629 | "proc-macro2", 630 | "quote", 631 | "syn 2.0.90", 632 | ] 633 | 634 | [[package]] 635 | name = "toml" 636 | version = "0.8.19" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" 639 | dependencies = [ 640 | "serde", 641 | "serde_spanned", 642 | "toml_datetime", 643 | "toml_edit", 644 | ] 645 | 646 | [[package]] 647 | name = "toml_datetime" 648 | version = "0.6.8" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 651 | dependencies = [ 652 | "serde", 653 | ] 654 | 655 | [[package]] 656 | name = "toml_edit" 657 | version = "0.22.22" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" 660 | dependencies = [ 661 | "indexmap", 662 | "serde", 663 | "serde_spanned", 664 | "toml_datetime", 665 | "winnow", 666 | ] 667 | 668 | [[package]] 669 | name = "trybuild" 670 | version = "1.0.101" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "8dcd332a5496c026f1e14b7f3d2b7bd98e509660c04239c58b0ba38a12daded4" 673 | dependencies = [ 674 | "glob", 675 | "serde", 676 | "serde_derive", 677 | "serde_json", 678 | "target-triple", 679 | "termcolor", 680 | "toml", 681 | ] 682 | 683 | [[package]] 684 | name = "unicode-ident" 685 | version = "1.0.14" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 688 | 689 | [[package]] 690 | name = "wasi" 691 | version = "0.11.0+wasi-snapshot-preview1" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 694 | 695 | [[package]] 696 | name = "wasm-bindgen" 697 | version = "0.2.99" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" 700 | dependencies = [ 701 | "cfg-if", 702 | "once_cell", 703 | "wasm-bindgen-macro", 704 | ] 705 | 706 | [[package]] 707 | name = "wasm-bindgen-backend" 708 | version = "0.2.99" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" 711 | dependencies = [ 712 | "bumpalo", 713 | "log", 714 | "proc-macro2", 715 | "quote", 716 | "syn 2.0.90", 717 | "wasm-bindgen-shared", 718 | ] 719 | 720 | [[package]] 721 | name = "wasm-bindgen-macro" 722 | version = "0.2.99" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" 725 | dependencies = [ 726 | "quote", 727 | "wasm-bindgen-macro-support", 728 | ] 729 | 730 | [[package]] 731 | name = "wasm-bindgen-macro-support" 732 | version = "0.2.99" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" 735 | dependencies = [ 736 | "proc-macro2", 737 | "quote", 738 | "syn 2.0.90", 739 | "wasm-bindgen-backend", 740 | "wasm-bindgen-shared", 741 | ] 742 | 743 | [[package]] 744 | name = "wasm-bindgen-shared" 745 | version = "0.2.99" 746 | source = "registry+https://github.com/rust-lang/crates.io-index" 747 | checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" 748 | 749 | [[package]] 750 | name = "web-time" 751 | version = "1.1.0" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 754 | dependencies = [ 755 | "js-sys", 756 | "wasm-bindgen", 757 | ] 758 | 759 | [[package]] 760 | name = "winapi-util" 761 | version = "0.1.9" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 764 | dependencies = [ 765 | "windows-sys", 766 | ] 767 | 768 | [[package]] 769 | name = "windows-sys" 770 | version = "0.59.0" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 773 | dependencies = [ 774 | "windows-targets", 775 | ] 776 | 777 | [[package]] 778 | name = "windows-targets" 779 | version = "0.52.6" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 782 | dependencies = [ 783 | "windows_aarch64_gnullvm", 784 | "windows_aarch64_msvc", 785 | "windows_i686_gnu", 786 | "windows_i686_gnullvm", 787 | "windows_i686_msvc", 788 | "windows_x86_64_gnu", 789 | "windows_x86_64_gnullvm", 790 | "windows_x86_64_msvc", 791 | ] 792 | 793 | [[package]] 794 | name = "windows_aarch64_gnullvm" 795 | version = "0.52.6" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 798 | 799 | [[package]] 800 | name = "windows_aarch64_msvc" 801 | version = "0.52.6" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 804 | 805 | [[package]] 806 | name = "windows_i686_gnu" 807 | version = "0.52.6" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 810 | 811 | [[package]] 812 | name = "windows_i686_gnullvm" 813 | version = "0.52.6" 814 | source = "registry+https://github.com/rust-lang/crates.io-index" 815 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 816 | 817 | [[package]] 818 | name = "windows_i686_msvc" 819 | version = "0.52.6" 820 | source = "registry+https://github.com/rust-lang/crates.io-index" 821 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 822 | 823 | [[package]] 824 | name = "windows_x86_64_gnu" 825 | version = "0.52.6" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 828 | 829 | [[package]] 830 | name = "windows_x86_64_gnullvm" 831 | version = "0.52.6" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 834 | 835 | [[package]] 836 | name = "windows_x86_64_msvc" 837 | version = "0.52.6" 838 | source = "registry+https://github.com/rust-lang/crates.io-index" 839 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 840 | 841 | [[package]] 842 | name = "winnow" 843 | version = "0.6.20" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" 846 | dependencies = [ 847 | "memchr", 848 | ] 849 | 850 | [[package]] 851 | name = "zerocopy" 852 | version = "0.7.35" 853 | source = "registry+https://github.com/rust-lang/crates.io-index" 854 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 855 | dependencies = [ 856 | "byteorder", 857 | "zerocopy-derive", 858 | ] 859 | 860 | [[package]] 861 | name = "zerocopy-derive" 862 | version = "0.7.35" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 865 | dependencies = [ 866 | "proc-macro2", 867 | "quote", 868 | "syn 2.0.90", 869 | ] 870 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "logcall" 3 | version = "0.1.11" 4 | edition = "2021" 5 | rust-version = "1.80.0" 6 | description = "An attribute macro that logs the function return value." 7 | repository = "https://github.com/fast/logcall" 8 | documentation = "https://docs.rs/logcall" 9 | categories = ["development-tools::debugging"] 10 | readme = "README.md" 11 | keywords = ["log", "macro", "derive", "logging", "function"] 12 | license = "MIT" 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | [dependencies] 18 | proc-macro-error2 = { version = "2" } 19 | proc-macro2 = { version = "1" } 20 | quote = { version = "1" } 21 | syn = { version = "2", features = [ 22 | "full", 23 | "parsing", 24 | "extra-traits", 25 | "proc-macro", 26 | "visit-mut", 27 | ] } 28 | 29 | [dev-dependencies] 30 | async-trait = { version = "0.1" } 31 | fastrace = { version = "0.7" } 32 | log = { version = "0.4" } 33 | logforth = { version = "0.19" } 34 | pollster = { version = "0.4" } 35 | tokio = { version = "1", features = ["rt-multi-thread", "macros"] } 36 | trybuild = { version = "1" } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023-2025 FastLabs Developers 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Logcall 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/logcall?style=flat-square&logo=rust)](https://crates.io/crates/logcall) 4 | [![Downloads](https://img.shields.io/crates/d/logcall?style=flat-square&logo=rust)](https://crates.io/crates/logcall) 5 | [![Documentation](https://img.shields.io/docsrs/logcall?style=flat-square&logo=rust)](https://docs.rs/logcall/) 6 | [![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) 7 | [![License](https://img.shields.io/crates/l/logcall?style=flat-square&logo=)](https://crates.io/crates/logcall) 8 | 9 | Logcall is a Rust procedural macro crate designed to automatically log function calls, their inputs, and their outputs. This macro facilitates debugging and monitoring by providing detailed logs of function executions with minimal boilerplate code. 10 | 11 | This is a re-implementation of the [`log-derive`](https://crates.io/crates/log-derive) crate with [`async-trait`](https://crates.io/crates/async-trait) compatibility. 12 | 13 | ## Installation 14 | 15 | Add `logcall` to your `Cargo.toml`: 16 | 17 | ```toml 18 | [dependencies] 19 | logcall = "0.1" 20 | ``` 21 | 22 | ## Usage 23 | 24 | Import the `logcall` crate and use the macro to annotate your functions: 25 | 26 | ```rust 27 | use logcall::logcall; 28 | use logforth::append; 29 | use logforth::filter::EnvFilter; 30 | 31 | /// Logs the function call at the default `debug` level. 32 | #[logcall] 33 | fn add(a: i32, b: i32) -> i32 { 34 | a + b 35 | } 36 | 37 | /// Logs the function call at the `info` level. 38 | #[logcall("info")] 39 | fn multiply(a: i32, b: i32) -> i32 { 40 | a * b 41 | } 42 | 43 | /// Logs `Ok` results at the `info` level and `Err` results at the `error` level. 44 | #[logcall(ok = "info", err = "error")] 45 | fn divide(a: i32, b: i32) -> Result { 46 | if b == 0 { 47 | Err("Division by zero".to_string()) 48 | } else { 49 | Ok(a / b) 50 | } 51 | } 52 | 53 | /// Logs errors at the `error` level. No log output for `Ok` variant. 54 | #[logcall(err = "error")] 55 | fn divide2(a: usize, b: usize) -> Result { 56 | if b == 0 { 57 | Err("Division by zero".to_string()) 58 | } else { 59 | Ok(a / b) 60 | } 61 | } 62 | 63 | /// Logs the function call with custom input logging format. 64 | #[logcall(input = "a = {a:?}, ..")] 65 | fn subtract(a: i32, b: i32) -> i32 { 66 | a - b 67 | } 68 | 69 | fn main() { 70 | logforth::builder() 71 | .dispatch(|d| { 72 | d.filter(EnvFilter::from_default_env_or("trace")) 73 | .append(append::Stderr::default()) 74 | }) 75 | .apply(); 76 | 77 | add(2, 3); 78 | multiply(2, 3); 79 | divide(2, 0).ok(); 80 | divide2(2, 0).ok(); 81 | subtract(3, 2); 82 | } 83 | ``` 84 | 85 | ### Log Output 86 | 87 | When the `main` function runs, it initializes the logger and logs each function call as specified: 88 | 89 | ```plaintext 90 | 2024-12-22T07:02:59.787586+08:00[Asia/Shanghai] DEBUG main: main.rs:6 main::add(a = 2, b = 3) => 5 91 | 2024-12-22T07:02:59.816839+08:00[Asia/Shanghai] INFO main: main.rs:12 main::multiply(a = 2, b = 3) => 6 92 | 2024-12-22T07:02:59.816929+08:00[Asia/Shanghai] ERROR main: main.rs:18 main::divide(a = 2, b = 0) => Err("Division by zero") 93 | 2024-12-22T07:02:59.816957+08:00[Asia/Shanghai] ERROR main: main.rs:28 main::divide2(a = 2, b = 0) => Err("Division by zero") 94 | 2024-12-22T07:02:59.816980+08:00[Asia/Shanghai] DEBUG main: main.rs:38 main::subtract(a = 3, ..) => 1 95 | ``` 96 | 97 | ## Customization 98 | 99 | - **Default Log Level**: If no log level is specified, `logcall` logs at the `debug` level: 100 | ```rust,ignore 101 | #[logcall] 102 | ``` 103 | - **Specify Log Level**: Use the macro parameters to specify log level: 104 | ```rust,ignore 105 | #[logcall("info")] 106 | - **Specify Log Levels for `Result`**: Use the `ok` and `err` parameters to specify log levels for `Ok` and `Err` variants: 107 | ```rust,ignore 108 | #[logcall(err = "error")] 109 | #[logcall(ok = "info", err = "error")] 110 | ``` 111 | - **Customize Input Logging**: Use the `input` parameter to customize the input log format: 112 | ```rust,ignore 113 | #[logcall(input = "a = {a:?}, ..")] 114 | #[logcall("info", input = "a = {a:?}, ..")] 115 | #[logcall(ok = "info", err = "error", input = "a = {a:?}, ..")] 116 | ``` 117 | 118 | ## Minimum Supported Rust Version (MSRV) 119 | 120 | This crate is built against the latest stable release, and its minimum supported rustc version is 1.80.0. 121 | 122 | The policy is that the minimum Rust version required to use this crate can be increased in minor version updates. For example, if Logcall 1.0 requires Rust 1.20.0, then Logcall 1.0.z for all values of z will also require Rust 1.20.0 or newer. However, Logcall 1.y for y > 0 may require a newer minimum version of Rust. 123 | 124 | ## Contributing 125 | 126 | Contributions are welcome! Please submit pull requests or open issues to improve the crate. 127 | 128 | ## License 129 | 130 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. 131 | -------------------------------------------------------------------------------- /examples/main.rs: -------------------------------------------------------------------------------- 1 | use logcall::logcall; 2 | use logforth::append; 3 | use logforth::filter::EnvFilter; 4 | 5 | /// Logs the function call at the default `debug` level. 6 | #[logcall] 7 | fn add(a: i32, b: i32) -> i32 { 8 | a + b 9 | } 10 | 11 | /// Logs the function call at the `info` level. 12 | #[logcall("info")] 13 | fn multiply(a: i32, b: i32) -> i32 { 14 | a * b 15 | } 16 | 17 | /// Logs `Ok` results at the `info` level and `Err` results at the `error` level. 18 | #[logcall(ok = "info", err = "error")] 19 | fn divide(a: i32, b: i32) -> Result { 20 | if b == 0 { 21 | Err("Division by zero".to_string()) 22 | } else { 23 | Ok(a / b) 24 | } 25 | } 26 | 27 | /// Logs errors at the `error` level. No log output for `Ok` variant. 28 | #[logcall(err = "error")] 29 | fn divide2(a: usize, b: usize) -> Result { 30 | if b == 0 { 31 | Err("Division by zero".to_string()) 32 | } else { 33 | Ok(a / b) 34 | } 35 | } 36 | 37 | /// Logs the function call with custom input logging format. 38 | #[logcall(input = "a = {a:?}, ..")] 39 | fn subtract(a: i32, b: i32) -> i32 { 40 | a - b 41 | } 42 | 43 | fn main() { 44 | logforth::builder() 45 | .dispatch(|d| { 46 | d.filter(EnvFilter::from_default_env_or("trace")) 47 | .append(append::Stderr::default()) 48 | }) 49 | .apply(); 50 | 51 | add(2, 3); 52 | multiply(2, 3); 53 | divide(2, 0).ok(); 54 | divide2(2, 0).ok(); 55 | subtract(3, 2); 56 | } 57 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | components = ["cargo", "rustfmt", "clippy", "rust-analyzer"] 4 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | // Instrumenting the async fn is not as straight forward as expected because `async_trait` 4 | // rewrites `async fn` into a normal fn which returns `Box`, and this stops 5 | // the macro from distinguishing `async fn` from `fn`. 6 | // 7 | // The following code reused the `async_trait` probes from tokio-tracing [1]. 8 | // 9 | // [1] https://github.com/tokio-rs/tracing/blob/6a61897a/tracing-attributes/src/expand.rs 10 | 11 | use proc_macro2::Span; 12 | use proc_macro_error2::abort_call_site; 13 | use proc_macro_error2::proc_macro_error; 14 | use syn::parse::Parse; 15 | use syn::parse::ParseStream; 16 | use syn::spanned::Spanned; 17 | use syn::Block; 18 | use syn::Expr; 19 | use syn::ExprAsync; 20 | use syn::ExprCall; 21 | use syn::FnArg; 22 | use syn::Generics; 23 | use syn::Ident; 24 | use syn::Item; 25 | use syn::ItemFn; 26 | use syn::LitStr; 27 | use syn::Pat; 28 | use syn::PatType; 29 | use syn::Path; 30 | use syn::Signature; 31 | use syn::Stmt; 32 | use syn::Token; 33 | 34 | #[derive(Debug)] 35 | enum Args { 36 | Simple { 37 | level: String, 38 | input_format: Option, 39 | }, 40 | Result { 41 | ok_level: Option, 42 | err_level: Option, 43 | input_format: Option, 44 | }, 45 | Option { 46 | some_level: Option, 47 | none_level: Option, 48 | input_format: Option, 49 | }, 50 | } 51 | 52 | impl Parse for Args { 53 | fn parse(input: ParseStream) -> syn::Result { 54 | #[derive(Default)] 55 | struct ArgContext { 56 | simple_level: Option, 57 | ok_level: Option, 58 | err_level: Option, 59 | some_level: Option, 60 | none_level: Option, 61 | input_format: Option, 62 | } 63 | 64 | impl Parse for ArgContext { 65 | fn parse(input: ParseStream) -> syn::Result { 66 | let mut ctx = ArgContext::default(); 67 | loop { 68 | if input.is_empty() { 69 | return Ok(ctx); 70 | } 71 | 72 | if input.peek(LitStr) { 73 | let level = input.parse::()?; 74 | if ctx.simple_level.is_some() { 75 | return Err(syn::Error::new( 76 | level.span(), 77 | "simple_level specified multiple times", 78 | )); 79 | } 80 | ctx.simple_level = Some(level.value()); 81 | } else if input.peek(Ident) { 82 | let ident = input.parse::()?; 83 | input.parse::()?; 84 | let level = input.parse::()?; 85 | match ident.to_string().as_str() { 86 | "some" => { 87 | if ctx.some_level.is_some() { 88 | return Err(syn::Error::new( 89 | level.span(), 90 | "some_level specified multiple times", 91 | )); 92 | } 93 | ctx.some_level = Some(level.value()); 94 | } 95 | "none" => { 96 | if ctx.none_level.is_some() { 97 | return Err(syn::Error::new( 98 | level.span(), 99 | "none_level specified multiple times", 100 | )); 101 | } 102 | ctx.none_level = Some(level.value()); 103 | } 104 | "ok" => { 105 | if ctx.ok_level.is_some() { 106 | return Err(syn::Error::new( 107 | level.span(), 108 | "ok_level specified multiple times", 109 | )); 110 | } 111 | ctx.ok_level = Some(level.value()); 112 | } 113 | "err" => { 114 | if ctx.err_level.is_some() { 115 | return Err(syn::Error::new( 116 | level.span(), 117 | "err_level specified multiple times", 118 | )); 119 | } 120 | ctx.err_level = Some(level.value()); 121 | } 122 | "input" => { 123 | if ctx.input_format.is_some() { 124 | return Err(syn::Error::new( 125 | level.span(), 126 | "input specified multiple times", 127 | )); 128 | } 129 | ctx.input_format = Some(level.value()); 130 | } 131 | _ => { 132 | return Err(syn::Error::new( 133 | ident.span(), 134 | "unknown attribute argument", 135 | )) 136 | } 137 | } 138 | } else { 139 | return Err(input.error("unexpected token")); 140 | } 141 | 142 | if input.is_empty() { 143 | return Ok(ctx); 144 | } 145 | input.parse::()?; 146 | } 147 | } 148 | } 149 | 150 | let ArgContext { 151 | simple_level, 152 | ok_level, 153 | err_level, 154 | some_level, 155 | none_level, 156 | input_format, 157 | } = input.parse::()?; 158 | 159 | if ok_level.is_some() || err_level.is_some() { 160 | if simple_level.is_some() { 161 | abort_call_site!("plain level cannot be specified with `ok` or `err` levels"); 162 | } 163 | if some_level.is_some() || none_level.is_some() { 164 | abort_call_site!( 165 | "`some` and `none` levels cannot be specified with `ok` or `err` levels" 166 | ); 167 | } 168 | Ok(Args::Result { 169 | ok_level, 170 | err_level, 171 | input_format, 172 | }) 173 | } else if some_level.is_some() || none_level.is_some() { 174 | if simple_level.is_some() { 175 | abort_call_site!("plain level cannot be specified with `some` or `none` levels"); 176 | } 177 | Ok(Args::Option { 178 | some_level, 179 | none_level, 180 | input_format, 181 | }) 182 | } else { 183 | Ok(Args::Simple { 184 | level: simple_level.unwrap_or_else(|| "info".to_string()), 185 | input_format, 186 | }) 187 | } 188 | } 189 | } 190 | 191 | /// `logcall` attribute macro that logs the function inputs and return values. 192 | #[proc_macro_attribute] 193 | #[proc_macro_error] 194 | pub fn logcall( 195 | args: proc_macro::TokenStream, 196 | item: proc_macro::TokenStream, 197 | ) -> proc_macro::TokenStream { 198 | let input = syn::parse_macro_input!(item as ItemFn); 199 | 200 | let args = syn::parse_macro_input!(args as Args); 201 | 202 | // check for async_trait-like patterns in the block, and instrument 203 | // the future instead of the wrapper 204 | let func_body = if let Some(internal_fun) = 205 | get_async_trait_info(&input.block, input.sig.asyncness.is_some()) 206 | { 207 | // let's rewrite some statements! 208 | match internal_fun.kind { 209 | // async-trait <= 0.1.43 210 | AsyncTraitKind::Function => { 211 | unimplemented!( 212 | "Please upgrade the crate `async-trait` to a version higher than 0.1.44" 213 | ) 214 | } 215 | // async-trait >= 0.1.44 216 | AsyncTraitKind::Async(async_expr) => { 217 | // fallback if we couldn't find the '__async_trait' binding, might be 218 | // useful for crates exhibiting the same behaviors as async-trait 219 | let instrumented_block = 220 | gen_block(&async_expr.block, true, false, &input.sig, args); 221 | let async_attrs = &async_expr.attrs; 222 | quote::quote_spanned! {async_expr.span()=> 223 | Box::pin(#(#async_attrs) * #instrumented_block ) 224 | } 225 | } 226 | } 227 | } else { 228 | gen_block( 229 | &input.block, 230 | input.sig.asyncness.is_some(), 231 | input.sig.asyncness.is_some(), 232 | &input.sig, 233 | args, 234 | ) 235 | }; 236 | 237 | let ItemFn { 238 | attrs, vis, sig, .. 239 | } = input.clone(); 240 | 241 | let Signature { 242 | output: return_type, 243 | inputs: params, 244 | unsafety, 245 | constness, 246 | abi, 247 | ident, 248 | asyncness, 249 | generics: 250 | Generics { 251 | params: gen_params, 252 | where_clause, 253 | .. 254 | }, 255 | .. 256 | } = sig; 257 | 258 | quote::quote_spanned!(input.span()=> 259 | #(#attrs) * 260 | #vis #constness #unsafety #asyncness #abi fn #ident<#gen_params>(#params) #return_type 261 | #where_clause 262 | { 263 | #func_body 264 | } 265 | ) 266 | .into() 267 | } 268 | 269 | /// Instrument a block 270 | fn gen_block( 271 | block: &Block, 272 | async_context: bool, 273 | async_keyword: bool, 274 | sig: &Signature, 275 | args: Args, 276 | ) -> proc_macro2::TokenStream { 277 | match args { 278 | Args::Simple { 279 | level, 280 | input_format, 281 | } => gen_plain_label_block( 282 | block, 283 | async_context, 284 | async_keyword, 285 | sig, 286 | &level, 287 | input_format, 288 | ), 289 | Args::Result { 290 | ok_level, 291 | err_level, 292 | input_format, 293 | } => gen_result_label_block( 294 | block, 295 | async_context, 296 | async_keyword, 297 | sig, 298 | ok_level, 299 | err_level, 300 | input_format, 301 | ), 302 | Args::Option { 303 | some_level, 304 | none_level, 305 | input_format, 306 | } => gen_option_label_block( 307 | block, 308 | async_context, 309 | async_keyword, 310 | sig, 311 | some_level, 312 | none_level, 313 | input_format, 314 | ), 315 | } 316 | } 317 | 318 | fn gen_plain_label_block( 319 | block: &Block, 320 | async_context: bool, 321 | async_keyword: bool, 322 | sig: &Signature, 323 | level: &str, 324 | input_format: Option, 325 | ) -> proc_macro2::TokenStream { 326 | // Generate the instrumented function body. 327 | // If the function is an `async fn`, this will wrap it in an async block. 328 | if async_context { 329 | let input_format = input_format.unwrap_or_else(|| gen_input_format(sig)); 330 | let log = gen_log(level, "__input_string", "__ret_value"); 331 | let block = quote::quote_spanned!(block.span()=> 332 | #[allow(unknown_lints)] 333 | #[allow(clippy::useless_format)] 334 | let __input_string = format!(#input_format); 335 | #[allow(unknown_lints)] 336 | let __ret_value = async { #block }.await; 337 | #log; 338 | __ret_value 339 | ); 340 | 341 | if async_keyword { 342 | block 343 | } else { 344 | quote::quote_spanned!(block.span()=> 345 | async move { 346 | #block 347 | } 348 | ) 349 | } 350 | } else { 351 | let input_format = input_format.unwrap_or_else(|| gen_input_format(sig)); 352 | let log = gen_log(level, "__input_string", "__ret_value"); 353 | quote::quote_spanned!(block.span()=> 354 | #[allow(unknown_lints)] 355 | #[allow(clippy::useless_format)] 356 | let __input_string = format!(#input_format); 357 | #[allow(unknown_lints)] 358 | #[allow(clippy::redundant_closure_call)] 359 | #[allow(clippy::let_unit_value)] 360 | let __ret_value = (move || #block)(); 361 | #log; 362 | __ret_value 363 | ) 364 | } 365 | } 366 | 367 | fn gen_result_label_block( 368 | block: &Block, 369 | async_context: bool, 370 | async_keyword: bool, 371 | sig: &Signature, 372 | ok_level: Option, 373 | err_level: Option, 374 | input_format: Option, 375 | ) -> proc_macro2::TokenStream { 376 | let ok_arm = if let Some(ok_level) = ok_level { 377 | let log_ok = gen_log(&ok_level, "__input_string", "__ret_value"); 378 | quote::quote_spanned!(block.span()=> 379 | __ret_value@Ok(_) => { 380 | #log_ok; 381 | __ret_value 382 | } 383 | ) 384 | } else { 385 | quote::quote_spanned!(block.span()=> 386 | Ok(__ret_value) => Ok(__ret_value), 387 | ) 388 | }; 389 | let err_arm = if let Some(err_level) = err_level { 390 | let log_err = gen_log(&err_level, "__input_string", "__ret_value"); 391 | quote::quote_spanned!(block.span()=> 392 | __ret_value@Err(_) => { 393 | #log_err; 394 | __ret_value 395 | } 396 | ) 397 | } else { 398 | quote::quote_spanned!(block.span()=> 399 | Err(__ret_value) => Err(__ret_value), 400 | ) 401 | }; 402 | 403 | // Generate the instrumented function body. 404 | // If the function is an `async fn`, this will wrap it in an async block. 405 | if async_context { 406 | let input_format = input_format.unwrap_or_else(|| gen_input_format(sig)); 407 | let block = quote::quote_spanned!(block.span()=> 408 | #[allow(unknown_lints)] 409 | #[allow(clippy::useless_format)] 410 | let __input_string = format!(#input_format); 411 | #[allow(unknown_lints)] 412 | let __ret_value = async { #block }.await; 413 | match __ret_value { 414 | #ok_arm 415 | #err_arm 416 | } 417 | ); 418 | 419 | if async_keyword { 420 | block 421 | } else { 422 | quote::quote_spanned!(block.span()=> 423 | async move { 424 | #block 425 | } 426 | ) 427 | } 428 | } else { 429 | let input_format = input_format.unwrap_or_else(|| gen_input_format(sig)); 430 | quote::quote_spanned!(block.span()=> 431 | #[allow(unknown_lints)] 432 | #[allow(clippy::useless_format)] 433 | let __input_string = format!(#input_format); 434 | #[allow(unknown_lints)] 435 | #[allow(clippy::redundant_closure_call)] 436 | #[allow(clippy::let_unit_value)] 437 | let __ret_value = (move || #block)(); 438 | match __ret_value { 439 | #ok_arm 440 | #err_arm 441 | } 442 | ) 443 | } 444 | } 445 | 446 | fn gen_option_label_block( 447 | block: &Block, 448 | async_context: bool, 449 | async_keyword: bool, 450 | sig: &Signature, 451 | some_level: Option, 452 | none_level: Option, 453 | input_format: Option, 454 | ) -> proc_macro2::TokenStream { 455 | let some_arm = if let Some(some_level) = some_level { 456 | let log_some = gen_log(&some_level, "__input_string", "__ret_value"); 457 | quote::quote_spanned!(block.span()=> 458 | __ret_value@Some(_) => { 459 | #log_some; 460 | __ret_value 461 | } 462 | ) 463 | } else { 464 | quote::quote_spanned!(block.span()=> 465 | Some(__ret_value) => Some(__ret_value), 466 | ) 467 | }; 468 | let none_arm = if let Some(none_level) = none_level { 469 | let log_none = gen_log(&none_level, "__input_string", "__ret_value"); 470 | quote::quote_spanned!(block.span()=> 471 | None => { 472 | #log_none; 473 | None 474 | } 475 | ) 476 | } else { 477 | quote::quote_spanned!(block.span()=> 478 | None => None, 479 | ) 480 | }; 481 | 482 | // Generate the instrumented function body. 483 | // If the function is an `async fn`, this will wrap it in an async block. 484 | if async_context { 485 | let input_format = input_format.unwrap_or_else(|| gen_input_format(sig)); 486 | let block = quote::quote_spanned!(block.span()=> 487 | #[allow(unknown_lints)] 488 | #[allow(clippy::useless_format)] 489 | let __input_string = format!(#input_format); 490 | #[allow(unknown_lints)] 491 | let __ret_value = async { #block }.await; 492 | match __ret_value { 493 | #some_arm 494 | #none_arm 495 | } 496 | ); 497 | 498 | if async_keyword { 499 | block 500 | } else { 501 | quote::quote_spanned!(block.span()=> 502 | async move { 503 | #block 504 | } 505 | ) 506 | } 507 | } else { 508 | let input_format = input_format.unwrap_or_else(|| gen_input_format(sig)); 509 | quote::quote_spanned!(block.span()=> 510 | #[allow(unknown_lints)] 511 | #[allow(clippy::useless_format)] 512 | let __input_string = format!(#input_format); 513 | #[allow(unknown_lints)] 514 | #[allow(clippy::redundant_closure_call)] 515 | #[allow(clippy::let_unit_value)] 516 | let __ret_value = (move || #block)(); 517 | match __ret_value { 518 | #some_arm 519 | #none_arm 520 | } 521 | ) 522 | } 523 | } 524 | 525 | fn gen_log(level: &str, input_string: &str, return_value: &str) -> proc_macro2::TokenStream { 526 | let level = level.to_lowercase(); 527 | if !["error", "warn", "info", "debug", "trace"].contains(&level.as_str()) { 528 | abort_call_site!("unknown log level"); 529 | } 530 | let level: Ident = Ident::new(&level, Span::call_site()); 531 | let input_string: Ident = Ident::new(input_string, Span::call_site()); 532 | let return_value: Ident = Ident::new(return_value, Span::call_site()); 533 | let fn_name = quote::quote! { 534 | { 535 | fn f() {} 536 | fn type_name_of(_: T) -> &'static str { 537 | std::any::type_name::() 538 | } 539 | let name = type_name_of(f); 540 | let name = &name[..name.len() - 3]; 541 | name.trim_end_matches("::{{closure}}") 542 | } 543 | }; 544 | quote::quote!( 545 | log::#level! ("{}({}) => {:?}", #fn_name, #input_string, &#return_value) 546 | ) 547 | } 548 | 549 | // fn(a: usize, b: usize) => "a = {a:?}, b = {b:?}" 550 | fn gen_input_format(sig: &Signature) -> String { 551 | let mut input_format = String::new(); 552 | for (i, input) in sig.inputs.iter().enumerate() { 553 | if i > 0 { 554 | input_format.push_str(", "); 555 | } 556 | match input { 557 | FnArg::Typed(PatType { pat, .. }) => { 558 | if let Pat::Ident(pat_ident) = &**pat { 559 | let ident = &pat_ident.ident; 560 | input_format.push_str(&format!("{ident} = {{{ident}:?}}")); 561 | } 562 | } 563 | FnArg::Receiver(_) => { 564 | input_format.push_str("self"); 565 | } 566 | } 567 | } 568 | input_format 569 | } 570 | 571 | enum AsyncTraitKind<'a> { 572 | // old construction. Contains the function 573 | Function, 574 | // new construction. Contains a reference to the async block 575 | Async(&'a ExprAsync), 576 | } 577 | 578 | struct AsyncTraitInfo<'a> { 579 | // statement that must be patched 580 | _source_stmt: &'a Stmt, 581 | kind: AsyncTraitKind<'a>, 582 | } 583 | 584 | // Get the AST of the inner function we need to hook, if it was generated 585 | // by async-trait. 586 | // When we are given a function annotated by async-trait, that function 587 | // is only a placeholder that returns a pinned future containing the 588 | // user logic, and it is that pinned future that needs to be instrumented. 589 | // Were we to instrument its parent, we would only collect information 590 | // regarding the allocation of that future, and not its own span of execution. 591 | // Depending on the version of async-trait, we inspect the block of the function 592 | // to find if it matches the pattern 593 | // `async fn foo<...>(...) {...}; Box::pin(foo<...>(...))` (<=0.1.43), or if 594 | // it matches `Box::pin(async move { ... }) (>=0.1.44). We the return the 595 | // statement that must be instrumented, along with some other information. 596 | // 'gen_body' will then be able to use that information to instrument the 597 | // proper function/future. 598 | // (this follows the approach suggested in 599 | // https://github.com/dtolnay/async-trait/issues/45#issuecomment-571245673) 600 | fn get_async_trait_info(block: &Block, block_is_async: bool) -> Option> { 601 | // are we in an async context? If yes, this isn't an async_trait-like pattern 602 | if block_is_async { 603 | return None; 604 | } 605 | 606 | // list of async functions declared inside the block 607 | let inside_fns = block.stmts.iter().filter_map(|stmt| { 608 | if let Stmt::Item(Item::Fn(fun)) = &stmt { 609 | // If the function is async, this is a candidate 610 | if fun.sig.asyncness.is_some() { 611 | return Some((stmt, fun)); 612 | } 613 | } 614 | None 615 | }); 616 | 617 | // last expression of the block (it determines the return value 618 | // of the block, so that if we are working on a function whose 619 | // `trait` or `impl` declaration is annotated by async_trait, 620 | // this is quite likely the point where the future is pinned) 621 | let (last_expr_stmt, last_expr) = block.stmts.iter().rev().find_map(|stmt| { 622 | if let Stmt::Expr(expr, ..) = stmt { 623 | Some((stmt, expr)) 624 | } else { 625 | None 626 | } 627 | })?; 628 | 629 | // is the last expression a function call? 630 | let (outside_func, outside_args) = match last_expr { 631 | Expr::Call(ExprCall { func, args, .. }) => (func, args), 632 | _ => return None, 633 | }; 634 | 635 | // is it a call to `Box::pin()`? 636 | let path = match outside_func.as_ref() { 637 | Expr::Path(path) => &path.path, 638 | _ => return None, 639 | }; 640 | if !path_to_string(path).ends_with("Box::pin") { 641 | return None; 642 | } 643 | 644 | // Does the call take an argument? If it doesn't, 645 | // it's not going to compile anyway, but that's no reason 646 | // to (try to) perform an out-of-bounds access 647 | if outside_args.is_empty() { 648 | return None; 649 | } 650 | 651 | // Is the argument to Box::pin an async block that 652 | // captures its arguments? 653 | if let Expr::Async(async_expr) = &outside_args[0] { 654 | // check that the move 'keyword' is present 655 | async_expr.capture?; 656 | 657 | return Some(AsyncTraitInfo { 658 | _source_stmt: last_expr_stmt, 659 | kind: AsyncTraitKind::Async(async_expr), 660 | }); 661 | } 662 | 663 | // Is the argument to Box::pin a function call itself? 664 | let func = match &outside_args[0] { 665 | Expr::Call(ExprCall { func, .. }) => func, 666 | _ => return None, 667 | }; 668 | 669 | // "stringify" the path of the function called 670 | let func_name = match **func { 671 | Expr::Path(ref func_path) => path_to_string(&func_path.path), 672 | _ => return None, 673 | }; 674 | 675 | // Was that function defined inside the current block? 676 | // If so, retrieve the statement where it was declared and the function itself 677 | let (stmt_func_declaration, _) = inside_fns 678 | .into_iter() 679 | .find(|(_, fun)| fun.sig.ident == func_name)?; 680 | 681 | Some(AsyncTraitInfo { 682 | _source_stmt: stmt_func_declaration, 683 | kind: AsyncTraitKind::Function, 684 | }) 685 | } 686 | 687 | // Return a path as a String 688 | fn path_to_string(path: &Path) -> String { 689 | use std::fmt::Write; 690 | // some heuristic to prevent too many allocations 691 | let mut res = String::with_capacity(path.segments.len() * 5); 692 | for i in 0..path.segments.len() { 693 | write!(res, "{}", path.segments[i].ident).expect("writing to a String should never fail"); 694 | if i < path.segments.len() - 1 { 695 | res.push_str("::"); 696 | } 697 | } 698 | res 699 | } 700 | -------------------------------------------------------------------------------- /tests/ui.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn ui() { 3 | let t = trybuild::TestCases::new(); 4 | t.compile_fail("tests/ui/err/*.rs"); 5 | t.pass("tests/ui/ok/*.rs"); 6 | } 7 | -------------------------------------------------------------------------------- /tests/ui/err/error-passthrough.rs: -------------------------------------------------------------------------------- 1 | #[logcall::logcall] 2 | fn f() {} 3 | 4 | #[logcall::logcall] 5 | fn f() {} 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /tests/ui/err/error-passthrough.stderr: -------------------------------------------------------------------------------- 1 | error[E0428]: the name `f` is defined multiple times 2 | --> tests/ui/err/error-passthrough.rs:5:1 3 | | 4 | 2 | fn f() {} 5 | | -- previous definition of the value `f` here 6 | ... 7 | 5 | fn f() {} 8 | | ^^ `f` redefined here 9 | | 10 | = note: `f` must be defined only once in the value namespace of this module 11 | -------------------------------------------------------------------------------- /tests/ui/err/has-too-many-arguments.rs: -------------------------------------------------------------------------------- 1 | #[logcall::logcall("info", "error")] 2 | fn f() {} 3 | 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /tests/ui/err/has-too-many-arguments.stderr: -------------------------------------------------------------------------------- 1 | error: simple_level specified multiple times 2 | --> tests/ui/err/has-too-many-arguments.rs:1:28 3 | | 4 | 1 | #[logcall::logcall("info", "error")] 5 | | ^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/err/item-is-not-a-function.rs: -------------------------------------------------------------------------------- 1 | #[logcall::logcall("info")] 2 | struct S; 3 | 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /tests/ui/err/item-is-not-a-function.stderr: -------------------------------------------------------------------------------- 1 | error: expected `fn` 2 | --> tests/ui/err/item-is-not-a-function.rs:2:1 3 | | 4 | 2 | struct S; 5 | | ^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/err/multiple-args.rs: -------------------------------------------------------------------------------- 1 | #[logcall::logcall("info", ok = "info")] 2 | fn f0() {} 3 | 4 | #[logcall::logcall("info", err = "info")] 5 | fn f1() {} 6 | 7 | #[logcall::logcall("info", ok = "info", err = "info")] 8 | fn f2() {} 9 | 10 | #[logcall::logcall("info", some = "info")] 11 | fn f3() {} 12 | 13 | #[logcall::logcall("info", none = "info")] 14 | fn f4() {} 15 | 16 | #[logcall::logcall("info", some = "info", none = "info")] 17 | fn f5() {} 18 | 19 | #[logcall::logcall(some = "info", ok = "info")] 20 | fn f6() {} 21 | 22 | #[logcall::logcall(ok = "info", some = "info")] 23 | fn f7() {} 24 | 25 | #[logcall::logcall(some = "info", none = "info", ok = "info", err = "info")] 26 | fn f8() {} 27 | 28 | #[logcall::logcall("info", some = "info", none = "info", ok = "info", err = "info")] 29 | fn f9() {} 30 | 31 | fn main() {} 32 | -------------------------------------------------------------------------------- /tests/ui/err/multiple-args.stderr: -------------------------------------------------------------------------------- 1 | error: plain level cannot be specified with `ok` or `err` levels 2 | --> tests/ui/err/multiple-args.rs:1:1 3 | | 4 | 1 | #[logcall::logcall("info", ok = "info")] 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = note: this error originates in the attribute macro `logcall::logcall` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | 9 | error: plain level cannot be specified with `ok` or `err` levels 10 | --> tests/ui/err/multiple-args.rs:4:1 11 | | 12 | 4 | #[logcall::logcall("info", err = "info")] 13 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 14 | | 15 | = note: this error originates in the attribute macro `logcall::logcall` (in Nightly builds, run with -Z macro-backtrace for more info) 16 | 17 | error: plain level cannot be specified with `ok` or `err` levels 18 | --> tests/ui/err/multiple-args.rs:7:1 19 | | 20 | 7 | #[logcall::logcall("info", ok = "info", err = "info")] 21 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 22 | | 23 | = note: this error originates in the attribute macro `logcall::logcall` (in Nightly builds, run with -Z macro-backtrace for more info) 24 | 25 | error: plain level cannot be specified with `some` or `none` levels 26 | --> tests/ui/err/multiple-args.rs:10:1 27 | | 28 | 10 | #[logcall::logcall("info", some = "info")] 29 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 30 | | 31 | = note: this error originates in the attribute macro `logcall::logcall` (in Nightly builds, run with -Z macro-backtrace for more info) 32 | 33 | error: plain level cannot be specified with `some` or `none` levels 34 | --> tests/ui/err/multiple-args.rs:13:1 35 | | 36 | 13 | #[logcall::logcall("info", none = "info")] 37 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 38 | | 39 | = note: this error originates in the attribute macro `logcall::logcall` (in Nightly builds, run with -Z macro-backtrace for more info) 40 | 41 | error: plain level cannot be specified with `some` or `none` levels 42 | --> tests/ui/err/multiple-args.rs:16:1 43 | | 44 | 16 | #[logcall::logcall("info", some = "info", none = "info")] 45 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 46 | | 47 | = note: this error originates in the attribute macro `logcall::logcall` (in Nightly builds, run with -Z macro-backtrace for more info) 48 | 49 | error: `some` and `none` levels cannot be specified with `ok` or `err` levels 50 | --> tests/ui/err/multiple-args.rs:19:1 51 | | 52 | 19 | #[logcall::logcall(some = "info", ok = "info")] 53 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 54 | | 55 | = note: this error originates in the attribute macro `logcall::logcall` (in Nightly builds, run with -Z macro-backtrace for more info) 56 | 57 | error: `some` and `none` levels cannot be specified with `ok` or `err` levels 58 | --> tests/ui/err/multiple-args.rs:22:1 59 | | 60 | 22 | #[logcall::logcall(ok = "info", some = "info")] 61 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 62 | | 63 | = note: this error originates in the attribute macro `logcall::logcall` (in Nightly builds, run with -Z macro-backtrace for more info) 64 | 65 | error: `some` and `none` levels cannot be specified with `ok` or `err` levels 66 | --> tests/ui/err/multiple-args.rs:25:1 67 | | 68 | 25 | #[logcall::logcall(some = "info", none = "info", ok = "info", err = "info")] 69 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 70 | | 71 | = note: this error originates in the attribute macro `logcall::logcall` (in Nightly builds, run with -Z macro-backtrace for more info) 72 | 73 | error: plain level cannot be specified with `ok` or `err` levels 74 | --> tests/ui/err/multiple-args.rs:28:1 75 | | 76 | 28 | #[logcall::logcall("info", some = "info", none = "info", ok = "info", err = "info")] 77 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 78 | | 79 | = note: this error originates in the attribute macro `logcall::logcall` (in Nightly builds, run with -Z macro-backtrace for more info) 80 | -------------------------------------------------------------------------------- /tests/ui/err/wrong-level.rs: -------------------------------------------------------------------------------- 1 | #[logcall::logcall("foo")] 2 | fn f() {} 3 | 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /tests/ui/err/wrong-level.stderr: -------------------------------------------------------------------------------- 1 | error: unknown log level 2 | --> tests/ui/err/wrong-level.rs:1:1 3 | | 4 | 1 | #[logcall::logcall("foo")] 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = note: this error originates in the attribute macro `logcall::logcall` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | -------------------------------------------------------------------------------- /tests/ui/ok/async-in-trait.rs: -------------------------------------------------------------------------------- 1 | trait MyTrait { 2 | async fn work(&self) -> Result; 3 | async fn run(&self) -> Option; 4 | } 5 | 6 | struct MyStruct; 7 | 8 | impl MyTrait for MyStruct { 9 | #[logcall::logcall("debug")] 10 | #[logcall::logcall(ok = "debug", err = "error")] 11 | async fn work(&self) -> Result { 12 | Ok(1) 13 | } 14 | 15 | #[logcall::logcall("debug")] 16 | #[logcall::logcall(some = "debug", none = "error")] 17 | async fn run(&self) -> Option { 18 | Some(1) 19 | } 20 | } 21 | 22 | fn main() {} 23 | -------------------------------------------------------------------------------- /tests/ui/ok/async-mut.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_mut)] 2 | 3 | #[logcall::logcall("warn")] 4 | async fn f(mut a: u32) -> u32 { 5 | a 6 | } 7 | 8 | #[tokio::main] 9 | async fn main() { 10 | f(1).await; 11 | } 12 | -------------------------------------------------------------------------------- /tests/ui/ok/async-trait.rs: -------------------------------------------------------------------------------- 1 | #[async_trait::async_trait] 2 | trait MyTrait { 3 | async fn work(&self) -> usize; 4 | } 5 | 6 | struct MyStruct; 7 | 8 | #[async_trait::async_trait] 9 | impl MyTrait for MyStruct { 10 | #[logcall::logcall("debug")] 11 | #[logcall::logcall("debug")] 12 | async fn work(&self) -> usize { 13 | todo!() 14 | } 15 | } 16 | 17 | fn main() {} 18 | -------------------------------------------------------------------------------- /tests/ui/ok/async.rs: -------------------------------------------------------------------------------- 1 | #[logcall::logcall("info")] 2 | async fn f(a: u32) -> u32 { 3 | a 4 | } 5 | 6 | #[logcall::logcall(ok = "info")] 7 | async fn g(a: u32) -> Result { 8 | Ok(a) 9 | } 10 | 11 | #[logcall::logcall(err = "info")] 12 | async fn h(a: u32) -> Result { 13 | Ok(a) 14 | } 15 | 16 | #[logcall::logcall(ok = "info", err = "info")] 17 | async fn i(a: u32) -> Result { 18 | Ok(a) 19 | } 20 | 21 | #[logcall::logcall(some = "info")] 22 | async fn j(a: u32) -> Option { 23 | Some(a) 24 | } 25 | 26 | #[logcall::logcall(none = "info")] 27 | async fn k(a: u32) -> Option { 28 | Some(a) 29 | } 30 | 31 | #[logcall::logcall(some = "info", none = "info")] 32 | async fn l(a: u32) -> Option { 33 | Some(a) 34 | } 35 | 36 | #[tokio::main] 37 | async fn main() { 38 | f(1).await; 39 | g(1).await.ok(); 40 | h(1).await.ok(); 41 | i(1).await.ok(); 42 | j(1).await.unwrap(); 43 | k(1).await.unwrap(); 44 | l(1).await.unwrap(); 45 | } 46 | -------------------------------------------------------------------------------- /tests/ui/ok/fastrace.rs: -------------------------------------------------------------------------------- 1 | #[logcall::logcall] 2 | #[fastrace::trace] 3 | fn f() {} 4 | 5 | #[logcall::logcall] 6 | #[fastrace::trace] 7 | async fn g() { 8 | std::future::ready(1).await; 9 | } 10 | 11 | fn main() { 12 | f(); 13 | pollster::block_on(g()); 14 | } 15 | -------------------------------------------------------------------------------- /tests/ui/ok/has-no-argument.rs: -------------------------------------------------------------------------------- 1 | #[logcall::logcall] 2 | fn f() {} 3 | 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /tests/ui/ok/sync-mut.rs: -------------------------------------------------------------------------------- 1 | #[logcall::logcall("info")] 2 | fn f(mut a: u32) -> u32 { 3 | a += 1; 4 | a 5 | } 6 | 7 | fn main() { 8 | f(1); 9 | } 10 | -------------------------------------------------------------------------------- /tests/ui/ok/sync.rs: -------------------------------------------------------------------------------- 1 | #[logcall::logcall("info")] 2 | fn f(a: u32) -> u32 { 3 | a 4 | } 5 | 6 | #[logcall::logcall(ok = "info")] 7 | fn g(a: u32) -> Result { 8 | Ok(a) 9 | } 10 | 11 | #[logcall::logcall(err = "info")] 12 | fn h(a: u32) -> Result { 13 | Ok(a) 14 | } 15 | 16 | #[logcall::logcall(ok = "info", err = "info")] 17 | fn i(a: u32) -> Result { 18 | Ok(a) 19 | } 20 | 21 | #[logcall::logcall(some = "info")] 22 | fn j(a: u32) -> Option { 23 | Some(a) 24 | } 25 | 26 | #[logcall::logcall(none = "info")] 27 | fn k(a: u32) -> Option { 28 | Some(a) 29 | } 30 | 31 | #[logcall::logcall(some = "info", none = "info")] 32 | fn l(a: u32) -> Option { 33 | Some(a) 34 | } 35 | 36 | fn main() { 37 | f(1); 38 | g(1).ok(); 39 | h(1).ok(); 40 | i(1).ok(); 41 | j(1).unwrap(); 42 | k(1).unwrap(); 43 | l(1).unwrap(); 44 | } 45 | -------------------------------------------------------------------------------- /tests/ui/ok/unreachable.rs: -------------------------------------------------------------------------------- 1 | #[logcall::logcall("info")] 2 | async fn f(a: u32) -> u32 { 3 | if a == 1 { 4 | return 1; 5 | } 6 | 7 | unreachable!() 8 | } 9 | 10 | #[tokio::main] 11 | async fn main() { 12 | f(1).await; 13 | } 14 | --------------------------------------------------------------------------------