aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFedor Tsarev <ftsarev@google.com>2023-03-27 08:32:47 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2023-03-27 08:32:47 +0000
commitded42890bd1816dcaacd3b921bcf85d2683f7d94 (patch)
treec6db64add42fc068987c8c9828ea19c1b7f3211f
parenta0148f6119c0f3eaa23497b0f7e7cc8315c5bcbf (diff)
parent26ef07ebe5f0a7723a509a4a9a599976d31b4e0c (diff)
downloadtracing-attributes-ded42890bd1816dcaacd3b921bcf85d2683f7d94.tar.gz
Import crate tracing-attributes am: fa35e1ea17 am: 6f5623c352 am: e69cd6ba41 am: 26ef07ebe5
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/tracing-attributes/+/2506420 Change-Id: Iba068dd34c08a355c37f835c985d6ec91d179f93 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--CHANGELOG.md327
-rw-r--r--Cargo.toml91
-rw-r--r--Cargo.toml.orig57
-rw-r--r--LICENSE25
-rw-r--r--METADATA19
-rw-r--r--MODULE_LICENSE_MIT0
-rw-r--r--OWNERS1
-rw-r--r--README.md91
-rw-r--r--src/attr.rs413
-rw-r--r--src/expand.rs814
-rw-r--r--src/lib.rs677
-rw-r--r--tests/async_fn.rs462
-rw-r--r--tests/destructuring.rs213
-rw-r--r--tests/err.rs233
-rw-r--r--tests/fields.rs160
-rw-r--r--tests/follows_from.rs99
-rw-r--r--tests/instrument.rs252
-rw-r--r--tests/levels.rs96
-rw-r--r--tests/names.rs63
-rw-r--r--tests/parents.rs102
-rw-r--r--tests/ret.rs255
-rw-r--r--tests/targets.rs97
-rw-r--r--tests/ui.rs7
-rw-r--r--tests/ui/async_instrument.rs46
-rw-r--r--tests/ui/async_instrument.stderr98
25 files changed, 4698 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..42cb09b
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,327 @@
+# 0.1.23 (October 6, 2022)
+
+This release of `tracing-attributes` fixes a bug where compiler diagnostic spans
+for type errors in `#[instrument]`ed `async fn`s have the location of the
+`#[instrument]` attribute rather than the location of the actual error, and a
+bug where inner attributes in `#[instrument]`ed functions would cause a compiler
+error.
+### Fixed
+
+- Fix incorrect handling of inner attributes in `#[instrument]`ed functions ([#2307])
+- Add fake return to improve spans generated for type errors in `async fn`s ([#2270])
+- Updated `syn` dependency to fix compilation with `-Z minimal-versions`
+ ([#2246])
+
+Thanks to new contributors @compiler-errors and @e-nomem, as well as @CAD97, for
+contributing to this release!
+
+[#2307]: https://github.com/tokio-rs/tracing/pull/2307
+[#2270]: https://github.com/tokio-rs/tracing/pull/2270
+[#2246]: https://github.com/tokio-rs/tracing/pull/2246
+
+# 0.1.22 (July 1, 2022)
+
+This release fixes an issue where using the `err` or `ret` arguments to
+`#[instrument]` along with an overridden target, such as
+
+```rust
+#[instrument(target = "...", err, ret)]
+```
+
+would not propagate the overridden target to the events generated for
+errors/return values.
+
+### Fixed
+
+- Error and return value events generated by `#[instrument(err)]` or
+ `#[instrument(ret)]` not inheriting an overridden target ([#2184])
+- Incorrect default level in documentation ([#2119])
+
+Thanks to new contributor @tbraun96 for contributing to this release!
+
+[#2184]: https://github.com/tokio-rs/tracing/pull/2184
+[#2119]: https://github.com/tokio-rs/tracing/pull/2119
+
+# 0.1.21 (April 26, 2022)
+
+This release adds support for setting explicit parent and follows-from spans
+in the `#[instrument]` attribute.
+
+### Added
+
+- `#[instrument(follows_from = ...)]` argument for setting one or more
+ follows-from span ([#2093])
+- `#[instrument(parent = ...)]` argument for overriding the generated span's
+ parent ([#2091])
+
+### Fixed
+
+- Extra braces around `async` blocks in expanded code (causes a Clippy warning)
+ ([#2090])
+- Broken documentation links ([#2068], [#2077])
+
+Thanks to @jarrodldavis, @ben0x539, and new contributor @jswrenn for
+contributing to this release!
+
+
+[#2093]: https://github.com/tokio-rs/tracing/pull/2093
+[#2091]: https://github.com/tokio-rs/tracing/pull/2091
+[#2090]: https://github.com/tokio-rs/tracing/pull/2090
+[#2077]: https://github.com/tokio-rs/tracing/pull/2077
+[#2068]: https://github.com/tokio-rs/tracing/pull/2068
+
+# 0.1.20 (March 8, 2022)
+
+### Fixed
+
+- Compilation failure with `--minimal-versions` due to a too-permissive `syn`
+ dependency ([#1960])
+
+### Changed
+
+- Bumped minimum supported Rust version (MSRV) to 1.49.0 ([#1913])
+
+Thanks to new contributor @udoprog for contributing to this release!
+
+[#1960]: https://github.com/tokio-rs/tracing/pull/1960
+[#1913]: https://github.com/tokio-rs/tracing/pull/1913
+
+# 0.1.19 (February 3, 2022)
+
+This release introduces a new `#[instrument(ret)]` argument to emit an event
+with the return value of an instrumented function.
+
+### Added
+
+- `#[instrument(ret)]` to record the return value of a function ([#1716])
+- added `err(Debug)` argument to cause `#[instrument(err)]` to record errors
+ with `Debug` rather than `Display ([#1631])
+
+### Fixed
+
+- incorrect code generation for functions returning async blocks ([#1866])
+- incorrect diagnostics when using `rust-analyzer` ([#1634])
+
+Thanks to @Swatinem, @hkmatsumoto, @cynecx, and @ciuncan for contributing to
+this release!
+
+[#1716]: https://github.com/tokio-rs/tracing/pull/1716
+[#1631]: https://github.com/tokio-rs/tracing/pull/1631
+[#1634]: https://github.com/tokio-rs/tracing/pull/1634
+[#1866]: https://github.com/tokio-rs/tracing/pull/1866
+
+# 0.1.18 (October 5, 2021)
+
+This release fixes issues introduced in v0.1.17.
+
+### Fixed
+
+- fixed mismatched types compiler error that may occur when using
+ `#[instrument]` on an `async fn` that returns an `impl Trait` value that
+ includes a closure ([#1616])
+- fixed false positives for `clippy::suspicious_else_formatting` warnings due to
+ rust-lang/rust-clippy#7760 and rust-lang/rust-clippy#6249 ([#1617])
+- fixed `clippy::let_unit_value` lints when using `#[instrument]` ([#1614])
+
+[#1617]: https://github.com/tokio-rs/tracing/pull/1617
+[#1616]: https://github.com/tokio-rs/tracing/pull/1616
+[#1614]: https://github.com/tokio-rs/tracing/pull/1614
+
+# 0.1.17 (YANKED) (October 1, 2021)
+
+This release significantly improves performance when `#[instrument]`-generated
+spans are below the maximum enabled level.
+
+### Added
+
+- improve performance when skipping `#[instrument]`-generated spans below the
+ max level ([#1600], [#1605])
+
+Thanks to @oli-obk for contributing to this release!
+
+[#1600]: https://github.com/tokio-rs/tracing/pull/1600
+[#1605]: https://github.com/tokio-rs/tracing/pull/1605
+
+# 0.1.16 (September 13, 2021)
+
+This release adds a new `#[instrument(skip_all)]` option to skip recording *all*
+arguments to an instrumented function as fields. Additionally, it adds support
+for recording arguments that are `tracing` primitive types as typed values,
+rather than as `fmt::Debug`.
+
+### Added
+
+- add `skip_all` option to `#[instrument]` ([#1548])
+- record primitive types as primitive values rather than as `fmt::Debug`
+ ([#1378])
+- added support for `f64`s as typed values ([#1522])
+
+Thanks to @Folyd and @jsgf for contributing to this release!
+
+[#1548]: https://github.com/tokio-rs/tracing/pull/1548
+[#1378]: https://github.com/tokio-rs/tracing/pull/1378
+[#1522]: https://github.com/tokio-rs/tracing/pull/1524
+
+# 0.1.15 (March 12, 2021)
+
+### Fixed
+
+- `#[instrument]` on functions returning `Box::pin`ned futures incorrectly
+ skipping function bodies prior to returning a future ([#1297])
+
+Thanks to @nightmared for contributing to this release!
+
+[#1297]: https://github.com/tokio-rs/tracing/pull/1297
+
+# 0.1.14 (March 10, 2021)
+
+### Fixed
+
+- Compatibility between `#[instrument]` and `async-trait` v0.1.43 and newer
+ ([#1228])
+
+Thanks to @nightmared for lots of hard work on this fix!
+
+[#1228]: https://github.com/tokio-rs/tracing/pull/1228
+
+# 0.1.13 (February 17, 2021)
+
+### Fixed
+
+- Compiler error when using `#[instrument(err)]` on functions which return `impl
+ Trait` ([#1236])
+
+[#1236]: https://github.com/tokio-rs/tracing/pull/1236
+
+# 0.1.12 (February 4, 2021)
+
+### Fixed
+
+- Compiler error when using `#[instrument(err)]` on functions with mutable
+ parameters ([#1167])
+- Missing function visibility modifier when using `#[instrument]` with
+ `async-trait` ([#977])
+- Multiple documentation fixes and improvements ([#965], [#981], [#1215])
+
+### Changed
+
+- `tracing-futures` dependency is no longer required when using `#[instrument]`
+ on async functions ([#808])
+
+Thanks to @nagisa, @Txuritan, @TaKO8Ki, and @okready for contributing to this
+release!
+
+[#1167]: https://github.com/tokio-rs/tracing/pull/1167
+[#977]: https://github.com/tokio-rs/tracing/pull/977
+[#965]: https://github.com/tokio-rs/tracing/pull/965
+[#981]: https://github.com/tokio-rs/tracing/pull/981
+[#1215]: https://github.com/tokio-rs/tracing/pull/1215
+[#808]: https://github.com/tokio-rs/tracing/pull/808
+
+# 0.1.11 (August 18, 2020)
+
+### Fixed
+
+- Corrected wrong minimum supported Rust version note in docs (#941)
+- Removed unused `syn` features (#928)
+
+Thanks to new contributor @jhpratt for contributing to this release!
+
+# 0.1.10 (August 10, 2020)
+
+### Added
+
+- Support for using `self` in field expressions when instrumenting `async-trait`
+ functions (#875)
+- Several documentation improvements (#832, #897, #911, #913)
+
+Thanks to @anton-dutov and @nightmared for contributing to this release!
+
+# 0.1.9 (July 8, 2020)
+
+### Added
+
+- Support for arbitrary expressions as fields in `#[instrument]` (#672)
+
+### Changed
+
+- `#[instrument]` now emits a compiler warning when ignoring unrecognized
+ input (#672, #786)
+
+# 0.1.8 (May 13, 2020)
+
+### Added
+
+- Support for using `#[instrument]` on methods that are part of [`async-trait`]
+ trait implementations (#711)
+- Optional `#[instrument(err)]` argument to automatically emit an event if an
+ instrumented function returns `Err` (#637)
+
+Thanks to @ilana and @nightmared for contributing to this release!
+
+[`async-trait`]: https://crates.io/crates/async-trait
+
+# 0.1.7 (February 26, 2020)
+
+### Added
+
+- Support for adding arbitrary literal fields to spans generated by
+ `#[instrument]` (#569)
+- `#[instrument]` now emits a helpful compiler error when attempting to skip a
+ function parameter (#600)
+
+Thanks to @Kobzol for contributing to this release!
+
+# 0.1.6 (December 20, 2019)
+
+### Added
+
+- Updated documentation (#468)
+
+# 0.1.5 (October 22, 2019)
+
+### Added
+
+- Support for destructuring in arguments to `#[instrument]`ed functions (#397)
+- Generated field for `self` parameters when `#[instrument]`ing methods (#397)
+
+# 0.1.4 (September 26, 2019)
+
+### Added
+
+- Optional `skip` argument to `#[instrument]` for excluding function parameters
+ from generated spans (#359)
+
+# 0.1.3 (September 12, 2019)
+
+### Fixed
+
+- Fixed `#[instrument]`ed async functions not compiling on `nightly-2019-09-11`
+ or newer (#342)
+
+# 0.1.2 (August 19, 2019)
+
+### Changed
+
+- Updated `syn` and `quote` dependencies to 1.0 (#292)
+- Removed direct dependency on `proc-macro2` to avoid potential version
+ conflicts (#296)
+
+### Fixed
+
+- Outdated idioms in examples (#271, #273)
+
+# 0.1.1 (August 9, 2019)
+
+### Changed
+
+- Using the `#[instrument]` attribute on `async fn`s no longer requires a
+ feature flag (#258)
+
+### Fixed
+
+- The `#[instrument]` macro now works on generic functions (#262)
+
+# 0.1.0 (August 8, 2019)
+
+- Initial release
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..5639de0
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,91 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2018"
+rust-version = "1.49.0"
+name = "tracing-attributes"
+version = "0.1.23"
+authors = [
+ "Tokio Contributors <team@tokio.rs>",
+ "Eliza Weisman <eliza@buoyant.io>",
+ "David Barsky <dbarsky@amazon.com>",
+]
+description = """
+Procedural macro attributes for automatically instrumenting functions.
+"""
+homepage = "https://tokio.rs"
+readme = "README.md"
+keywords = [
+ "logging",
+ "tracing",
+ "macro",
+ "instrument",
+ "log",
+]
+categories = [
+ "development-tools::debugging",
+ "development-tools::profiling",
+ "asynchronous",
+]
+license = "MIT"
+repository = "https://github.com/tokio-rs/tracing"
+
+[lib]
+proc-macro = true
+
+[dependencies.proc-macro2]
+version = "1"
+
+[dependencies.quote]
+version = "1"
+
+[dependencies.syn]
+version = "1.0.98"
+features = [
+ "full",
+ "parsing",
+ "printing",
+ "visit",
+ "visit-mut",
+ "clone-impls",
+ "extra-traits",
+ "proc-macro",
+]
+default-features = false
+
+[dev-dependencies.async-trait]
+version = "0.1.56"
+
+[dev-dependencies.rustversion]
+version = "1.0.9"
+
+[dev-dependencies.tokio-test]
+version = "0.3.0"
+
+[dev-dependencies.tracing]
+version = "0.1.35"
+
+[dev-dependencies.tracing-core]
+version = "0.1.28"
+
+[dev-dependencies.tracing-subscriber]
+version = "0.3.0"
+features = ["env-filter"]
+
+[dev-dependencies.trybuild]
+version = "1.0.64"
+
+[features]
+async-await = []
+
+[badges.maintenance]
+status = "experimental"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644
index 0000000..d6e3adb
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,57 @@
+[package]
+name = "tracing-attributes"
+# When releasing to crates.io:
+# - Remove path dependencies
+# - Update html_root_url.
+# - Update doc url
+# - Cargo.toml
+# - README.md
+# - Update CHANGELOG.md.
+# - Create "v0.1.x" git tag.
+version = "0.1.23"
+authors = [
+ "Tokio Contributors <team@tokio.rs>",
+ "Eliza Weisman <eliza@buoyant.io>",
+ "David Barsky <dbarsky@amazon.com>",
+]
+repository = "https://github.com/tokio-rs/tracing"
+homepage = "https://tokio.rs"
+description = """
+Procedural macro attributes for automatically instrumenting functions.
+"""
+categories = [
+ "development-tools::debugging",
+ "development-tools::profiling",
+ "asynchronous",
+]
+keywords = ["logging", "tracing", "macro", "instrument", "log"]
+license = "MIT"
+readme = "README.md"
+edition = "2018"
+rust-version = "1.49.0"
+
+[lib]
+proc-macro = true
+
+[features]
+
+# This feature flag is no longer necessary.
+async-await = []
+
+[dependencies]
+proc-macro2 = "1"
+syn = { version = "1.0.98", default-features = false, features = ["full", "parsing", "printing", "visit", "visit-mut", "clone-impls", "extra-traits", "proc-macro"] }
+quote = "1"
+
+[dev-dependencies]
+tracing = { path = "../tracing", version = "0.1.35" }
+tracing-mock = { path = "../tracing-mock", features = ["tokio-test"] }
+tracing-subscriber = { path = "../tracing-subscriber", version = "0.3.0", features = ["env-filter"] }
+tokio-test = { version = "0.3.0" }
+tracing-core = { path = "../tracing-core", version = "0.1.28"}
+async-trait = "0.1.56"
+trybuild = "1.0.64"
+rustversion = "1.0.9"
+
+[badges]
+maintenance = { status = "experimental" }
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..cdb28b4
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,25 @@
+Copyright (c) 2019 Tokio Contributors
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..ff17979
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,19 @@
+name: "tracing-attributes"
+description: "Macro attributes for application-level tracing."
+third_party {
+ url {
+ type: HOMEPAGE
+ value: "https://crates.io/crates/tracing-attributes"
+ }
+ url {
+ type: ARCHIVE
+ value: "https://static.crates.io/crates/tracing-attributes/tracing-attributes-0.1.23.crate"
+ }
+ version: "0.1.23"
+ license_type: NOTICE
+ last_upgrade_date {
+ year: 2023
+ month: 3
+ day: 3
+ }
+}
diff --git a/MODULE_LICENSE_MIT b/MODULE_LICENSE_MIT
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_MIT
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..45dc4dd
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1 @@
+include platform/prebuilts/rust:master:/OWNERS
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..356b511
--- /dev/null
+++ b/README.md
@@ -0,0 +1,91 @@
+![Tracing — Structured, application-level diagnostics][splash]
+
+[splash]: https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/splash.svg
+
+# tracing-attributes
+
+Macro attributes for application-level tracing.
+
+[![Crates.io][crates-badge]][crates-url]
+[![Documentation][docs-badge]][docs-url]
+[![Documentation (master)][docs-master-badge]][docs-master-url]
+[![MIT licensed][mit-badge]][mit-url]
+[![Build Status][actions-badge]][actions-url]
+[![Discord chat][discord-badge]][discord-url]
+
+[Documentation][docs-url] | [Chat][discord-url]
+
+[crates-badge]: https://img.shields.io/crates/v/tracing-attributes.svg
+[crates-url]: https://crates.io/crates/tracing-attributes
+[docs-badge]: https://docs.rs/tracing-attributes/badge.svg
+[docs-url]: https://docs.rs/tracing-attributes/0.1.23
+[docs-master-badge]: https://img.shields.io/badge/docs-master-blue
+[docs-master-url]: https://tracing-rs.netlify.com/tracing_attributes
+[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
+[mit-url]: LICENSE
+[actions-badge]: https://github.com/tokio-rs/tracing/workflows/CI/badge.svg
+[actions-url]:https://github.com/tokio-rs/tracing/actions?query=workflow%3ACI
+[discord-badge]: https://img.shields.io/discord/500028886025895936?logo=discord&label=discord&logoColor=white
+[discord-url]: https://discord.gg/EeF3cQw
+
+## Overview
+
+[`tracing`] is a framework for instrumenting Rust programs to collect
+structured, event-based diagnostic information. This crate provides the
+`#[instrument]` attribute for automatically instrumenting functions using
+`tracing`.
+
+Note that this macro is also re-exported by the main `tracing` crate.
+
+*Compiler support: [requires `rustc` 1.49+][msrv]*
+
+[msrv]: #supported-rust-versions
+
+## Usage
+
+First, add this to your `Cargo.toml`:
+
+```toml
+[dependencies]
+tracing-attributes = "0.1.23"
+```
+
+
+This crate provides the `#[instrument]` attribute for instrumenting a function
+with a `tracing` [span]. For example:
+
+```rust
+use tracing_attributes::instrument;
+
+#[instrument]
+pub fn my_function(my_arg: usize) {
+ // ...
+}
+```
+
+[`tracing`]: https://crates.io/crates/tracing
+[span]: https://docs.rs/tracing/latest/tracing/span/index.html
+
+## Supported Rust Versions
+
+Tracing is built against the latest stable release. The minimum supported
+version is 1.49. The current Tracing version is not guaranteed to build on Rust
+versions earlier than the minimum supported version.
+
+Tracing follows the same compiler support policies as the rest of the Tokio
+project. The current stable Rust compiler and the three most recent minor
+versions before it will always be supported. For example, if the current stable
+compiler version is 1.45, the minimum supported version will not be increased
+past 1.42, three minor versions prior. Increasing the minimum supported compiler
+version is not considered a semver breaking change as long as doing so complies
+with this policy.
+
+## License
+
+This project is licensed under the [MIT license](LICENSE).
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in Tokio by you, shall be licensed as MIT, without any additional
+terms or conditions.
diff --git a/src/attr.rs b/src/attr.rs
new file mode 100644
index 0000000..ff875e1
--- /dev/null
+++ b/src/attr.rs
@@ -0,0 +1,413 @@
+use std::collections::HashSet;
+use syn::{punctuated::Punctuated, Expr, Ident, LitInt, LitStr, Path, Token};
+
+use proc_macro2::TokenStream;
+use quote::{quote, quote_spanned, ToTokens};
+use syn::ext::IdentExt as _;
+use syn::parse::{Parse, ParseStream};
+
+#[derive(Clone, Default, Debug)]
+pub(crate) struct InstrumentArgs {
+ level: Option<Level>,
+ pub(crate) name: Option<LitStr>,
+ target: Option<LitStr>,
+ pub(crate) parent: Option<Expr>,
+ pub(crate) follows_from: Option<Expr>,
+ pub(crate) skips: HashSet<Ident>,
+ pub(crate) skip_all: bool,
+ pub(crate) fields: Option<Fields>,
+ pub(crate) err_mode: Option<FormatMode>,
+ pub(crate) ret_mode: Option<FormatMode>,
+ /// Errors describing any unrecognized parse inputs that we skipped.
+ parse_warnings: Vec<syn::Error>,
+}
+
+impl InstrumentArgs {
+ pub(crate) fn level(&self) -> impl ToTokens {
+ fn is_level(lit: &LitInt, expected: u64) -> bool {
+ match lit.base10_parse::<u64>() {
+ Ok(value) => value == expected,
+ Err(_) => false,
+ }
+ }
+
+ match &self.level {
+ Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("trace") => {
+ quote!(tracing::Level::TRACE)
+ }
+ Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("debug") => {
+ quote!(tracing::Level::DEBUG)
+ }
+ Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("info") => {
+ quote!(tracing::Level::INFO)
+ }
+ Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("warn") => {
+ quote!(tracing::Level::WARN)
+ }
+ Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("error") => {
+ quote!(tracing::Level::ERROR)
+ }
+ Some(Level::Int(ref lit)) if is_level(lit, 1) => quote!(tracing::Level::TRACE),
+ Some(Level::Int(ref lit)) if is_level(lit, 2) => quote!(tracing::Level::DEBUG),
+ Some(Level::Int(ref lit)) if is_level(lit, 3) => quote!(tracing::Level::INFO),
+ Some(Level::Int(ref lit)) if is_level(lit, 4) => quote!(tracing::Level::WARN),
+ Some(Level::Int(ref lit)) if is_level(lit, 5) => quote!(tracing::Level::ERROR),
+ Some(Level::Path(ref pat)) => quote!(#pat),
+ Some(_) => quote! {
+ compile_error!(
+ "unknown verbosity level, expected one of \"trace\", \
+ \"debug\", \"info\", \"warn\", or \"error\", or a number 1-5"
+ )
+ },
+ None => quote!(tracing::Level::INFO),
+ }
+ }
+
+ pub(crate) fn target(&self) -> impl ToTokens {
+ if let Some(ref target) = self.target {
+ quote!(#target)
+ } else {
+ quote!(module_path!())
+ }
+ }
+
+ /// Generate "deprecation" warnings for any unrecognized attribute inputs
+ /// that we skipped.
+ ///
+ /// For backwards compatibility, we need to emit compiler warnings rather
+ /// than errors for unrecognized inputs. Generating a fake deprecation is
+ /// the only way to do this on stable Rust right now.
+ pub(crate) fn warnings(&self) -> impl ToTokens {
+ let warnings = self.parse_warnings.iter().map(|err| {
+ let msg = format!("found unrecognized input, {}", err);
+ let msg = LitStr::new(&msg, err.span());
+ // TODO(eliza): This is a bit of a hack, but it's just about the
+ // only way to emit warnings from a proc macro on stable Rust.
+ // Eventually, when the `proc_macro::Diagnostic` API stabilizes, we
+ // should definitely use that instead.
+ quote_spanned! {err.span()=>
+ #[warn(deprecated)]
+ {
+ #[deprecated(since = "not actually deprecated", note = #msg)]
+ const TRACING_INSTRUMENT_WARNING: () = ();
+ let _ = TRACING_INSTRUMENT_WARNING;
+ }
+ }
+ });
+ quote! {
+ { #(#warnings)* }
+ }
+ }
+}
+
+impl Parse for InstrumentArgs {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ let mut args = Self::default();
+ while !input.is_empty() {
+ let lookahead = input.lookahead1();
+ if lookahead.peek(kw::name) {
+ if args.name.is_some() {
+ return Err(input.error("expected only a single `name` argument"));
+ }
+ let name = input.parse::<StrArg<kw::name>>()?.value;
+ args.name = Some(name);
+ } else if lookahead.peek(LitStr) {
+ // XXX: apparently we support names as either named args with an
+ // sign, _or_ as unnamed string literals. That's weird, but
+ // changing it is apparently breaking.
+ if args.name.is_some() {
+ return Err(input.error("expected only a single `name` argument"));
+ }
+ args.name = Some(input.parse()?);
+ } else if lookahead.peek(kw::target) {
+ if args.target.is_some() {
+ return Err(input.error("expected only a single `target` argument"));
+ }
+ let target = input.parse::<StrArg<kw::target>>()?.value;
+ args.target = Some(target);
+ } else if lookahead.peek(kw::parent) {
+ if args.target.is_some() {
+ return Err(input.error("expected only a single `parent` argument"));
+ }
+ let parent = input.parse::<ExprArg<kw::parent>>()?;
+ args.parent = Some(parent.value);
+ } else if lookahead.peek(kw::follows_from) {
+ if args.target.is_some() {
+ return Err(input.error("expected only a single `follows_from` argument"));
+ }
+ let follows_from = input.parse::<ExprArg<kw::follows_from>>()?;
+ args.follows_from = Some(follows_from.value);
+ } else if lookahead.peek(kw::level) {
+ if args.level.is_some() {
+ return Err(input.error("expected only a single `level` argument"));
+ }
+ args.level = Some(input.parse()?);
+ } else if lookahead.peek(kw::skip) {
+ if !args.skips.is_empty() {
+ return Err(input.error("expected only a single `skip` argument"));
+ }
+ if args.skip_all {
+ return Err(input.error("expected either `skip` or `skip_all` argument"));
+ }
+ let Skips(skips) = input.parse()?;
+ args.skips = skips;
+ } else if lookahead.peek(kw::skip_all) {
+ if args.skip_all {
+ return Err(input.error("expected only a single `skip_all` argument"));
+ }
+ if !args.skips.is_empty() {
+ return Err(input.error("expected either `skip` or `skip_all` argument"));
+ }
+ let _ = input.parse::<kw::skip_all>()?;
+ args.skip_all = true;
+ } else if lookahead.peek(kw::fields) {
+ if args.fields.is_some() {
+ return Err(input.error("expected only a single `fields` argument"));
+ }
+ args.fields = Some(input.parse()?);
+ } else if lookahead.peek(kw::err) {
+ let _ = input.parse::<kw::err>();
+ let mode = FormatMode::parse(input)?;
+ args.err_mode = Some(mode);
+ } else if lookahead.peek(kw::ret) {
+ let _ = input.parse::<kw::ret>()?;
+ let mode = FormatMode::parse(input)?;
+ args.ret_mode = Some(mode);
+ } else if lookahead.peek(Token![,]) {
+ let _ = input.parse::<Token![,]>()?;
+ } else {
+ // We found a token that we didn't expect!
+ // We want to emit warnings for these, rather than errors, so
+ // we'll add it to the list of unrecognized inputs we've seen so
+ // far and keep going.
+ args.parse_warnings.push(lookahead.error());
+ // Parse the unrecognized token tree to advance the parse
+ // stream, and throw it away so we can keep parsing.
+ let _ = input.parse::<proc_macro2::TokenTree>();
+ }
+ }
+ Ok(args)
+ }
+}
+
+struct StrArg<T> {
+ value: LitStr,
+ _p: std::marker::PhantomData<T>,
+}
+
+impl<T: Parse> Parse for StrArg<T> {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ let _ = input.parse::<T>()?;
+ let _ = input.parse::<Token![=]>()?;
+ let value = input.parse()?;
+ Ok(Self {
+ value,
+ _p: std::marker::PhantomData,
+ })
+ }
+}
+
+struct ExprArg<T> {
+ value: Expr,
+ _p: std::marker::PhantomData<T>,
+}
+
+impl<T: Parse> Parse for ExprArg<T> {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ let _ = input.parse::<T>()?;
+ let _ = input.parse::<Token![=]>()?;
+ let value = input.parse()?;
+ Ok(Self {
+ value,
+ _p: std::marker::PhantomData,
+ })
+ }
+}
+
+struct Skips(HashSet<Ident>);
+
+impl Parse for Skips {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ let _ = input.parse::<kw::skip>();
+ let content;
+ let _ = syn::parenthesized!(content in input);
+ let names: Punctuated<Ident, Token![,]> = content.parse_terminated(Ident::parse_any)?;
+ let mut skips = HashSet::new();
+ for name in names {
+ if skips.contains(&name) {
+ return Err(syn::Error::new(
+ name.span(),
+ "tried to skip the same field twice",
+ ));
+ } else {
+ skips.insert(name);
+ }
+ }
+ Ok(Self(skips))
+ }
+}
+
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+pub(crate) enum FormatMode {
+ Default,
+ Display,
+ Debug,
+}
+
+impl Default for FormatMode {
+ fn default() -> Self {
+ FormatMode::Default
+ }
+}
+
+impl Parse for FormatMode {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ if !input.peek(syn::token::Paren) {
+ return Ok(FormatMode::default());
+ }
+ let content;
+ let _ = syn::parenthesized!(content in input);
+ let maybe_mode: Option<Ident> = content.parse()?;
+ maybe_mode.map_or(Ok(FormatMode::default()), |ident| {
+ match ident.to_string().as_str() {
+ "Debug" => Ok(FormatMode::Debug),
+ "Display" => Ok(FormatMode::Display),
+ _ => Err(syn::Error::new(
+ ident.span(),
+ "unknown error mode, must be Debug or Display",
+ )),
+ }
+ })
+ }
+}
+
+#[derive(Clone, Debug)]
+pub(crate) struct Fields(pub(crate) Punctuated<Field, Token![,]>);
+
+#[derive(Clone, Debug)]
+pub(crate) struct Field {
+ pub(crate) name: Punctuated<Ident, Token![.]>,
+ pub(crate) value: Option<Expr>,
+ pub(crate) kind: FieldKind,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub(crate) enum FieldKind {
+ Debug,
+ Display,
+ Value,
+}
+
+impl Parse for Fields {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ let _ = input.parse::<kw::fields>();
+ let content;
+ let _ = syn::parenthesized!(content in input);
+ let fields: Punctuated<_, Token![,]> = content.parse_terminated(Field::parse)?;
+ Ok(Self(fields))
+ }
+}
+
+impl ToTokens for Fields {
+ fn to_tokens(&self, tokens: &mut TokenStream) {
+ self.0.to_tokens(tokens)
+ }
+}
+
+impl Parse for Field {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ let mut kind = FieldKind::Value;
+ if input.peek(Token![%]) {
+ input.parse::<Token![%]>()?;
+ kind = FieldKind::Display;
+ } else if input.peek(Token![?]) {
+ input.parse::<Token![?]>()?;
+ kind = FieldKind::Debug;
+ };
+ let name = Punctuated::parse_separated_nonempty_with(input, Ident::parse_any)?;
+ let value = if input.peek(Token![=]) {
+ input.parse::<Token![=]>()?;
+ if input.peek(Token![%]) {
+ input.parse::<Token![%]>()?;
+ kind = FieldKind::Display;
+ } else if input.peek(Token![?]) {
+ input.parse::<Token![?]>()?;
+ kind = FieldKind::Debug;
+ };
+ Some(input.parse()?)
+ } else {
+ None
+ };
+ Ok(Self { name, value, kind })
+ }
+}
+
+impl ToTokens for Field {
+ fn to_tokens(&self, tokens: &mut TokenStream) {
+ if let Some(ref value) = self.value {
+ let name = &self.name;
+ let kind = &self.kind;
+ tokens.extend(quote! {
+ #name = #kind#value
+ })
+ } else if self.kind == FieldKind::Value {
+ // XXX(eliza): I don't like that fields without values produce
+ // empty fields rather than local variable shorthand...but,
+ // we've released a version where field names without values in
+ // `instrument` produce empty field values, so changing it now
+ // is a breaking change. agh.
+ let name = &self.name;
+ tokens.extend(quote!(#name = tracing::field::Empty))
+ } else {
+ self.kind.to_tokens(tokens);
+ self.name.to_tokens(tokens);
+ }
+ }
+}
+
+impl ToTokens for FieldKind {
+ fn to_tokens(&self, tokens: &mut TokenStream) {
+ match self {
+ FieldKind::Debug => tokens.extend(quote! { ? }),
+ FieldKind::Display => tokens.extend(quote! { % }),
+ _ => {}
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+enum Level {
+ Str(LitStr),
+ Int(LitInt),
+ Path(Path),
+}
+
+impl Parse for Level {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ let _ = input.parse::<kw::level>()?;
+ let _ = input.parse::<Token![=]>()?;
+ let lookahead = input.lookahead1();
+ if lookahead.peek(LitStr) {
+ Ok(Self::Str(input.parse()?))
+ } else if lookahead.peek(LitInt) {
+ Ok(Self::Int(input.parse()?))
+ } else if lookahead.peek(Ident) {
+ Ok(Self::Path(input.parse()?))
+ } else {
+ Err(lookahead.error())
+ }
+ }
+}
+
+mod kw {
+ syn::custom_keyword!(fields);
+ syn::custom_keyword!(skip);
+ syn::custom_keyword!(skip_all);
+ syn::custom_keyword!(level);
+ syn::custom_keyword!(target);
+ syn::custom_keyword!(parent);
+ syn::custom_keyword!(follows_from);
+ syn::custom_keyword!(name);
+ syn::custom_keyword!(err);
+ syn::custom_keyword!(ret);
+}
diff --git a/src/expand.rs b/src/expand.rs
new file mode 100644
index 0000000..7005b44
--- /dev/null
+++ b/src/expand.rs
@@ -0,0 +1,814 @@
+use std::iter;
+
+use proc_macro2::TokenStream;
+use quote::{quote, quote_spanned, ToTokens};
+use syn::visit_mut::VisitMut;
+use syn::{
+ punctuated::Punctuated, spanned::Spanned, Block, Expr, ExprAsync, ExprCall, FieldPat, FnArg,
+ Ident, Item, ItemFn, Pat, PatIdent, PatReference, PatStruct, PatTuple, PatTupleStruct, PatType,
+ Path, ReturnType, Signature, Stmt, Token, Type, TypePath,
+};
+
+use crate::{
+ attr::{Field, Fields, FormatMode, InstrumentArgs},
+ MaybeItemFn, MaybeItemFnRef,
+};
+
+/// Given an existing function, generate an instrumented version of that function
+pub(crate) fn gen_function<'a, B: ToTokens + 'a>(
+ input: MaybeItemFnRef<'a, B>,
+ args: InstrumentArgs,
+ instrumented_function_name: &str,
+ self_type: Option<&TypePath>,
+) -> proc_macro2::TokenStream {
+ // these are needed ahead of time, as ItemFn contains the function body _and_
+ // isn't representable inside a quote!/quote_spanned! macro
+ // (Syn's ToTokens isn't implemented for ItemFn)
+ let MaybeItemFnRef {
+ outer_attrs,
+ inner_attrs,
+ vis,
+ sig,
+ block,
+ } = input;
+
+ let Signature {
+ output,
+ inputs: params,
+ unsafety,
+ asyncness,
+ constness,
+ abi,
+ ident,
+ generics:
+ syn::Generics {
+ params: gen_params,
+ where_clause,
+ ..
+ },
+ ..
+ } = sig;
+
+ let warnings = args.warnings();
+
+ let (return_type, return_span) = if let ReturnType::Type(_, return_type) = &output {
+ (erase_impl_trait(return_type), return_type.span())
+ } else {
+ // Point at function name if we don't have an explicit return type
+ (syn::parse_quote! { () }, ident.span())
+ };
+ // Install a fake return statement as the first thing in the function
+ // body, so that we eagerly infer that the return type is what we
+ // declared in the async fn signature.
+ // The `#[allow(..)]` is given because the return statement is
+ // unreachable, but does affect inference, so it needs to be written
+ // exactly that way for it to do its magic.
+ let fake_return_edge = quote_spanned! {return_span=>
+ #[allow(unreachable_code, clippy::diverging_sub_expression, clippy::let_unit_value)]
+ if false {
+ let __tracing_attr_fake_return: #return_type =
+ unreachable!("this is just for type inference, and is unreachable code");
+ return __tracing_attr_fake_return;
+ }
+ };
+ let block = quote! {
+ {
+ #fake_return_edge
+ #block
+ }
+ };
+
+ let body = gen_block(
+ &block,
+ params,
+ asyncness.is_some(),
+ args,
+ instrumented_function_name,
+ self_type,
+ );
+
+ quote!(
+ #(#outer_attrs) *
+ #vis #constness #unsafety #asyncness #abi fn #ident<#gen_params>(#params) #output
+ #where_clause
+ {
+ #(#inner_attrs) *
+ #warnings
+ #body
+ }
+ )
+}
+
+/// Instrument a block
+fn gen_block<B: ToTokens>(
+ block: &B,
+ params: &Punctuated<FnArg, Token![,]>,
+ async_context: bool,
+ mut args: InstrumentArgs,
+ instrumented_function_name: &str,
+ self_type: Option<&TypePath>,
+) -> proc_macro2::TokenStream {
+ // generate the span's name
+ let span_name = args
+ // did the user override the span's name?
+ .name
+ .as_ref()
+ .map(|name| quote!(#name))
+ .unwrap_or_else(|| quote!(#instrumented_function_name));
+
+ let level = args.level();
+
+ let follows_from = args.follows_from.iter();
+ let follows_from = quote! {
+ #(for cause in #follows_from {
+ __tracing_attr_span.follows_from(cause);
+ })*
+ };
+
+ // generate this inside a closure, so we can return early on errors.
+ let span = (|| {
+ // Pull out the arguments-to-be-skipped first, so we can filter results
+ // below.
+ let param_names: Vec<(Ident, (Ident, RecordType))> = params
+ .clone()
+ .into_iter()
+ .flat_map(|param| match param {
+ FnArg::Typed(PatType { pat, ty, .. }) => {
+ param_names(*pat, RecordType::parse_from_ty(&*ty))
+ }
+ FnArg::Receiver(_) => Box::new(iter::once((
+ Ident::new("self", param.span()),
+ RecordType::Debug,
+ ))),
+ })
+ // Little dance with new (user-exposed) names and old (internal)
+ // names of identifiers. That way, we could do the following
+ // even though async_trait (<=0.1.43) rewrites "self" as "_self":
+ // ```
+ // #[async_trait]
+ // impl Foo for FooImpl {
+ // #[instrument(skip(self))]
+ // async fn foo(&self, v: usize) {}
+ // }
+ // ```
+ .map(|(x, record_type)| {
+ // if we are inside a function generated by async-trait <=0.1.43, we need to
+ // take care to rewrite "_self" as "self" for 'user convenience'
+ if self_type.is_some() && x == "_self" {
+ (Ident::new("self", x.span()), (x, record_type))
+ } else {
+ (x.clone(), (x, record_type))
+ }
+ })
+ .collect();
+
+ for skip in &args.skips {
+ if !param_names.iter().map(|(user, _)| user).any(|y| y == skip) {
+ return quote_spanned! {skip.span()=>
+ compile_error!("attempting to skip non-existent parameter")
+ };
+ }
+ }
+
+ let target = args.target();
+
+ let parent = args.parent.iter();
+
+ // filter out skipped fields
+ let quoted_fields: Vec<_> = param_names
+ .iter()
+ .filter(|(param, _)| {
+ if args.skip_all || args.skips.contains(param) {
+ return false;
+ }
+
+ // If any parameters have the same name as a custom field, skip
+ // and allow them to be formatted by the custom field.
+ if let Some(ref fields) = args.fields {
+ fields.0.iter().all(|Field { ref name, .. }| {
+ let first = name.first();
+ first != name.last() || !first.iter().any(|name| name == &param)
+ })
+ } else {
+ true
+ }
+ })
+ .map(|(user_name, (real_name, record_type))| match record_type {
+ RecordType::Value => quote!(#user_name = #real_name),
+ RecordType::Debug => quote!(#user_name = tracing::field::debug(&#real_name)),
+ })
+ .collect();
+
+ // replace every use of a variable with its original name
+ if let Some(Fields(ref mut fields)) = args.fields {
+ let mut replacer = IdentAndTypesRenamer {
+ idents: param_names.into_iter().map(|(a, (b, _))| (a, b)).collect(),
+ types: Vec::new(),
+ };
+
+ // when async-trait <=0.1.43 is in use, replace instances
+ // of the "Self" type inside the fields values
+ if let Some(self_type) = self_type {
+ replacer.types.push(("Self", self_type.clone()));
+ }
+
+ for e in fields.iter_mut().filter_map(|f| f.value.as_mut()) {
+ syn::visit_mut::visit_expr_mut(&mut replacer, e);
+ }
+ }
+
+ let custom_fields = &args.fields;
+
+ quote!(tracing::span!(
+ target: #target,
+ #(parent: #parent,)*
+ #level,
+ #span_name,
+ #(#quoted_fields,)*
+ #custom_fields
+
+ ))
+ })();
+
+ let target = args.target();
+
+ let err_event = match args.err_mode {
+ Some(FormatMode::Default) | Some(FormatMode::Display) => {
+ Some(quote!(tracing::error!(target: #target, error = %e)))
+ }
+ Some(FormatMode::Debug) => Some(quote!(tracing::error!(target: #target, error = ?e))),
+ _ => None,
+ };
+
+ let ret_event = match args.ret_mode {
+ Some(FormatMode::Display) => Some(quote!(
+ tracing::event!(target: #target, #level, return = %x)
+ )),
+ Some(FormatMode::Default) | Some(FormatMode::Debug) => Some(quote!(
+ tracing::event!(target: #target, #level, return = ?x)
+ )),
+ _ => None,
+ };
+
+ // Generate the instrumented function body.
+ // If the function is an `async fn`, this will wrap it in an async block,
+ // which is `instrument`ed using `tracing-futures`. Otherwise, this will
+ // enter the span and then perform the rest of the body.
+ // If `err` is in args, instrument any resulting `Err`s.
+ // If `ret` is in args, instrument any resulting `Ok`s when the function
+ // returns `Result`s, otherwise instrument any resulting values.
+ if async_context {
+ let mk_fut = match (err_event, ret_event) {
+ (Some(err_event), Some(ret_event)) => quote_spanned!(block.span()=>
+ async move {
+ match async move #block.await {
+ #[allow(clippy::unit_arg)]
+ Ok(x) => {
+ #ret_event;
+ Ok(x)
+ },
+ Err(e) => {
+ #err_event;
+ Err(e)
+ }
+ }
+ }
+ ),
+ (Some(err_event), None) => quote_spanned!(block.span()=>
+ async move {
+ match async move #block.await {
+ #[allow(clippy::unit_arg)]
+ Ok(x) => Ok(x),
+ Err(e) => {
+ #err_event;
+ Err(e)
+ }
+ }
+ }
+ ),
+ (None, Some(ret_event)) => quote_spanned!(block.span()=>
+ async move {
+ let x = async move #block.await;
+ #ret_event;
+ x
+ }
+ ),
+ (None, None) => quote_spanned!(block.span()=>
+ async move #block
+ ),
+ };
+
+ return quote!(
+ let __tracing_attr_span = #span;
+ let __tracing_instrument_future = #mk_fut;
+ if !__tracing_attr_span.is_disabled() {
+ #follows_from
+ tracing::Instrument::instrument(
+ __tracing_instrument_future,
+ __tracing_attr_span
+ )
+ .await
+ } else {
+ __tracing_instrument_future.await
+ }
+ );
+ }
+
+ let span = quote!(
+ // These variables are left uninitialized and initialized only
+ // if the tracing level is statically enabled at this point.
+ // While the tracing level is also checked at span creation
+ // time, that will still create a dummy span, and a dummy guard
+ // and drop the dummy guard later. By lazily initializing these
+ // variables, Rust will generate a drop flag for them and thus
+ // only drop the guard if it was created. This creates code that
+ // is very straightforward for LLVM to optimize out if the tracing
+ // level is statically disabled, while not causing any performance
+ // regression in case the level is enabled.
+ let __tracing_attr_span;
+ let __tracing_attr_guard;
+ if tracing::level_enabled!(#level) {
+ __tracing_attr_span = #span;
+ #follows_from
+ __tracing_attr_guard = __tracing_attr_span.enter();
+ }
+ );
+
+ match (err_event, ret_event) {
+ (Some(err_event), Some(ret_event)) => quote_spanned! {block.span()=>
+ #span
+ #[allow(clippy::redundant_closure_call)]
+ match (move || #block)() {
+ #[allow(clippy::unit_arg)]
+ Ok(x) => {
+ #ret_event;
+ Ok(x)
+ },
+ Err(e) => {
+ #err_event;
+ Err(e)
+ }
+ }
+ },
+ (Some(err_event), None) => quote_spanned!(block.span()=>
+ #span
+ #[allow(clippy::redundant_closure_call)]
+ match (move || #block)() {
+ #[allow(clippy::unit_arg)]
+ Ok(x) => Ok(x),
+ Err(e) => {
+ #err_event;
+ Err(e)
+ }
+ }
+ ),
+ (None, Some(ret_event)) => quote_spanned!(block.span()=>
+ #span
+ #[allow(clippy::redundant_closure_call)]
+ let x = (move || #block)();
+ #ret_event;
+ x
+ ),
+ (None, None) => quote_spanned!(block.span() =>
+ // Because `quote` produces a stream of tokens _without_ whitespace, the
+ // `if` and the block will appear directly next to each other. This
+ // generates a clippy lint about suspicious `if/else` formatting.
+ // Therefore, suppress the lint inside the generated code...
+ #[allow(clippy::suspicious_else_formatting)]
+ {
+ #span
+ // ...but turn the lint back on inside the function body.
+ #[warn(clippy::suspicious_else_formatting)]
+ #block
+ }
+ ),
+ }
+}
+
+/// Indicates whether a field should be recorded as `Value` or `Debug`.
+enum RecordType {
+ /// The field should be recorded using its `Value` implementation.
+ Value,
+ /// The field should be recorded using `tracing::field::debug()`.
+ Debug,
+}
+
+impl RecordType {
+ /// Array of primitive types which should be recorded as [RecordType::Value].
+ const TYPES_FOR_VALUE: &'static [&'static str] = &[
+ "bool",
+ "str",
+ "u8",
+ "i8",
+ "u16",
+ "i16",
+ "u32",
+ "i32",
+ "u64",
+ "i64",
+ "f32",
+ "f64",
+ "usize",
+ "isize",
+ "NonZeroU8",
+ "NonZeroI8",
+ "NonZeroU16",
+ "NonZeroI16",
+ "NonZeroU32",
+ "NonZeroI32",
+ "NonZeroU64",
+ "NonZeroI64",
+ "NonZeroUsize",
+ "NonZeroIsize",
+ "Wrapping",
+ ];
+
+ /// Parse `RecordType` from [Type] by looking up
+ /// the [RecordType::TYPES_FOR_VALUE] array.
+ fn parse_from_ty(ty: &Type) -> Self {
+ match ty {
+ Type::Path(TypePath { path, .. })
+ if path
+ .segments
+ .iter()
+ .last()
+ .map(|path_segment| {
+ let ident = path_segment.ident.to_string();
+ Self::TYPES_FOR_VALUE.iter().any(|&t| t == ident)
+ })
+ .unwrap_or(false) =>
+ {
+ RecordType::Value
+ }
+ Type::Reference(syn::TypeReference { elem, .. }) => RecordType::parse_from_ty(elem),
+ _ => RecordType::Debug,
+ }
+ }
+}
+
+fn param_names(pat: Pat, record_type: RecordType) -> Box<dyn Iterator<Item = (Ident, RecordType)>> {
+ match pat {
+ Pat::Ident(PatIdent { ident, .. }) => Box::new(iter::once((ident, record_type))),
+ Pat::Reference(PatReference { pat, .. }) => param_names(*pat, record_type),
+ // We can't get the concrete type of fields in the struct/tuple
+ // patterns by using `syn`. e.g. `fn foo(Foo { x, y }: Foo) {}`.
+ // Therefore, the struct/tuple patterns in the arguments will just
+ // always be recorded as `RecordType::Debug`.
+ Pat::Struct(PatStruct { fields, .. }) => Box::new(
+ fields
+ .into_iter()
+ .flat_map(|FieldPat { pat, .. }| param_names(*pat, RecordType::Debug)),
+ ),
+ Pat::Tuple(PatTuple { elems, .. }) => Box::new(
+ elems
+ .into_iter()
+ .flat_map(|p| param_names(p, RecordType::Debug)),
+ ),
+ Pat::TupleStruct(PatTupleStruct {
+ pat: PatTuple { elems, .. },
+ ..
+ }) => Box::new(
+ elems
+ .into_iter()
+ .flat_map(|p| param_names(p, RecordType::Debug)),
+ ),
+
+ // The above *should* cover all cases of irrefutable patterns,
+ // but we purposefully don't do any funny business here
+ // (such as panicking) because that would obscure rustc's
+ // much more informative error message.
+ _ => Box::new(iter::empty()),
+ }
+}
+
+/// The specific async code pattern that was detected
+enum AsyncKind<'a> {
+ /// Immediately-invoked async fn, as generated by `async-trait <= 0.1.43`:
+ /// `async fn foo<...>(...) {...}; Box::pin(foo<...>(...))`
+ Function(&'a ItemFn),
+ /// A function returning an async (move) block, optionally `Box::pin`-ed,
+ /// as generated by `async-trait >= 0.1.44`:
+ /// `Box::pin(async move { ... })`
+ Async {
+ async_expr: &'a ExprAsync,
+ pinned_box: bool,
+ },
+}
+
+pub(crate) struct AsyncInfo<'block> {
+ // statement that must be patched
+ source_stmt: &'block Stmt,
+ kind: AsyncKind<'block>,
+ self_type: Option<TypePath>,
+ input: &'block ItemFn,
+}
+
+impl<'block> AsyncInfo<'block> {
+ /// Get the AST of the inner function we need to hook, if it looks like a
+ /// manual future implementation.
+ ///
+ /// When we are given a function that returns a (pinned) future containing the
+ /// user logic, it is that (pinned) future that needs to be instrumented.
+ /// Were we to instrument its parent, we would only collect information
+ /// regarding the allocation of that future, and not its own span of execution.
+ ///
+ /// We inspect the block of the function to find if it matches any of the
+ /// following patterns:
+ ///
+ /// - Immediately-invoked async fn, as generated by `async-trait <= 0.1.43`:
+ /// `async fn foo<...>(...) {...}; Box::pin(foo<...>(...))`
+ ///
+ /// - A function returning an async (move) block, optionally `Box::pin`-ed,
+ /// as generated by `async-trait >= 0.1.44`:
+ /// `Box::pin(async move { ... })`
+ ///
+ /// We the return the statement that must be instrumented, along with some
+ /// other information.
+ /// 'gen_body' will then be able to use that information to instrument the
+ /// proper function/future.
+ ///
+ /// (this follows the approach suggested in
+ /// https://github.com/dtolnay/async-trait/issues/45#issuecomment-571245673)
+ pub(crate) fn from_fn(input: &'block ItemFn) -> Option<Self> {
+ // are we in an async context? If yes, this isn't a manual async-like pattern
+ if input.sig.asyncness.is_some() {
+ return None;
+ }
+
+ let block = &input.block;
+
+ // list of async functions declared inside the block
+ let inside_funs = block.stmts.iter().filter_map(|stmt| {
+ if let Stmt::Item(Item::Fn(fun)) = &stmt {
+ // If the function is async, this is a candidate
+ if fun.sig.asyncness.is_some() {
+ return Some((stmt, fun));
+ }
+ }
+ None
+ });
+
+ // last expression of the block: it determines the return value of the
+ // block, this is quite likely a `Box::pin` statement or an async block
+ let (last_expr_stmt, last_expr) = block.stmts.iter().rev().find_map(|stmt| {
+ if let Stmt::Expr(expr) = stmt {
+ Some((stmt, expr))
+ } else {
+ None
+ }
+ })?;
+
+ // is the last expression an async block?
+ if let Expr::Async(async_expr) = last_expr {
+ return Some(AsyncInfo {
+ source_stmt: last_expr_stmt,
+ kind: AsyncKind::Async {
+ async_expr,
+ pinned_box: false,
+ },
+ self_type: None,
+ input,
+ });
+ }
+
+ // is the last expression a function call?
+ let (outside_func, outside_args) = match last_expr {
+ Expr::Call(ExprCall { func, args, .. }) => (func, args),
+ _ => return None,
+ };
+
+ // is it a call to `Box::pin()`?
+ let path = match outside_func.as_ref() {
+ Expr::Path(path) => &path.path,
+ _ => return None,
+ };
+ if !path_to_string(path).ends_with("Box::pin") {
+ return None;
+ }
+
+ // Does the call take an argument? If it doesn't,
+ // it's not gonna compile anyway, but that's no reason
+ // to (try to) perform an out of bounds access
+ if outside_args.is_empty() {
+ return None;
+ }
+
+ // Is the argument to Box::pin an async block that
+ // captures its arguments?
+ if let Expr::Async(async_expr) = &outside_args[0] {
+ return Some(AsyncInfo {
+ source_stmt: last_expr_stmt,
+ kind: AsyncKind::Async {
+ async_expr,
+ pinned_box: true,
+ },
+ self_type: None,
+ input,
+ });
+ }
+
+ // Is the argument to Box::pin a function call itself?
+ let func = match &outside_args[0] {
+ Expr::Call(ExprCall { func, .. }) => func,
+ _ => return None,
+ };
+
+ // "stringify" the path of the function called
+ let func_name = match **func {
+ Expr::Path(ref func_path) => path_to_string(&func_path.path),
+ _ => return None,
+ };
+
+ // Was that function defined inside of the current block?
+ // If so, retrieve the statement where it was declared and the function itself
+ let (stmt_func_declaration, func) = inside_funs
+ .into_iter()
+ .find(|(_, fun)| fun.sig.ident == func_name)?;
+
+ // If "_self" is present as an argument, we store its type to be able to rewrite "Self" (the
+ // parameter type) with the type of "_self"
+ let mut self_type = None;
+ for arg in &func.sig.inputs {
+ if let FnArg::Typed(ty) = arg {
+ if let Pat::Ident(PatIdent { ref ident, .. }) = *ty.pat {
+ if ident == "_self" {
+ let mut ty = *ty.ty.clone();
+ // extract the inner type if the argument is "&self" or "&mut self"
+ if let Type::Reference(syn::TypeReference { elem, .. }) = ty {
+ ty = *elem;
+ }
+
+ if let Type::Path(tp) = ty {
+ self_type = Some(tp);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ Some(AsyncInfo {
+ source_stmt: stmt_func_declaration,
+ kind: AsyncKind::Function(func),
+ self_type,
+ input,
+ })
+ }
+
+ pub(crate) fn gen_async(
+ self,
+ args: InstrumentArgs,
+ instrumented_function_name: &str,
+ ) -> Result<proc_macro::TokenStream, syn::Error> {
+ // let's rewrite some statements!
+ let mut out_stmts: Vec<TokenStream> = self
+ .input
+ .block
+ .stmts
+ .iter()
+ .map(|stmt| stmt.to_token_stream())
+ .collect();
+
+ if let Some((iter, _stmt)) = self
+ .input
+ .block
+ .stmts
+ .iter()
+ .enumerate()
+ .find(|(_iter, stmt)| *stmt == self.source_stmt)
+ {
+ // instrument the future by rewriting the corresponding statement
+ out_stmts[iter] = match self.kind {
+ // `Box::pin(immediately_invoked_async_fn())`
+ AsyncKind::Function(fun) => {
+ let fun = MaybeItemFn::from(fun.clone());
+ gen_function(
+ fun.as_ref(),
+ args,
+ instrumented_function_name,
+ self.self_type.as_ref(),
+ )
+ }
+ // `async move { ... }`, optionally pinned
+ AsyncKind::Async {
+ async_expr,
+ pinned_box,
+ } => {
+ let instrumented_block = gen_block(
+ &async_expr.block,
+ &self.input.sig.inputs,
+ true,
+ args,
+ instrumented_function_name,
+ None,
+ );
+ let async_attrs = &async_expr.attrs;
+ if pinned_box {
+ quote! {
+ Box::pin(#(#async_attrs) * async move { #instrumented_block })
+ }
+ } else {
+ quote! {
+ #(#async_attrs) * async move { #instrumented_block }
+ }
+ }
+ }
+ };
+ }
+
+ let vis = &self.input.vis;
+ let sig = &self.input.sig;
+ let attrs = &self.input.attrs;
+ Ok(quote!(
+ #(#attrs) *
+ #vis #sig {
+ #(#out_stmts) *
+ }
+ )
+ .into())
+ }
+}
+
+// Return a path as a String
+fn path_to_string(path: &Path) -> String {
+ use std::fmt::Write;
+ // some heuristic to prevent too many allocations
+ let mut res = String::with_capacity(path.segments.len() * 5);
+ for i in 0..path.segments.len() {
+ write!(&mut res, "{}", path.segments[i].ident)
+ .expect("writing to a String should never fail");
+ if i < path.segments.len() - 1 {
+ res.push_str("::");
+ }
+ }
+ res
+}
+
+/// A visitor struct to replace idents and types in some piece
+/// of code (e.g. the "self" and "Self" tokens in user-supplied
+/// fields expressions when the function is generated by an old
+/// version of async-trait).
+struct IdentAndTypesRenamer<'a> {
+ types: Vec<(&'a str, TypePath)>,
+ idents: Vec<(Ident, Ident)>,
+}
+
+impl<'a> VisitMut for IdentAndTypesRenamer<'a> {
+ // we deliberately compare strings because we want to ignore the spans
+ // If we apply clippy's lint, the behavior changes
+ #[allow(clippy::cmp_owned)]
+ fn visit_ident_mut(&mut self, id: &mut Ident) {
+ for (old_ident, new_ident) in &self.idents {
+ if id.to_string() == old_ident.to_string() {
+ *id = new_ident.clone();
+ }
+ }
+ }
+
+ fn visit_type_mut(&mut self, ty: &mut Type) {
+ for (type_name, new_type) in &self.types {
+ if let Type::Path(TypePath { path, .. }) = ty {
+ if path_to_string(path) == *type_name {
+ *ty = Type::Path(new_type.clone());
+ }
+ }
+ }
+ }
+}
+
+// A visitor struct that replace an async block by its patched version
+struct AsyncTraitBlockReplacer<'a> {
+ block: &'a Block,
+ patched_block: Block,
+}
+
+impl<'a> VisitMut for AsyncTraitBlockReplacer<'a> {
+ fn visit_block_mut(&mut self, i: &mut Block) {
+ if i == self.block {
+ *i = self.patched_block.clone();
+ }
+ }
+}
+
+// Replaces any `impl Trait` with `_` so it can be used as the type in
+// a `let` statement's LHS.
+struct ImplTraitEraser;
+
+impl VisitMut for ImplTraitEraser {
+ fn visit_type_mut(&mut self, t: &mut Type) {
+ if let Type::ImplTrait(..) = t {
+ *t = syn::TypeInfer {
+ underscore_token: Token![_](t.span()),
+ }
+ .into();
+ } else {
+ syn::visit_mut::visit_type_mut(self, t);
+ }
+ }
+}
+
+fn erase_impl_trait(ty: &Type) -> Type {
+ let mut ty = ty.clone();
+ ImplTraitEraser.visit_type_mut(&mut ty);
+ ty
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..f5974e4
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,677 @@
+//! A procedural macro attribute for instrumenting functions with [`tracing`].
+//!
+//! [`tracing`] is a framework for instrumenting Rust programs to collect
+//! structured, event-based diagnostic information. This crate provides the
+//! [`#[instrument]`][instrument] procedural macro attribute.
+//!
+//! Note that this macro is also re-exported by the main `tracing` crate.
+//!
+//! *Compiler support: [requires `rustc` 1.49+][msrv]*
+//!
+//! [msrv]: #supported-rust-versions
+//!
+//! ## Usage
+//!
+//! First, add this to your `Cargo.toml`:
+//!
+//! ```toml
+//! [dependencies]
+//! tracing-attributes = "0.1.23"
+//! ```
+//!
+//! The [`#[instrument]`][instrument] attribute can now be added to a function
+//! to automatically create and enter `tracing` [span] when that function is
+//! called. For example:
+//!
+//! ```
+//! use tracing_attributes::instrument;
+//!
+//! #[instrument]
+//! pub fn my_function(my_arg: usize) {
+//! // ...
+//! }
+//!
+//! # fn main() {}
+//! ```
+//!
+//! [`tracing`]: https://crates.io/crates/tracing
+//! [span]: https://docs.rs/tracing/latest/tracing/span/index.html
+//! [instrument]: macro@self::instrument
+//!
+//! ## Supported Rust Versions
+//!
+//! Tracing is built against the latest stable release. The minimum supported
+//! version is 1.49. The current Tracing version is not guaranteed to build on
+//! Rust versions earlier than the minimum supported version.
+//!
+//! Tracing follows the same compiler support policies as the rest of the Tokio
+//! project. The current stable Rust compiler and the three most recent minor
+//! versions before it will always be supported. For example, if the current
+//! stable compiler version is 1.45, the minimum supported version will not be
+//! increased past 1.42, three minor versions prior. Increasing the minimum
+//! supported compiler version is not considered a semver breaking change as
+//! long as doing so complies with this policy.
+//!
+#![doc(html_root_url = "https://docs.rs/tracing-attributes/0.1.23")]
+#![doc(
+ html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png",
+ issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/"
+)]
+#![cfg_attr(docsrs, deny(rustdoc::broken_intra_doc_links))]
+#![warn(
+ missing_debug_implementations,
+ missing_docs,
+ rust_2018_idioms,
+ unreachable_pub,
+ bad_style,
+ const_err,
+ dead_code,
+ improper_ctypes,
+ non_shorthand_field_patterns,
+ no_mangle_generic_items,
+ overflowing_literals,
+ path_statements,
+ patterns_in_fns_without_body,
+ private_in_public,
+ unconditional_recursion,
+ unused_allocation,
+ unused_comparisons,
+ unused_parens,
+ while_true
+)]
+// TODO: once `tracing` bumps its MSRV to 1.42, remove this allow.
+#![allow(unused)]
+extern crate proc_macro;
+
+use proc_macro2::TokenStream;
+use quote::ToTokens;
+use syn::parse::{Parse, ParseStream};
+use syn::{Attribute, ItemFn, Signature, Visibility};
+
+mod attr;
+mod expand;
+/// Instruments a function to create and enter a `tracing` [span] every time
+/// the function is called.
+///
+/// Unless overriden, a span with the [`INFO`] [level] will be generated.
+/// The generated span's name will be the name of the function.
+/// By default, all arguments to the function are included as fields on the
+/// span. Arguments that are `tracing` [primitive types] implementing the
+/// [`Value` trait] will be recorded as fields of that type. Types which do
+/// not implement `Value` will be recorded using [`std::fmt::Debug`].
+///
+/// [primitive types]: https://docs.rs/tracing/latest/tracing/field/trait.Value.html#foreign-impls
+/// [`Value` trait]: https://docs.rs/tracing/latest/tracing/field/trait.Value.html.
+///
+/// # Overriding Span Attributes
+///
+/// To change the [name] of the generated span, add a `name` argument to the
+/// `#[instrument]` macro, followed by an equals sign and a string literal. For
+/// example:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+///
+/// // The generated span's name will be "my_span" rather than "my_function".
+/// #[instrument(name = "my_span")]
+/// pub fn my_function() {
+/// // ... do something incredibly interesting and important ...
+/// }
+/// ```
+///
+/// To override the [target] of the generated span, add a `target` argument to
+/// the `#[instrument]` macro, followed by an equals sign and a string literal
+/// for the new target. The [module path] is still recorded separately. For
+/// example:
+///
+/// ```
+/// pub mod my_module {
+/// # use tracing_attributes::instrument;
+/// // The generated span's target will be "my_crate::some_special_target",
+/// // rather than "my_crate::my_module".
+/// #[instrument(target = "my_crate::some_special_target")]
+/// pub fn my_function() {
+/// // ... all kinds of neat code in here ...
+/// }
+/// }
+/// ```
+///
+/// Finally, to override the [level] of the generated span, add a `level`
+/// argument, followed by an equals sign and a string literal with the name of
+/// the desired level. Level names are not case sensitive. For example:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+/// // The span's level will be TRACE rather than INFO.
+/// #[instrument(level = "trace")]
+/// pub fn my_function() {
+/// // ... I have written a truly marvelous implementation of this function,
+/// // which this example is too narrow to contain ...
+/// }
+/// ```
+///
+/// # Skipping Fields
+///
+/// To skip recording one or more arguments to a function or method, pass
+/// the argument's name inside the `skip()` argument on the `#[instrument]`
+/// macro. This can be used when an argument to an instrumented function does
+/// not implement [`fmt::Debug`], or to exclude an argument with a verbose or
+/// costly `Debug` implementation. Note that:
+///
+/// - multiple argument names can be passed to `skip`.
+/// - arguments passed to `skip` do _not_ need to implement `fmt::Debug`.
+///
+/// You can also use `skip_all` to skip all arguments.
+///
+/// ## Examples
+///
+/// ```
+/// # use tracing_attributes::instrument;
+/// # use std::collections::HashMap;
+/// // This type doesn't implement `fmt::Debug`!
+/// struct NonDebug;
+///
+/// // `arg` will be recorded, while `non_debug` will not.
+/// #[instrument(skip(non_debug))]
+/// fn my_function(arg: usize, non_debug: NonDebug) {
+/// // ...
+/// }
+///
+/// // These arguments are huge
+/// #[instrument(skip_all)]
+/// fn my_big_data_function(large: Vec<u8>, also_large: HashMap<String, String>) {
+/// // ...
+/// }
+/// ```
+///
+/// Skipping the `self` parameter:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[derive(Debug)]
+/// struct MyType {
+/// data: Vec<u8>, // Suppose this buffer is often quite long...
+/// }
+///
+/// impl MyType {
+/// // Suppose we don't want to print an entire kilobyte of `data`
+/// // every time this is called...
+/// #[instrument(skip(self))]
+/// pub fn my_method(&mut self, an_interesting_argument: usize) {
+/// // ... do something (hopefully, using all that `data`!)
+/// }
+/// }
+/// ```
+///
+/// # Adding Fields
+///
+/// Additional fields (key-value pairs with arbitrary data) may be added to the
+/// generated span using the `fields` argument on the `#[instrument]` macro. Any
+/// Rust expression can be used as a field value in this manner. These
+/// expressions will be evaluated at the beginning of the function's body, so
+/// arguments to the function may be used in these expressions. Field names may
+/// also be specified *without* values. Doing so will result in an [empty field]
+/// whose value may be recorded later within the function body.
+///
+/// This supports the same [field syntax] as the `span!` and `event!` macros.
+///
+/// Note that overlap between the names of fields and (non-skipped) arguments
+/// will result in a compile error.
+///
+/// ## Examples
+///
+/// Adding a new field based on the value of an argument:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+///
+/// // This will record a field named "i" with the value of `i` *and* a field
+/// // named "next" with the value of `i` + 1.
+/// #[instrument(fields(next = i + 1))]
+/// pub fn my_function(i: usize) {
+/// // ...
+/// }
+/// ```
+///
+/// Recording specific properties of a struct as their own fields:
+///
+/// ```
+/// # mod http {
+/// # pub struct Error;
+/// # pub struct Response<B> { pub(super) _b: std::marker::PhantomData<B> }
+/// # pub struct Request<B> { _b: B }
+/// # impl<B> std::fmt::Debug for Request<B> {
+/// # fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+/// # f.pad("request")
+/// # }
+/// # }
+/// # impl<B> Request<B> {
+/// # pub fn uri(&self) -> &str { "fake" }
+/// # pub fn method(&self) -> &str { "GET" }
+/// # }
+/// # }
+/// # use tracing_attributes::instrument;
+///
+/// // This will record the request's URI and HTTP method as their own separate
+/// // fields.
+/// #[instrument(fields(http.uri = req.uri(), http.method = req.method()))]
+/// pub fn handle_request<B>(req: http::Request<B>) -> http::Response<B> {
+/// // ... handle the request ...
+/// # http::Response { _b: std::marker::PhantomData }
+/// }
+/// ```
+///
+/// This can be used in conjunction with `skip` or `skip_all` to record only
+/// some fields of a struct:
+/// ```
+/// # use tracing_attributes::instrument;
+/// // Remember the struct with the very large `data` field from the earlier
+/// // example? Now it also has a `name`, which we might want to include in
+/// // our span.
+/// #[derive(Debug)]
+/// struct MyType {
+/// name: &'static str,
+/// data: Vec<u8>,
+/// }
+///
+/// impl MyType {
+/// // This will skip the `data` field, but will include `self.name`,
+/// // formatted using `fmt::Display`.
+/// #[instrument(skip(self), fields(self.name = %self.name))]
+/// pub fn my_method(&mut self, an_interesting_argument: usize) {
+/// // ... do something (hopefully, using all that `data`!)
+/// }
+/// }
+/// ```
+///
+/// Adding an empty field to be recorded later:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+///
+/// // This function does a very interesting and important mathematical calculation.
+/// // Suppose we want to record both the inputs to the calculation *and* its result...
+/// #[instrument(fields(result))]
+/// pub fn do_calculation(input_1: usize, input_2: usize) -> usize {
+/// // Rerform the calculation.
+/// let result = input_1 + input_2;
+///
+/// // Record the result as part of the current span.
+/// tracing::Span::current().record("result", &result);
+///
+/// // Now, the result will also be included on this event!
+/// tracing::info!("calculation complete!");
+///
+/// // ... etc ...
+/// # 0
+/// }
+/// ```
+///
+/// # Examples
+///
+/// Instrumenting a function:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument]
+/// pub fn my_function(my_arg: usize) {
+/// // This event will be recorded inside a span named `my_function` with the
+/// // field `my_arg`.
+/// tracing::info!("inside my_function!");
+/// // ...
+/// }
+/// ```
+/// Setting the level for the generated span:
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument(level = "debug")]
+/// pub fn my_function() {
+/// // ...
+/// }
+/// ```
+/// Overriding the generated span's name:
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument(name = "my_name")]
+/// pub fn my_function() {
+/// // ...
+/// }
+/// ```
+/// Overriding the generated span's target:
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument(target = "my_target")]
+/// pub fn my_function() {
+/// // ...
+/// }
+/// ```
+/// Overriding the generated span's parent:
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument(parent = None)]
+/// pub fn my_function() {
+/// // ...
+/// }
+/// ```
+/// ```
+/// # use tracing_attributes::instrument;
+/// // A struct which owns a span handle.
+/// struct MyStruct
+/// {
+/// span: tracing::Span
+/// }
+///
+/// impl MyStruct
+/// {
+/// // Use the struct's `span` field as the parent span
+/// #[instrument(parent = &self.span, skip(self))]
+/// fn my_method(&self) {}
+/// }
+/// ```
+/// Specifying [`follows_from`] relationships:
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument(follows_from = causes)]
+/// pub fn my_function(causes: &[tracing::Id]) {
+/// // ...
+/// }
+/// ```
+/// Any expression of type `impl IntoIterator<Item = impl Into<Option<Id>>>`
+/// may be provided to `follows_from`; e.g.:
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument(follows_from = [cause])]
+/// pub fn my_function(cause: &tracing::span::EnteredSpan) {
+/// // ...
+/// }
+/// ```
+///
+///
+/// To skip recording an argument, pass the argument's name to the `skip`:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+/// struct NonDebug;
+///
+/// #[instrument(skip(non_debug))]
+/// fn my_function(arg: usize, non_debug: NonDebug) {
+/// // ...
+/// }
+/// ```
+///
+/// To add an additional context to the span, pass key-value pairs to `fields`:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument(fields(foo="bar", id=1, show=true))]
+/// fn my_function(arg: usize) {
+/// // ...
+/// }
+/// ```
+///
+/// Adding the `ret` argument to `#[instrument]` will emit an event with the function's
+/// return value when the function returns:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument(ret)]
+/// fn my_function() -> i32 {
+/// 42
+/// }
+/// ```
+/// The return value event will have the same level as the span generated by `#[instrument]`.
+/// By default, this will be [`INFO`], but if the level is overridden, the event will be at the same
+/// level.
+///
+/// **Note**: if the function returns a `Result<T, E>`, `ret` will record returned values if and
+/// only if the function returns [`Result::Ok`].
+///
+/// By default, returned values will be recorded using their [`std::fmt::Debug`] implementations.
+/// If a returned value implements [`std::fmt::Display`], it can be recorded using its `Display`
+/// implementation instead, by writing `ret(Display)`:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument(ret(Display))]
+/// fn my_function() -> i32 {
+/// 42
+/// }
+/// ```
+///
+/// If the function returns a `Result<T, E>` and `E` implements `std::fmt::Display`, you can add
+/// `err` or `err(Display)` to emit error events when the function returns `Err`:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument(err)]
+/// fn my_function(arg: usize) -> Result<(), std::io::Error> {
+/// Ok(())
+/// }
+/// ```
+///
+/// By default, error values will be recorded using their `std::fmt::Display` implementations.
+/// If an error implements `std::fmt::Debug`, it can be recorded using its `Debug` implementation
+/// instead, by writing `err(Debug)`:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument(err(Debug))]
+/// fn my_function(arg: usize) -> Result<(), std::io::Error> {
+/// Ok(())
+/// }
+/// ```
+///
+/// If a `target` is specified, both the `ret` and `err` arguments will emit outputs to
+/// the declared target (or the default channel if `target` is not specified).
+///
+/// The `ret` and `err` arguments can be combined in order to record an event if a
+/// function returns [`Result::Ok`] or [`Result::Err`]:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument(err, ret)]
+/// fn my_function(arg: usize) -> Result<(), std::io::Error> {
+/// Ok(())
+/// }
+/// ```
+///
+/// `async fn`s may also be instrumented:
+///
+/// ```
+/// # use tracing_attributes::instrument;
+/// #[instrument]
+/// pub async fn my_function() -> Result<(), ()> {
+/// // ...
+/// # Ok(())
+/// }
+/// ```
+///
+/// It also works with [async-trait](https://crates.io/crates/async-trait)
+/// (a crate that allows defining async functions in traits,
+/// something not currently possible in Rust),
+/// and hopefully most libraries that exhibit similar behaviors:
+///
+/// ```
+/// # use tracing::instrument;
+/// use async_trait::async_trait;
+///
+/// #[async_trait]
+/// pub trait Foo {
+/// async fn foo(&self, arg: usize);
+/// }
+///
+/// #[derive(Debug)]
+/// struct FooImpl(usize);
+///
+/// #[async_trait]
+/// impl Foo for FooImpl {
+/// #[instrument(fields(value = self.0, tmp = std::any::type_name::<Self>()))]
+/// async fn foo(&self, arg: usize) {}
+/// }
+/// ```
+///
+/// Note than on `async-trait` <= 0.1.43, references to the `Self`
+/// type inside the `fields` argument were only allowed when the instrumented
+/// function is a method (i.e., the function receives `self` as an argument).
+/// For example, this *used to not work* because the instrument function
+/// didn't receive `self`:
+/// ```
+/// # use tracing::instrument;
+/// use async_trait::async_trait;
+///
+/// #[async_trait]
+/// pub trait Bar {
+/// async fn bar();
+/// }
+///
+/// #[derive(Debug)]
+/// struct BarImpl(usize);
+///
+/// #[async_trait]
+/// impl Bar for BarImpl {
+/// #[instrument(fields(tmp = std::any::type_name::<Self>()))]
+/// async fn bar() {}
+/// }
+/// ```
+/// Instead, you should manually rewrite any `Self` types as the type for
+/// which you implement the trait: `#[instrument(fields(tmp = std::any::type_name::<Bar>()))]`
+/// (or maybe you can just bump `async-trait`).
+///
+/// [span]: https://docs.rs/tracing/latest/tracing/span/index.html
+/// [name]: https://docs.rs/tracing/latest/tracing/struct.Metadata.html#method.name
+/// [target]: https://docs.rs/tracing/latest/tracing/struct.Metadata.html#method.target
+/// [level]: https://docs.rs/tracing/latest/tracing/struct.Level.html
+/// [module path]: https://docs.rs/tracing/latest/tracing/struct.Metadata.html#method.module_path
+/// [`INFO`]: https://docs.rs/tracing/latest/tracing/struct.Level.html#associatedconstant.INFO
+/// [empty field]: https://docs.rs/tracing/latest/tracing/field/struct.Empty.html
+/// [field syntax]: https://docs.rs/tracing/latest/tracing/#recording-fields
+/// [`follows_from`]: https://docs.rs/tracing/latest/tracing/struct.Span.html#method.follows_from
+/// [`tracing`]: https://github.com/tokio-rs/tracing
+/// [`fmt::Debug`]: std::fmt::Debug
+#[proc_macro_attribute]
+pub fn instrument(
+ args: proc_macro::TokenStream,
+ item: proc_macro::TokenStream,
+) -> proc_macro::TokenStream {
+ let args = syn::parse_macro_input!(args as attr::InstrumentArgs);
+ // Cloning a `TokenStream` is cheap since it's reference counted internally.
+ instrument_precise(args.clone(), item.clone())
+ .unwrap_or_else(|_err| instrument_speculative(args, item))
+}
+
+/// Instrument the function, without parsing the function body (instead using the raw tokens).
+fn instrument_speculative(
+ args: attr::InstrumentArgs,
+ item: proc_macro::TokenStream,
+) -> proc_macro::TokenStream {
+ let input = syn::parse_macro_input!(item as MaybeItemFn);
+ let instrumented_function_name = input.sig.ident.to_string();
+ expand::gen_function(
+ input.as_ref(),
+ args,
+ instrumented_function_name.as_str(),
+ None,
+ )
+ .into()
+}
+
+/// Instrument the function, by fully parsing the function body,
+/// which allows us to rewrite some statements related to async-like patterns.
+fn instrument_precise(
+ args: attr::InstrumentArgs,
+ item: proc_macro::TokenStream,
+) -> Result<proc_macro::TokenStream, syn::Error> {
+ let input = syn::parse::<ItemFn>(item)?;
+ let instrumented_function_name = input.sig.ident.to_string();
+
+ // check for async_trait-like patterns in the block, and instrument
+ // the future instead of the wrapper
+ if let Some(async_like) = expand::AsyncInfo::from_fn(&input) {
+ return async_like.gen_async(args, instrumented_function_name.as_str());
+ }
+
+ let input = MaybeItemFn::from(input);
+
+ Ok(expand::gen_function(
+ input.as_ref(),
+ args,
+ instrumented_function_name.as_str(),
+ None,
+ )
+ .into())
+}
+
+/// This is a more flexible/imprecise `ItemFn` type,
+/// which's block is just a `TokenStream` (it may contain invalid code).
+#[derive(Debug, Clone)]
+struct MaybeItemFn {
+ outer_attrs: Vec<Attribute>,
+ inner_attrs: Vec<Attribute>,
+ vis: Visibility,
+ sig: Signature,
+ block: TokenStream,
+}
+
+impl MaybeItemFn {
+ fn as_ref(&self) -> MaybeItemFnRef<'_, TokenStream> {
+ MaybeItemFnRef {
+ outer_attrs: &self.outer_attrs,
+ inner_attrs: &self.inner_attrs,
+ vis: &self.vis,
+ sig: &self.sig,
+ block: &self.block,
+ }
+ }
+}
+
+/// This parses a `TokenStream` into a `MaybeItemFn`
+/// (just like `ItemFn`, but skips parsing the body).
+impl Parse for MaybeItemFn {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ let outer_attrs = input.call(Attribute::parse_outer)?;
+ let vis: Visibility = input.parse()?;
+ let sig: Signature = input.parse()?;
+ let inner_attrs = input.call(Attribute::parse_inner)?;
+ let block: TokenStream = input.parse()?;
+ Ok(Self {
+ outer_attrs,
+ inner_attrs,
+ vis,
+ sig,
+ block,
+ })
+ }
+}
+
+impl From<ItemFn> for MaybeItemFn {
+ fn from(
+ ItemFn {
+ attrs,
+ vis,
+ sig,
+ block,
+ }: ItemFn,
+ ) -> Self {
+ let (outer_attrs, inner_attrs) = attrs
+ .into_iter()
+ .partition(|attr| attr.style == syn::AttrStyle::Outer);
+ Self {
+ outer_attrs,
+ inner_attrs,
+ vis,
+ sig,
+ block: block.to_token_stream(),
+ }
+ }
+}
+
+/// A generic reference type for `MaybeItemFn`,
+/// that takes a generic block type `B` that implements `ToTokens` (eg. `TokenStream`, `Block`).
+#[derive(Debug, Clone)]
+struct MaybeItemFnRef<'a, B: ToTokens> {
+ outer_attrs: &'a Vec<Attribute>,
+ inner_attrs: &'a Vec<Attribute>,
+ vis: &'a Visibility,
+ sig: &'a Signature,
+ block: &'a B,
+}
diff --git a/tests/async_fn.rs b/tests/async_fn.rs
new file mode 100644
index 0000000..c899636
--- /dev/null
+++ b/tests/async_fn.rs
@@ -0,0 +1,462 @@
+use tracing_mock::*;
+
+use std::convert::Infallible;
+use std::{future::Future, pin::Pin, sync::Arc};
+use tracing::subscriber::with_default;
+use tracing_attributes::instrument;
+
+#[instrument]
+async fn test_async_fn(polls: usize) -> Result<(), ()> {
+ let future = PollN::new_ok(polls);
+ tracing::trace!(awaiting = true);
+ future.await
+}
+
+// Reproduces a compile error when returning an `impl Trait` from an
+// instrumented async fn (see https://github.com/tokio-rs/tracing/issues/1615)
+#[allow(dead_code)] // this is just here to test whether it compiles.
+#[instrument]
+async fn test_ret_impl_trait(n: i32) -> Result<impl Iterator<Item = i32>, ()> {
+ let n = n;
+ Ok((0..10).filter(move |x| *x < n))
+}
+
+// Reproduces a compile error when returning an `impl Trait` from an
+// instrumented async fn (see https://github.com/tokio-rs/tracing/issues/1615)
+#[allow(dead_code)] // this is just here to test whether it compiles.
+#[instrument(err)]
+async fn test_ret_impl_trait_err(n: i32) -> Result<impl Iterator<Item = i32>, &'static str> {
+ Ok((0..10).filter(move |x| *x < n))
+}
+
+#[instrument]
+async fn test_async_fn_empty() {}
+
+// Reproduces a compile error when an instrumented function body contains inner
+// attributes (https://github.com/tokio-rs/tracing/issues/2294).
+#[deny(unused_variables)]
+#[instrument]
+async fn repro_async_2294() {
+ #![allow(unused_variables)]
+ let i = 42;
+}
+
+// Reproduces https://github.com/tokio-rs/tracing/issues/1613
+#[instrument]
+// LOAD-BEARING `#[rustfmt::skip]`! This is necessary to reproduce the bug;
+// with the rustfmt-generated formatting, the lint will not be triggered!
+#[rustfmt::skip]
+#[deny(clippy::suspicious_else_formatting)]
+async fn repro_1613(var: bool) {
+ println!(
+ "{}",
+ if var { "true" } else { "false" }
+ );
+}
+
+// Reproduces https://github.com/tokio-rs/tracing/issues/1613
+// and https://github.com/rust-lang/rust-clippy/issues/7760
+#[instrument]
+#[deny(clippy::suspicious_else_formatting)]
+async fn repro_1613_2() {
+ // hello world
+ // else
+}
+
+// Reproduces https://github.com/tokio-rs/tracing/issues/1831
+#[allow(dead_code)] // this is just here to test whether it compiles.
+#[instrument]
+#[deny(unused_braces)]
+fn repro_1831() -> Pin<Box<dyn Future<Output = ()>>> {
+ Box::pin(async move {})
+}
+
+// This replicates the pattern used to implement async trait methods on nightly using the
+// `type_alias_impl_trait` feature
+#[allow(dead_code)] // this is just here to test whether it compiles.
+#[instrument(ret, err)]
+#[deny(unused_braces)]
+#[allow(clippy::manual_async_fn)]
+fn repro_1831_2() -> impl Future<Output = Result<(), Infallible>> {
+ async { Ok(()) }
+}
+
+#[test]
+fn async_fn_only_enters_for_polls() {
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(span::mock().named("test_async_fn"))
+ .enter(span::mock().named("test_async_fn"))
+ .event(event::mock().with_fields(field::mock("awaiting").with_value(&true)))
+ .exit(span::mock().named("test_async_fn"))
+ .enter(span::mock().named("test_async_fn"))
+ .exit(span::mock().named("test_async_fn"))
+ .drop_span(span::mock().named("test_async_fn"))
+ .done()
+ .run_with_handle();
+ with_default(subscriber, || {
+ block_on_future(async { test_async_fn(2).await }).unwrap();
+ });
+ handle.assert_finished();
+}
+
+#[test]
+fn async_fn_nested() {
+ #[instrument]
+ async fn test_async_fns_nested() {
+ test_async_fns_nested_other().await
+ }
+
+ #[instrument]
+ async fn test_async_fns_nested_other() {
+ tracing::trace!(nested = true);
+ }
+
+ let span = span::mock().named("test_async_fns_nested");
+ let span2 = span::mock().named("test_async_fns_nested_other");
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(span.clone())
+ .enter(span.clone())
+ .new_span(span2.clone())
+ .enter(span2.clone())
+ .event(event::mock().with_fields(field::mock("nested").with_value(&true)))
+ .exit(span2.clone())
+ .drop_span(span2)
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ block_on_future(async { test_async_fns_nested().await });
+ });
+
+ handle.assert_finished();
+}
+
+#[test]
+fn async_fn_with_async_trait() {
+ use async_trait::async_trait;
+
+ // test the correctness of the metadata obtained by #[instrument]
+ // (function name, functions parameters) when async-trait is used
+ #[async_trait]
+ pub trait TestA {
+ async fn foo(&mut self, v: usize);
+ }
+
+ // test nesting of async fns with aync-trait
+ #[async_trait]
+ pub trait TestB {
+ async fn bar(&self);
+ }
+
+ // test skip(self) with async-await
+ #[async_trait]
+ pub trait TestC {
+ async fn baz(&self);
+ }
+
+ #[derive(Debug)]
+ struct TestImpl(usize);
+
+ #[async_trait]
+ impl TestA for TestImpl {
+ #[instrument]
+ async fn foo(&mut self, v: usize) {
+ self.baz().await;
+ self.0 = v;
+ self.bar().await
+ }
+ }
+
+ #[async_trait]
+ impl TestB for TestImpl {
+ #[instrument]
+ async fn bar(&self) {
+ tracing::trace!(val = self.0);
+ }
+ }
+
+ #[async_trait]
+ impl TestC for TestImpl {
+ #[instrument(skip(self))]
+ async fn baz(&self) {
+ tracing::trace!(val = self.0);
+ }
+ }
+
+ let span = span::mock().named("foo");
+ let span2 = span::mock().named("bar");
+ let span3 = span::mock().named("baz");
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(
+ span.clone()
+ .with_field(field::mock("self"))
+ .with_field(field::mock("v")),
+ )
+ .enter(span.clone())
+ .new_span(span3.clone())
+ .enter(span3.clone())
+ .event(event::mock().with_fields(field::mock("val").with_value(&2u64)))
+ .exit(span3.clone())
+ .drop_span(span3)
+ .new_span(span2.clone().with_field(field::mock("self")))
+ .enter(span2.clone())
+ .event(event::mock().with_fields(field::mock("val").with_value(&5u64)))
+ .exit(span2.clone())
+ .drop_span(span2)
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ let mut test = TestImpl(2);
+ block_on_future(async { test.foo(5).await });
+ });
+
+ handle.assert_finished();
+}
+
+#[test]
+fn async_fn_with_async_trait_and_fields_expressions() {
+ use async_trait::async_trait;
+
+ #[async_trait]
+ pub trait Test {
+ async fn call(&mut self, v: usize);
+ }
+
+ #[derive(Clone, Debug)]
+ struct TestImpl;
+
+ impl TestImpl {
+ fn foo(&self) -> usize {
+ 42
+ }
+ }
+
+ #[async_trait]
+ impl Test for TestImpl {
+ // check that self is correctly handled, even when using async_trait
+ #[instrument(fields(val=self.foo(), val2=Self::clone(self).foo(), test=%_v+5))]
+ async fn call(&mut self, _v: usize) {}
+ }
+
+ let span = span::mock().named("call");
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(
+ span.clone().with_field(
+ field::mock("_v")
+ .with_value(&5usize)
+ .and(field::mock("test").with_value(&tracing::field::debug(10)))
+ .and(field::mock("val").with_value(&42u64))
+ .and(field::mock("val2").with_value(&42u64)),
+ ),
+ )
+ .enter(span.clone())
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ block_on_future(async { TestImpl.call(5).await });
+ });
+
+ handle.assert_finished();
+}
+
+#[test]
+fn async_fn_with_async_trait_and_fields_expressions_with_generic_parameter() {
+ use async_trait::async_trait;
+
+ #[async_trait]
+ pub trait Test {
+ async fn call();
+ async fn call_with_self(&self);
+ async fn call_with_mut_self(&mut self);
+ }
+
+ #[derive(Clone, Debug)]
+ struct TestImpl;
+
+ // we also test sync functions that return futures, as they should be handled just like
+ // async-trait (>= 0.1.44) functions
+ impl TestImpl {
+ #[instrument(fields(Self=std::any::type_name::<Self>()))]
+ fn sync_fun(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
+ let val = self.clone();
+ Box::pin(async move {
+ let _ = val;
+ })
+ }
+ }
+
+ #[async_trait]
+ impl Test for TestImpl {
+ // instrumenting this is currently not possible, see https://github.com/tokio-rs/tracing/issues/864#issuecomment-667508801
+ //#[instrument(fields(Self=std::any::type_name::<Self>()))]
+ async fn call() {}
+
+ #[instrument(fields(Self=std::any::type_name::<Self>()))]
+ async fn call_with_self(&self) {
+ self.sync_fun().await;
+ }
+
+ #[instrument(fields(Self=std::any::type_name::<Self>()))]
+ async fn call_with_mut_self(&mut self) {}
+ }
+
+ //let span = span::mock().named("call");
+ let span2 = span::mock().named("call_with_self");
+ let span3 = span::mock().named("call_with_mut_self");
+ let span4 = span::mock().named("sync_fun");
+ let (subscriber, handle) = subscriber::mock()
+ /*.new_span(span.clone()
+ .with_field(
+ field::mock("Self").with_value(&"TestImpler")))
+ .enter(span.clone())
+ .exit(span.clone())
+ .drop_span(span)*/
+ .new_span(
+ span2
+ .clone()
+ .with_field(field::mock("Self").with_value(&std::any::type_name::<TestImpl>())),
+ )
+ .enter(span2.clone())
+ .new_span(
+ span4
+ .clone()
+ .with_field(field::mock("Self").with_value(&std::any::type_name::<TestImpl>())),
+ )
+ .enter(span4.clone())
+ .exit(span4)
+ .exit(span2.clone())
+ .drop_span(span2)
+ .new_span(
+ span3
+ .clone()
+ .with_field(field::mock("Self").with_value(&std::any::type_name::<TestImpl>())),
+ )
+ .enter(span3.clone())
+ .exit(span3.clone())
+ .drop_span(span3)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ block_on_future(async {
+ TestImpl::call().await;
+ TestImpl.call_with_self().await;
+ TestImpl.call_with_mut_self().await
+ });
+ });
+
+ handle.assert_finished();
+}
+
+#[test]
+fn out_of_scope_fields() {
+ // Reproduces tokio-rs/tracing#1296
+
+ struct Thing {
+ metrics: Arc<()>,
+ }
+
+ impl Thing {
+ #[instrument(skip(self, _req), fields(app_id))]
+ fn call(&mut self, _req: ()) -> Pin<Box<dyn Future<Output = Arc<()>> + Send + Sync>> {
+ // ...
+ let metrics = self.metrics.clone();
+ // ...
+ Box::pin(async move {
+ // ...
+ metrics // cannot find value `metrics` in this scope
+ })
+ }
+ }
+
+ let span = span::mock().named("call");
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(span.clone())
+ .enter(span.clone())
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ block_on_future(async {
+ let mut my_thing = Thing {
+ metrics: Arc::new(()),
+ };
+ my_thing.call(()).await;
+ });
+ });
+
+ handle.assert_finished();
+}
+
+#[test]
+fn manual_impl_future() {
+ #[allow(clippy::manual_async_fn)]
+ #[instrument]
+ fn manual_impl_future() -> impl Future<Output = ()> {
+ async {
+ tracing::trace!(poll = true);
+ }
+ }
+
+ let span = span::mock().named("manual_impl_future");
+ let poll_event = || event::mock().with_fields(field::mock("poll").with_value(&true));
+
+ let (subscriber, handle) = subscriber::mock()
+ // await manual_impl_future
+ .new_span(span.clone())
+ .enter(span.clone())
+ .event(poll_event())
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ block_on_future(async {
+ manual_impl_future().await;
+ });
+ });
+
+ handle.assert_finished();
+}
+
+#[test]
+fn manual_box_pin() {
+ #[instrument]
+ fn manual_box_pin() -> Pin<Box<dyn Future<Output = ()>>> {
+ Box::pin(async {
+ tracing::trace!(poll = true);
+ })
+ }
+
+ let span = span::mock().named("manual_box_pin");
+ let poll_event = || event::mock().with_fields(field::mock("poll").with_value(&true));
+
+ let (subscriber, handle) = subscriber::mock()
+ // await manual_box_pin
+ .new_span(span.clone())
+ .enter(span.clone())
+ .event(poll_event())
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ block_on_future(async {
+ manual_box_pin().await;
+ });
+ });
+
+ handle.assert_finished();
+}
diff --git a/tests/destructuring.rs b/tests/destructuring.rs
new file mode 100644
index 0000000..09cf1ad
--- /dev/null
+++ b/tests/destructuring.rs
@@ -0,0 +1,213 @@
+use tracing::subscriber::with_default;
+use tracing_attributes::instrument;
+use tracing_mock::*;
+
+#[test]
+fn destructure_tuples() {
+ #[instrument]
+ fn my_fn((arg1, arg2): (usize, usize)) {}
+
+ let span = span::mock().named("my_fn");
+
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(
+ span.clone().with_field(
+ field::mock("arg1")
+ .with_value(&format_args!("1"))
+ .and(field::mock("arg2").with_value(&format_args!("2")))
+ .only(),
+ ),
+ )
+ .enter(span.clone())
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ my_fn((1, 2));
+ });
+
+ handle.assert_finished();
+}
+
+#[test]
+fn destructure_nested_tuples() {
+ #[instrument]
+ fn my_fn(((arg1, arg2), (arg3, arg4)): ((usize, usize), (usize, usize))) {}
+
+ let span = span::mock().named("my_fn");
+
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(
+ span.clone().with_field(
+ field::mock("arg1")
+ .with_value(&format_args!("1"))
+ .and(field::mock("arg2").with_value(&format_args!("2")))
+ .and(field::mock("arg3").with_value(&format_args!("3")))
+ .and(field::mock("arg4").with_value(&format_args!("4")))
+ .only(),
+ ),
+ )
+ .enter(span.clone())
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ my_fn(((1, 2), (3, 4)));
+ });
+
+ handle.assert_finished();
+}
+
+#[test]
+fn destructure_refs() {
+ #[instrument]
+ fn my_fn(&arg1: &usize) {}
+
+ let span = span::mock().named("my_fn");
+
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(
+ span.clone()
+ .with_field(field::mock("arg1").with_value(&1usize).only()),
+ )
+ .enter(span.clone())
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ my_fn(&1);
+ });
+
+ handle.assert_finished();
+}
+
+#[test]
+fn destructure_tuple_structs() {
+ struct Foo(usize, usize);
+
+ #[instrument]
+ fn my_fn(Foo(arg1, arg2): Foo) {}
+
+ let span = span::mock().named("my_fn");
+
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(
+ span.clone().with_field(
+ field::mock("arg1")
+ .with_value(&format_args!("1"))
+ .and(field::mock("arg2").with_value(&format_args!("2")))
+ .only(),
+ ),
+ )
+ .enter(span.clone())
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ my_fn(Foo(1, 2));
+ });
+
+ handle.assert_finished();
+}
+
+#[test]
+fn destructure_structs() {
+ struct Foo {
+ bar: usize,
+ baz: usize,
+ }
+
+ #[instrument]
+ fn my_fn(
+ Foo {
+ bar: arg1,
+ baz: arg2,
+ }: Foo,
+ ) {
+ let _ = (arg1, arg2);
+ }
+
+ let span = span::mock().named("my_fn");
+
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(
+ span.clone().with_field(
+ field::mock("arg1")
+ .with_value(&format_args!("1"))
+ .and(field::mock("arg2").with_value(&format_args!("2")))
+ .only(),
+ ),
+ )
+ .enter(span.clone())
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ my_fn(Foo { bar: 1, baz: 2 });
+ });
+
+ handle.assert_finished();
+}
+
+#[test]
+fn destructure_everything() {
+ struct Foo {
+ bar: Bar,
+ baz: (usize, usize),
+ qux: NoDebug,
+ }
+ struct Bar((usize, usize));
+ struct NoDebug;
+
+ #[instrument]
+ fn my_fn(
+ &Foo {
+ bar: Bar((arg1, arg2)),
+ baz: (arg3, arg4),
+ ..
+ }: &Foo,
+ ) {
+ let _ = (arg1, arg2, arg3, arg4);
+ }
+
+ let span = span::mock().named("my_fn");
+
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(
+ span.clone().with_field(
+ field::mock("arg1")
+ .with_value(&format_args!("1"))
+ .and(field::mock("arg2").with_value(&format_args!("2")))
+ .and(field::mock("arg3").with_value(&format_args!("3")))
+ .and(field::mock("arg4").with_value(&format_args!("4")))
+ .only(),
+ ),
+ )
+ .enter(span.clone())
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ let foo = Foo {
+ bar: Bar((1, 2)),
+ baz: (3, 4),
+ qux: NoDebug,
+ };
+ let _ = foo.qux; // to eliminate unused field warning
+ my_fn(&foo);
+ });
+
+ handle.assert_finished();
+}
diff --git a/tests/err.rs b/tests/err.rs
new file mode 100644
index 0000000..9e6d6b7
--- /dev/null
+++ b/tests/err.rs
@@ -0,0 +1,233 @@
+use tracing::subscriber::with_default;
+use tracing::Level;
+use tracing_attributes::instrument;
+use tracing_mock::*;
+use tracing_subscriber::filter::EnvFilter;
+use tracing_subscriber::layer::SubscriberExt;
+
+use std::convert::TryFrom;
+use std::num::TryFromIntError;
+
+#[instrument(err)]
+fn err() -> Result<u8, TryFromIntError> {
+ u8::try_from(1234)
+}
+
+#[instrument(err)]
+fn err_suspicious_else() -> Result<u8, TryFromIntError> {
+ {}
+ u8::try_from(1234)
+}
+
+#[test]
+fn test() {
+ let span = span::mock().named("err");
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(span.clone())
+ .enter(span.clone())
+ .event(event::mock().at_level(Level::ERROR))
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+ with_default(subscriber, || err().ok());
+ handle.assert_finished();
+}
+
+#[instrument(err)]
+async fn err_async(polls: usize) -> Result<u8, TryFromIntError> {
+ let future = PollN::new_ok(polls);
+ tracing::trace!(awaiting = true);
+ future.await.ok();
+ u8::try_from(1234)
+}
+
+#[test]
+fn test_async() {
+ let span = span::mock().named("err_async");
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(span.clone())
+ .enter(span.clone())
+ .event(
+ event::mock()
+ .with_fields(field::mock("awaiting").with_value(&true))
+ .at_level(Level::TRACE),
+ )
+ .exit(span.clone())
+ .enter(span.clone())
+ .event(event::mock().at_level(Level::ERROR))
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+ with_default(subscriber, || {
+ block_on_future(async { err_async(2).await }).ok();
+ });
+ handle.assert_finished();
+}
+
+#[instrument(err)]
+fn err_mut(out: &mut u8) -> Result<(), TryFromIntError> {
+ *out = u8::try_from(1234)?;
+ Ok(())
+}
+
+#[test]
+fn test_mut() {
+ let span = span::mock().named("err_mut");
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(span.clone())
+ .enter(span.clone())
+ .event(event::mock().at_level(Level::ERROR))
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+ with_default(subscriber, || err_mut(&mut 0).ok());
+ handle.assert_finished();
+}
+
+#[instrument(err)]
+async fn err_mut_async(polls: usize, out: &mut u8) -> Result<(), TryFromIntError> {
+ let future = PollN::new_ok(polls);
+ tracing::trace!(awaiting = true);
+ future.await.ok();
+ *out = u8::try_from(1234)?;
+ Ok(())
+}
+
+#[test]
+fn test_mut_async() {
+ let span = span::mock().named("err_mut_async");
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(span.clone())
+ .enter(span.clone())
+ .event(
+ event::mock()
+ .with_fields(field::mock("awaiting").with_value(&true))
+ .at_level(Level::TRACE),
+ )
+ .exit(span.clone())
+ .enter(span.clone())
+ .event(event::mock().at_level(Level::ERROR))
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+ with_default(subscriber, || {
+ block_on_future(async { err_mut_async(2, &mut 0).await }).ok();
+ });
+ handle.assert_finished();
+}
+
+#[test]
+fn impl_trait_return_type() {
+ // Reproduces https://github.com/tokio-rs/tracing/issues/1227
+
+ #[instrument(err)]
+ fn returns_impl_trait(x: usize) -> Result<impl Iterator<Item = usize>, String> {
+ Ok(0..x)
+ }
+
+ let span = span::mock().named("returns_impl_trait");
+
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(
+ span.clone()
+ .with_field(field::mock("x").with_value(&10usize).only()),
+ )
+ .enter(span.clone())
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ for _ in returns_impl_trait(10).unwrap() {
+ // nop
+ }
+ });
+
+ handle.assert_finished();
+}
+
+#[instrument(err(Debug))]
+fn err_dbg() -> Result<u8, TryFromIntError> {
+ u8::try_from(1234)
+}
+
+#[test]
+fn test_err_dbg() {
+ let span = span::mock().named("err_dbg");
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(span.clone())
+ .enter(span.clone())
+ .event(
+ event::mock().at_level(Level::ERROR).with_fields(
+ field::mock("error")
+ // use the actual error value that will be emitted, so
+ // that this test doesn't break if the standard library
+ // changes the `fmt::Debug` output from the error type
+ // in the future.
+ .with_value(&tracing::field::debug(u8::try_from(1234).unwrap_err())),
+ ),
+ )
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+ with_default(subscriber, || err_dbg().ok());
+ handle.assert_finished();
+}
+
+#[test]
+fn test_err_display_default() {
+ let span = span::mock().named("err");
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(span.clone())
+ .enter(span.clone())
+ .event(
+ event::mock().at_level(Level::ERROR).with_fields(
+ field::mock("error")
+ // by default, errors will be emitted with their display values
+ .with_value(&tracing::field::display(u8::try_from(1234).unwrap_err())),
+ ),
+ )
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+ with_default(subscriber, || err().ok());
+ handle.assert_finished();
+}
+
+#[test]
+fn test_err_custom_target() {
+ let filter: EnvFilter = "my_target=error".parse().expect("filter should parse");
+ let span = span::mock().named("error_span").with_target("my_target");
+
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(span.clone())
+ .enter(span.clone())
+ .event(
+ event::mock()
+ .at_level(Level::ERROR)
+ .with_target("my_target"),
+ )
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+
+ let subscriber = subscriber.with(filter);
+
+ with_default(subscriber, || {
+ let error_span = tracing::error_span!(target: "my_target", "error_span");
+
+ {
+ let _enter = error_span.enter();
+ tracing::error!(target: "my_target", "This should display")
+ }
+ });
+ handle.assert_finished();
+}
diff --git a/tests/fields.rs b/tests/fields.rs
new file mode 100644
index 0000000..c178fbb
--- /dev/null
+++ b/tests/fields.rs
@@ -0,0 +1,160 @@
+use tracing::subscriber::with_default;
+use tracing_attributes::instrument;
+use tracing_mock::field::mock;
+use tracing_mock::span::NewSpan;
+use tracing_mock::*;
+
+#[instrument(fields(foo = "bar", dsa = true, num = 1))]
+fn fn_no_param() {}
+
+#[instrument(fields(foo = "bar"))]
+fn fn_param(param: u32) {}
+
+#[instrument(fields(foo = "bar", empty))]
+fn fn_empty_field() {}
+
+#[instrument(fields(len = s.len()))]
+fn fn_expr_field(s: &str) {}
+
+#[instrument(fields(s.len = s.len(), s.is_empty = s.is_empty()))]
+fn fn_two_expr_fields(s: &str) {
+ let _ = s;
+}
+
+#[instrument(fields(%s, s.len = s.len()))]
+fn fn_clashy_expr_field(s: &str) {
+ let _ = s;
+}
+
+#[instrument(fields(s = "s"))]
+fn fn_clashy_expr_field2(s: &str) {
+ let _ = s;
+}
+
+#[instrument(fields(s = &s))]
+fn fn_string(s: String) {
+ let _ = s;
+}
+
+#[derive(Debug)]
+struct HasField {
+ my_field: &'static str,
+}
+
+impl HasField {
+ #[instrument(fields(my_field = self.my_field), skip(self))]
+ fn self_expr_field(&self) {}
+}
+
+#[test]
+fn fields() {
+ let span = span::mock().with_field(
+ mock("foo")
+ .with_value(&"bar")
+ .and(mock("dsa").with_value(&true))
+ .and(mock("num").with_value(&1))
+ .only(),
+ );
+ run_test(span, || {
+ fn_no_param();
+ });
+}
+
+#[test]
+fn expr_field() {
+ let span = span::mock().with_field(
+ mock("s")
+ .with_value(&"hello world")
+ .and(mock("len").with_value(&"hello world".len()))
+ .only(),
+ );
+ run_test(span, || {
+ fn_expr_field("hello world");
+ });
+}
+
+#[test]
+fn two_expr_fields() {
+ let span = span::mock().with_field(
+ mock("s")
+ .with_value(&"hello world")
+ .and(mock("s.len").with_value(&"hello world".len()))
+ .and(mock("s.is_empty").with_value(&false))
+ .only(),
+ );
+ run_test(span, || {
+ fn_two_expr_fields("hello world");
+ });
+}
+
+#[test]
+fn clashy_expr_field() {
+ let span = span::mock().with_field(
+ // Overriding the `s` field should record `s` as a `Display` value,
+ // rather than as a `Debug` value.
+ mock("s")
+ .with_value(&tracing::field::display("hello world"))
+ .and(mock("s.len").with_value(&"hello world".len()))
+ .only(),
+ );
+ run_test(span, || {
+ fn_clashy_expr_field("hello world");
+ });
+
+ let span = span::mock().with_field(mock("s").with_value(&"s").only());
+ run_test(span, || {
+ fn_clashy_expr_field2("hello world");
+ });
+}
+
+#[test]
+fn self_expr_field() {
+ let span = span::mock().with_field(mock("my_field").with_value(&"hello world").only());
+ run_test(span, || {
+ let has_field = HasField {
+ my_field: "hello world",
+ };
+ has_field.self_expr_field();
+ });
+}
+
+#[test]
+fn parameters_with_fields() {
+ let span = span::mock().with_field(
+ mock("foo")
+ .with_value(&"bar")
+ .and(mock("param").with_value(&1u32))
+ .only(),
+ );
+ run_test(span, || {
+ fn_param(1);
+ });
+}
+
+#[test]
+fn empty_field() {
+ let span = span::mock().with_field(mock("foo").with_value(&"bar").only());
+ run_test(span, || {
+ fn_empty_field();
+ });
+}
+
+#[test]
+fn string_field() {
+ let span = span::mock().with_field(mock("s").with_value(&"hello world").only());
+ run_test(span, || {
+ fn_string(String::from("hello world"));
+ });
+}
+
+fn run_test<F: FnOnce() -> T, T>(span: NewSpan, fun: F) {
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(span)
+ .enter(span::mock())
+ .exit(span::mock())
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, fun);
+ handle.assert_finished();
+}
diff --git a/tests/follows_from.rs b/tests/follows_from.rs
new file mode 100644
index 0000000..da0eec6
--- /dev/null
+++ b/tests/follows_from.rs
@@ -0,0 +1,99 @@
+use tracing::{subscriber::with_default, Id, Level, Span};
+use tracing_attributes::instrument;
+use tracing_mock::*;
+
+#[instrument(follows_from = causes, skip(causes))]
+fn with_follows_from_sync(causes: impl IntoIterator<Item = impl Into<Option<Id>>>) {}
+
+#[instrument(follows_from = causes, skip(causes))]
+async fn with_follows_from_async(causes: impl IntoIterator<Item = impl Into<Option<Id>>>) {}
+
+#[instrument(follows_from = [&Span::current()])]
+fn follows_from_current() {}
+
+#[test]
+fn follows_from_sync_test() {
+ let cause_a = span::mock().named("cause_a");
+ let cause_b = span::mock().named("cause_b");
+ let cause_c = span::mock().named("cause_c");
+ let consequence = span::mock().named("with_follows_from_sync");
+
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(cause_a.clone())
+ .new_span(cause_b.clone())
+ .new_span(cause_c.clone())
+ .new_span(consequence.clone())
+ .follows_from(consequence.clone(), cause_a)
+ .follows_from(consequence.clone(), cause_b)
+ .follows_from(consequence.clone(), cause_c)
+ .enter(consequence.clone())
+ .exit(consequence)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ let cause_a = tracing::span!(Level::TRACE, "cause_a");
+ let cause_b = tracing::span!(Level::TRACE, "cause_b");
+ let cause_c = tracing::span!(Level::TRACE, "cause_c");
+
+ with_follows_from_sync(&[cause_a, cause_b, cause_c])
+ });
+
+ handle.assert_finished();
+}
+
+#[test]
+fn follows_from_async_test() {
+ let cause_a = span::mock().named("cause_a");
+ let cause_b = span::mock().named("cause_b");
+ let cause_c = span::mock().named("cause_c");
+ let consequence = span::mock().named("with_follows_from_async");
+
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(cause_a.clone())
+ .new_span(cause_b.clone())
+ .new_span(cause_c.clone())
+ .new_span(consequence.clone())
+ .follows_from(consequence.clone(), cause_a)
+ .follows_from(consequence.clone(), cause_b)
+ .follows_from(consequence.clone(), cause_c)
+ .enter(consequence.clone())
+ .exit(consequence)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ block_on_future(async {
+ let cause_a = tracing::span!(Level::TRACE, "cause_a");
+ let cause_b = tracing::span!(Level::TRACE, "cause_b");
+ let cause_c = tracing::span!(Level::TRACE, "cause_c");
+
+ with_follows_from_async(&[cause_a, cause_b, cause_c]).await
+ })
+ });
+
+ handle.assert_finished();
+}
+
+#[test]
+fn follows_from_current_test() {
+ let cause = span::mock().named("cause");
+ let consequence = span::mock().named("follows_from_current");
+
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(cause.clone())
+ .enter(cause.clone())
+ .new_span(consequence.clone())
+ .follows_from(consequence.clone(), cause.clone())
+ .enter(consequence.clone())
+ .exit(consequence)
+ .exit(cause)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ tracing::span!(Level::TRACE, "cause").in_scope(follows_from_current)
+ });
+
+ handle.assert_finished();
+}
diff --git a/tests/instrument.rs b/tests/instrument.rs
new file mode 100644
index 0000000..7686927
--- /dev/null
+++ b/tests/instrument.rs
@@ -0,0 +1,252 @@
+use tracing::subscriber::with_default;
+use tracing::Level;
+use tracing_attributes::instrument;
+use tracing_mock::*;
+
+// Reproduces a compile error when an instrumented function body contains inner
+// attributes (https://github.com/tokio-rs/tracing/issues/2294).
+#[deny(unused_variables)]
+#[instrument]
+fn repro_2294() {
+ #![allow(unused_variables)]
+ let i = 42;
+}
+
+#[test]
+fn override_everything() {
+ #[instrument(target = "my_target", level = "debug")]
+ fn my_fn() {}
+
+ #[instrument(level = "debug", target = "my_target")]
+ fn my_other_fn() {}
+
+ let span = span::mock()
+ .named("my_fn")
+ .at_level(Level::DEBUG)
+ .with_target("my_target");
+ let span2 = span::mock()
+ .named("my_other_fn")
+ .at_level(Level::DEBUG)
+ .with_target("my_target");
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(span.clone())
+ .enter(span.clone())
+ .exit(span.clone())
+ .drop_span(span)
+ .new_span(span2.clone())
+ .enter(span2.clone())
+ .exit(span2.clone())
+ .drop_span(span2)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ my_fn();
+ my_other_fn();
+ });
+
+ handle.assert_finished();
+}
+
+#[test]
+fn fields() {
+ #[instrument(target = "my_target", level = "debug")]
+ fn my_fn(arg1: usize, arg2: bool) {}
+
+ let span = span::mock()
+ .named("my_fn")
+ .at_level(Level::DEBUG)
+ .with_target("my_target");
+
+ let span2 = span::mock()
+ .named("my_fn")
+ .at_level(Level::DEBUG)
+ .with_target("my_target");
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(
+ span.clone().with_field(
+ field::mock("arg1")
+ .with_value(&2usize)
+ .and(field::mock("arg2").with_value(&false))
+ .only(),
+ ),
+ )
+ .enter(span.clone())
+ .exit(span.clone())
+ .drop_span(span)
+ .new_span(
+ span2.clone().with_field(
+ field::mock("arg1")
+ .with_value(&3usize)
+ .and(field::mock("arg2").with_value(&true))
+ .only(),
+ ),
+ )
+ .enter(span2.clone())
+ .exit(span2.clone())
+ .drop_span(span2)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ my_fn(2, false);
+ my_fn(3, true);
+ });
+
+ handle.assert_finished();
+}
+
+#[test]
+fn skip() {
+ struct UnDebug(pub u32);
+
+ #[instrument(target = "my_target", level = "debug", skip(_arg2, _arg3))]
+ fn my_fn(arg1: usize, _arg2: UnDebug, _arg3: UnDebug) {}
+
+ #[instrument(target = "my_target", level = "debug", skip_all)]
+ fn my_fn2(_arg1: usize, _arg2: UnDebug, _arg3: UnDebug) {}
+
+ let span = span::mock()
+ .named("my_fn")
+ .at_level(Level::DEBUG)
+ .with_target("my_target");
+
+ let span2 = span::mock()
+ .named("my_fn")
+ .at_level(Level::DEBUG)
+ .with_target("my_target");
+
+ let span3 = span::mock()
+ .named("my_fn2")
+ .at_level(Level::DEBUG)
+ .with_target("my_target");
+
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(
+ span.clone()
+ .with_field(field::mock("arg1").with_value(&2usize).only()),
+ )
+ .enter(span.clone())
+ .exit(span.clone())
+ .drop_span(span)
+ .new_span(
+ span2
+ .clone()
+ .with_field(field::mock("arg1").with_value(&3usize).only()),
+ )
+ .enter(span2.clone())
+ .exit(span2.clone())
+ .drop_span(span2)
+ .new_span(span3.clone())
+ .enter(span3.clone())
+ .exit(span3.clone())
+ .drop_span(span3)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ my_fn(2, UnDebug(0), UnDebug(1));
+ my_fn(3, UnDebug(0), UnDebug(1));
+ my_fn2(2, UnDebug(0), UnDebug(1));
+ });
+
+ handle.assert_finished();
+}
+
+#[test]
+fn generics() {
+ #[derive(Debug)]
+ struct Foo;
+
+ #[instrument]
+ fn my_fn<S, T: std::fmt::Debug>(arg1: S, arg2: T)
+ where
+ S: std::fmt::Debug,
+ {
+ }
+
+ let span = span::mock().named("my_fn");
+
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(
+ span.clone().with_field(
+ field::mock("arg1")
+ .with_value(&format_args!("Foo"))
+ .and(field::mock("arg2").with_value(&format_args!("false"))),
+ ),
+ )
+ .enter(span.clone())
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ my_fn(Foo, false);
+ });
+
+ handle.assert_finished();
+}
+
+#[test]
+fn methods() {
+ #[derive(Debug)]
+ struct Foo;
+
+ impl Foo {
+ #[instrument]
+ fn my_fn(&self, arg1: usize) {}
+ }
+
+ let span = span::mock().named("my_fn");
+
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(
+ span.clone().with_field(
+ field::mock("self")
+ .with_value(&format_args!("Foo"))
+ .and(field::mock("arg1").with_value(&42usize)),
+ ),
+ )
+ .enter(span.clone())
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ let foo = Foo;
+ foo.my_fn(42);
+ });
+
+ handle.assert_finished();
+}
+
+#[test]
+fn impl_trait_return_type() {
+ #[instrument]
+ fn returns_impl_trait(x: usize) -> impl Iterator<Item = usize> {
+ 0..x
+ }
+
+ let span = span::mock().named("returns_impl_trait");
+
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(
+ span.clone()
+ .with_field(field::mock("x").with_value(&10usize).only()),
+ )
+ .enter(span.clone())
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ for _ in returns_impl_trait(10) {
+ // nop
+ }
+ });
+
+ handle.assert_finished();
+}
diff --git a/tests/levels.rs b/tests/levels.rs
new file mode 100644
index 0000000..b074ea4
--- /dev/null
+++ b/tests/levels.rs
@@ -0,0 +1,96 @@
+use tracing::subscriber::with_default;
+use tracing::Level;
+use tracing_attributes::instrument;
+use tracing_mock::*;
+
+#[test]
+fn named_levels() {
+ #[instrument(level = "trace")]
+ fn trace() {}
+
+ #[instrument(level = "Debug")]
+ fn debug() {}
+
+ #[instrument(level = "INFO")]
+ fn info() {}
+
+ #[instrument(level = "WARn")]
+ fn warn() {}
+
+ #[instrument(level = "eRrOr")]
+ fn error() {}
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(span::mock().named("trace").at_level(Level::TRACE))
+ .enter(span::mock().named("trace").at_level(Level::TRACE))
+ .exit(span::mock().named("trace").at_level(Level::TRACE))
+ .new_span(span::mock().named("debug").at_level(Level::DEBUG))
+ .enter(span::mock().named("debug").at_level(Level::DEBUG))
+ .exit(span::mock().named("debug").at_level(Level::DEBUG))
+ .new_span(span::mock().named("info").at_level(Level::INFO))
+ .enter(span::mock().named("info").at_level(Level::INFO))
+ .exit(span::mock().named("info").at_level(Level::INFO))
+ .new_span(span::mock().named("warn").at_level(Level::WARN))
+ .enter(span::mock().named("warn").at_level(Level::WARN))
+ .exit(span::mock().named("warn").at_level(Level::WARN))
+ .new_span(span::mock().named("error").at_level(Level::ERROR))
+ .enter(span::mock().named("error").at_level(Level::ERROR))
+ .exit(span::mock().named("error").at_level(Level::ERROR))
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ trace();
+ debug();
+ info();
+ warn();
+ error();
+ });
+
+ handle.assert_finished();
+}
+
+#[test]
+fn numeric_levels() {
+ #[instrument(level = 1)]
+ fn trace() {}
+
+ #[instrument(level = 2)]
+ fn debug() {}
+
+ #[instrument(level = 3)]
+ fn info() {}
+
+ #[instrument(level = 4)]
+ fn warn() {}
+
+ #[instrument(level = 5)]
+ fn error() {}
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(span::mock().named("trace").at_level(Level::TRACE))
+ .enter(span::mock().named("trace").at_level(Level::TRACE))
+ .exit(span::mock().named("trace").at_level(Level::TRACE))
+ .new_span(span::mock().named("debug").at_level(Level::DEBUG))
+ .enter(span::mock().named("debug").at_level(Level::DEBUG))
+ .exit(span::mock().named("debug").at_level(Level::DEBUG))
+ .new_span(span::mock().named("info").at_level(Level::INFO))
+ .enter(span::mock().named("info").at_level(Level::INFO))
+ .exit(span::mock().named("info").at_level(Level::INFO))
+ .new_span(span::mock().named("warn").at_level(Level::WARN))
+ .enter(span::mock().named("warn").at_level(Level::WARN))
+ .exit(span::mock().named("warn").at_level(Level::WARN))
+ .new_span(span::mock().named("error").at_level(Level::ERROR))
+ .enter(span::mock().named("error").at_level(Level::ERROR))
+ .exit(span::mock().named("error").at_level(Level::ERROR))
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ trace();
+ debug();
+ info();
+ warn();
+ error();
+ });
+
+ handle.assert_finished();
+}
diff --git a/tests/names.rs b/tests/names.rs
new file mode 100644
index 0000000..d97dece
--- /dev/null
+++ b/tests/names.rs
@@ -0,0 +1,63 @@
+use tracing::subscriber::with_default;
+use tracing_attributes::instrument;
+use tracing_mock::*;
+
+#[instrument]
+fn default_name() {}
+
+#[instrument(name = "my_name")]
+fn custom_name() {}
+
+// XXX: it's weird that we support both of these forms, but apparently we
+// managed to release a version that accepts both syntax, so now we have to
+// support it! yay!
+#[instrument("my_other_name")]
+fn custom_name_no_equals() {}
+
+#[test]
+fn default_name_test() {
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(span::mock().named("default_name"))
+ .enter(span::mock().named("default_name"))
+ .exit(span::mock().named("default_name"))
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ default_name();
+ });
+
+ handle.assert_finished();
+}
+
+#[test]
+fn custom_name_test() {
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(span::mock().named("my_name"))
+ .enter(span::mock().named("my_name"))
+ .exit(span::mock().named("my_name"))
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ custom_name();
+ });
+
+ handle.assert_finished();
+}
+
+#[test]
+fn custom_name_no_equals_test() {
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(span::mock().named("my_other_name"))
+ .enter(span::mock().named("my_other_name"))
+ .exit(span::mock().named("my_other_name"))
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ custom_name_no_equals();
+ });
+
+ handle.assert_finished();
+}
diff --git a/tests/parents.rs b/tests/parents.rs
new file mode 100644
index 0000000..7069b98
--- /dev/null
+++ b/tests/parents.rs
@@ -0,0 +1,102 @@
+use tracing::{subscriber::with_default, Id, Level};
+use tracing_attributes::instrument;
+use tracing_mock::*;
+
+#[instrument]
+fn with_default_parent() {}
+
+#[instrument(parent = parent_span, skip(parent_span))]
+fn with_explicit_parent<P>(parent_span: P)
+where
+ P: Into<Option<Id>>,
+{
+}
+
+#[test]
+fn default_parent_test() {
+ let contextual_parent = span::mock().named("contextual_parent");
+ let child = span::mock().named("with_default_parent");
+
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(
+ contextual_parent
+ .clone()
+ .with_contextual_parent(None)
+ .with_explicit_parent(None),
+ )
+ .new_span(
+ child
+ .clone()
+ .with_contextual_parent(Some("contextual_parent"))
+ .with_explicit_parent(None),
+ )
+ .enter(child.clone())
+ .exit(child.clone())
+ .enter(contextual_parent.clone())
+ .new_span(
+ child
+ .clone()
+ .with_contextual_parent(Some("contextual_parent"))
+ .with_explicit_parent(None),
+ )
+ .enter(child.clone())
+ .exit(child)
+ .exit(contextual_parent)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ let contextual_parent = tracing::span!(Level::TRACE, "contextual_parent");
+
+ with_default_parent();
+
+ contextual_parent.in_scope(|| {
+ with_default_parent();
+ });
+ });
+
+ handle.assert_finished();
+}
+
+#[test]
+fn explicit_parent_test() {
+ let contextual_parent = span::mock().named("contextual_parent");
+ let explicit_parent = span::mock().named("explicit_parent");
+ let child = span::mock().named("with_explicit_parent");
+
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(
+ contextual_parent
+ .clone()
+ .with_contextual_parent(None)
+ .with_explicit_parent(None),
+ )
+ .new_span(
+ explicit_parent
+ .with_contextual_parent(None)
+ .with_explicit_parent(None),
+ )
+ .enter(contextual_parent.clone())
+ .new_span(
+ child
+ .clone()
+ .with_contextual_parent(Some("contextual_parent"))
+ .with_explicit_parent(Some("explicit_parent")),
+ )
+ .enter(child.clone())
+ .exit(child)
+ .exit(contextual_parent)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ let contextual_parent = tracing::span!(Level::INFO, "contextual_parent");
+ let explicit_parent = tracing::span!(Level::INFO, "explicit_parent");
+
+ contextual_parent.in_scope(|| {
+ with_explicit_parent(&explicit_parent);
+ });
+ });
+
+ handle.assert_finished();
+}
diff --git a/tests/ret.rs b/tests/ret.rs
new file mode 100644
index 0000000..cfd2de1
--- /dev/null
+++ b/tests/ret.rs
@@ -0,0 +1,255 @@
+use std::convert::TryFrom;
+use std::num::TryFromIntError;
+use tracing_mock::*;
+
+use tracing::{subscriber::with_default, Level};
+use tracing_attributes::instrument;
+use tracing_subscriber::layer::SubscriberExt;
+use tracing_subscriber::EnvFilter;
+
+#[instrument(ret)]
+fn ret() -> i32 {
+ 42
+}
+
+#[instrument(target = "my_target", ret)]
+fn ret_with_target() -> i32 {
+ 42
+}
+
+#[test]
+fn test() {
+ let span = span::mock().named("ret");
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(span.clone())
+ .enter(span.clone())
+ .event(
+ event::mock()
+ .with_fields(field::mock("return").with_value(&tracing::field::debug(42)))
+ .at_level(Level::INFO),
+ )
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, ret);
+ handle.assert_finished();
+}
+
+#[test]
+fn test_custom_target() {
+ let filter: EnvFilter = "my_target=info".parse().expect("filter should parse");
+ let span = span::mock()
+ .named("ret_with_target")
+ .with_target("my_target");
+
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(span.clone())
+ .enter(span.clone())
+ .event(
+ event::mock()
+ .with_fields(field::mock("return").with_value(&tracing::field::debug(42)))
+ .at_level(Level::INFO)
+ .with_target("my_target"),
+ )
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+
+ let subscriber = subscriber.with(filter);
+
+ with_default(subscriber, ret_with_target);
+ handle.assert_finished();
+}
+
+#[instrument(level = "warn", ret)]
+fn ret_warn() -> i32 {
+ 42
+}
+
+#[test]
+fn test_warn() {
+ let span = span::mock().named("ret_warn");
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(span.clone())
+ .enter(span.clone())
+ .event(
+ event::mock()
+ .with_fields(field::mock("return").with_value(&tracing::field::debug(42)))
+ .at_level(Level::WARN),
+ )
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, ret_warn);
+ handle.assert_finished();
+}
+
+#[instrument(ret)]
+fn ret_mut(a: &mut i32) -> i32 {
+ *a *= 2;
+ tracing::info!(?a);
+ *a
+}
+
+#[test]
+fn test_mut() {
+ let span = span::mock().named("ret_mut");
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(span.clone())
+ .enter(span.clone())
+ .event(
+ event::mock()
+ .with_fields(field::mock("a").with_value(&tracing::field::display(2)))
+ .at_level(Level::INFO),
+ )
+ .event(
+ event::mock()
+ .with_fields(field::mock("return").with_value(&tracing::field::debug(2)))
+ .at_level(Level::INFO),
+ )
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || ret_mut(&mut 1));
+ handle.assert_finished();
+}
+
+#[instrument(ret)]
+async fn ret_async() -> i32 {
+ 42
+}
+
+#[test]
+fn test_async() {
+ let span = span::mock().named("ret_async");
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(span.clone())
+ .enter(span.clone())
+ .event(
+ event::mock()
+ .with_fields(field::mock("return").with_value(&tracing::field::debug(42)))
+ .at_level(Level::INFO),
+ )
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || block_on_future(async { ret_async().await }));
+ handle.assert_finished();
+}
+
+#[instrument(ret)]
+fn ret_impl_type() -> impl Copy {
+ 42
+}
+
+#[test]
+fn test_impl_type() {
+ let span = span::mock().named("ret_impl_type");
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(span.clone())
+ .enter(span.clone())
+ .event(
+ event::mock()
+ .with_fields(field::mock("return").with_value(&tracing::field::debug(42)))
+ .at_level(Level::INFO),
+ )
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, ret_impl_type);
+ handle.assert_finished();
+}
+
+#[instrument(ret(Display))]
+fn ret_display() -> i32 {
+ 42
+}
+
+#[test]
+fn test_dbg() {
+ let span = span::mock().named("ret_display");
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(span.clone())
+ .enter(span.clone())
+ .event(
+ event::mock()
+ .with_fields(field::mock("return").with_value(&tracing::field::display(42)))
+ .at_level(Level::INFO),
+ )
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, ret_display);
+ handle.assert_finished();
+}
+
+#[instrument(err, ret)]
+fn ret_and_err() -> Result<u8, TryFromIntError> {
+ u8::try_from(1234)
+}
+
+#[test]
+fn test_ret_and_err() {
+ let span = span::mock().named("ret_and_err");
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(span.clone())
+ .enter(span.clone())
+ .event(
+ event::mock()
+ .with_fields(
+ field::mock("error")
+ .with_value(&tracing::field::display(u8::try_from(1234).unwrap_err()))
+ .only(),
+ )
+ .at_level(Level::ERROR),
+ )
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || ret_and_err().ok());
+ handle.assert_finished();
+}
+
+#[instrument(err, ret)]
+fn ret_and_ok() -> Result<u8, TryFromIntError> {
+ u8::try_from(123)
+}
+
+#[test]
+fn test_ret_and_ok() {
+ let span = span::mock().named("ret_and_ok");
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(span.clone())
+ .enter(span.clone())
+ .event(
+ event::mock()
+ .with_fields(
+ field::mock("return")
+ .with_value(&tracing::field::debug(u8::try_from(123).unwrap()))
+ .only(),
+ )
+ .at_level(Level::INFO),
+ )
+ .exit(span.clone())
+ .drop_span(span)
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || ret_and_ok().ok());
+ handle.assert_finished();
+}
diff --git a/tests/targets.rs b/tests/targets.rs
new file mode 100644
index 0000000..363f628
--- /dev/null
+++ b/tests/targets.rs
@@ -0,0 +1,97 @@
+use tracing::subscriber::with_default;
+use tracing_attributes::instrument;
+use tracing_mock::*;
+
+#[instrument]
+fn default_target() {}
+
+#[instrument(target = "my_target")]
+fn custom_target() {}
+
+mod my_mod {
+ use tracing_attributes::instrument;
+
+ pub const MODULE_PATH: &str = module_path!();
+
+ #[instrument]
+ pub fn default_target() {}
+
+ #[instrument(target = "my_other_target")]
+ pub fn custom_target() {}
+}
+
+#[test]
+fn default_targets() {
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(
+ span::mock()
+ .named("default_target")
+ .with_target(module_path!()),
+ )
+ .enter(
+ span::mock()
+ .named("default_target")
+ .with_target(module_path!()),
+ )
+ .exit(
+ span::mock()
+ .named("default_target")
+ .with_target(module_path!()),
+ )
+ .new_span(
+ span::mock()
+ .named("default_target")
+ .with_target(my_mod::MODULE_PATH),
+ )
+ .enter(
+ span::mock()
+ .named("default_target")
+ .with_target(my_mod::MODULE_PATH),
+ )
+ .exit(
+ span::mock()
+ .named("default_target")
+ .with_target(my_mod::MODULE_PATH),
+ )
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ default_target();
+ my_mod::default_target();
+ });
+
+ handle.assert_finished();
+}
+
+#[test]
+fn custom_targets() {
+ let (subscriber, handle) = subscriber::mock()
+ .new_span(span::mock().named("custom_target").with_target("my_target"))
+ .enter(span::mock().named("custom_target").with_target("my_target"))
+ .exit(span::mock().named("custom_target").with_target("my_target"))
+ .new_span(
+ span::mock()
+ .named("custom_target")
+ .with_target("my_other_target"),
+ )
+ .enter(
+ span::mock()
+ .named("custom_target")
+ .with_target("my_other_target"),
+ )
+ .exit(
+ span::mock()
+ .named("custom_target")
+ .with_target("my_other_target"),
+ )
+ .done()
+ .run_with_handle();
+
+ with_default(subscriber, || {
+ custom_target();
+ my_mod::custom_target();
+ });
+
+ handle.assert_finished();
+}
diff --git a/tests/ui.rs b/tests/ui.rs
new file mode 100644
index 0000000..f11cc01
--- /dev/null
+++ b/tests/ui.rs
@@ -0,0 +1,7 @@
+// Only test on nightly, since UI tests are bound to change over time
+#[rustversion::stable]
+#[test]
+fn async_instrument() {
+ let t = trybuild::TestCases::new();
+ t.compile_fail("tests/ui/async_instrument.rs");
+}
diff --git a/tests/ui/async_instrument.rs b/tests/ui/async_instrument.rs
new file mode 100644
index 0000000..5b24574
--- /dev/null
+++ b/tests/ui/async_instrument.rs
@@ -0,0 +1,46 @@
+#![allow(unreachable_code)]
+
+#[tracing::instrument]
+async fn unit() {
+ ""
+}
+
+#[tracing::instrument]
+async fn simple_mismatch() -> String {
+ ""
+}
+
+// FIXME: this span is still pretty poor
+#[tracing::instrument]
+async fn opaque_unsatisfied() -> impl std::fmt::Display {
+ ("",)
+}
+
+struct Wrapper<T>(T);
+
+#[tracing::instrument]
+async fn mismatch_with_opaque() -> Wrapper<impl std::fmt::Display> {
+ ""
+}
+
+#[tracing::instrument]
+async fn early_return_unit() {
+ if true {
+ return "";
+ }
+}
+
+#[tracing::instrument]
+async fn early_return() -> String {
+ if true {
+ return "";
+ }
+ String::new()
+}
+
+#[tracing::instrument]
+async fn extra_semicolon() -> i32 {
+ 1;
+}
+
+fn main() {}
diff --git a/tests/ui/async_instrument.stderr b/tests/ui/async_instrument.stderr
new file mode 100644
index 0000000..db6f6b4
--- /dev/null
+++ b/tests/ui/async_instrument.stderr
@@ -0,0 +1,98 @@
+error[E0308]: mismatched types
+ --> tests/ui/async_instrument.rs:5:5
+ |
+5 | ""
+ | ^^ expected `()`, found `&str`
+ |
+note: return type inferred to be `()` here
+ --> tests/ui/async_instrument.rs:4:10
+ |
+4 | async fn unit() {
+ | ^^^^
+
+error[E0308]: mismatched types
+ --> tests/ui/async_instrument.rs:10:5
+ |
+10 | ""
+ | ^^- help: try using a conversion method: `.to_string()`
+ | |
+ | expected struct `String`, found `&str`
+ |
+note: return type inferred to be `String` here
+ --> tests/ui/async_instrument.rs:9:31
+ |
+9 | async fn simple_mismatch() -> String {
+ | ^^^^^^
+
+error[E0277]: `(&str,)` doesn't implement `std::fmt::Display`
+ --> tests/ui/async_instrument.rs:14:1
+ |
+14 | #[tracing::instrument]
+ | ^^^^^^^^^^^^^^^^^^^^^^ `(&str,)` cannot be formatted with the default formatter
+ |
+ = help: the trait `std::fmt::Display` is not implemented for `(&str,)`
+ = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
+ = note: this error originates in the attribute macro `tracing::instrument` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error[E0277]: `(&str,)` doesn't implement `std::fmt::Display`
+ --> tests/ui/async_instrument.rs:15:34
+ |
+15 | async fn opaque_unsatisfied() -> impl std::fmt::Display {
+ | ^^^^^^^^^^^^^^^^^^^^^^ `(&str,)` cannot be formatted with the default formatter
+ |
+ = help: the trait `std::fmt::Display` is not implemented for `(&str,)`
+ = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
+
+error[E0308]: mismatched types
+ --> tests/ui/async_instrument.rs:23:5
+ |
+23 | ""
+ | ^^ expected struct `Wrapper`, found `&str`
+ |
+ = note: expected struct `Wrapper<_>`
+ found reference `&'static str`
+note: return type inferred to be `Wrapper<_>` here
+ --> tests/ui/async_instrument.rs:22:36
+ |
+22 | async fn mismatch_with_opaque() -> Wrapper<impl std::fmt::Display> {
+ | ^^^^^^^
+help: try wrapping the expression in `Wrapper`
+ |
+23 | Wrapper("")
+ | ++++++++ +
+
+error[E0308]: mismatched types
+ --> tests/ui/async_instrument.rs:29:16
+ |
+29 | return "";
+ | ^^ expected `()`, found `&str`
+ |
+note: return type inferred to be `()` here
+ --> tests/ui/async_instrument.rs:27:10
+ |
+27 | async fn early_return_unit() {
+ | ^^^^^^^^^^^^^^^^^
+
+error[E0308]: mismatched types
+ --> tests/ui/async_instrument.rs:36:16
+ |
+36 | return "";
+ | ^^- help: try using a conversion method: `.to_string()`
+ | |
+ | expected struct `String`, found `&str`
+ |
+note: return type inferred to be `String` here
+ --> tests/ui/async_instrument.rs:34:28
+ |
+34 | async fn early_return() -> String {
+ | ^^^^^^
+
+error[E0308]: mismatched types
+ --> tests/ui/async_instrument.rs:42:35
+ |
+42 | async fn extra_semicolon() -> i32 {
+ | ___________________________________^
+43 | | 1;
+ | | - help: remove this semicolon
+44 | | }
+ | |_^ expected `i32`, found `()`