diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /third_party/rust/tracing-attributes | |
parent | Initial commit. (diff) | |
download | firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/tracing-attributes')
22 files changed, 4622 insertions, 0 deletions
diff --git a/third_party/rust/tracing-attributes/.cargo-checksum.json b/third_party/rust/tracing-attributes/.cargo-checksum.json new file mode 100644 index 0000000000..e29c0854b7 --- /dev/null +++ b/third_party/rust/tracing-attributes/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"CHANGELOG.md":"7c292985e41b0fd9c89d32bd15594b76bb3109ff96177af7461d1a0d3ea48574","Cargo.toml":"1c272401b940ef2fd88638a6228af02c14e3d47d4b592ee5c445f7470a0d1763","LICENSE":"898b1ae9821e98daf8964c8d6c7f61641f5f5aa78ad500020771c0939ee0dea1","README.md":"aae710a2c0c4ade929a84f8f3d9f98fcb9ad1c1e9e52ae0b0393db9f353f6248","src/attr.rs":"4d26dad70c765fff3d4677ebe8e71b63599d67ceba5c48dbcb204d247b5394ce","src/expand.rs":"f070e38eb3526c4c50e98cfafb2c26e50c7170f27d0bd716838a29777c1174cd","src/lib.rs":"14803c868525f6ae95b846ce2f369a47d50b2befba336335354867cf2a1b2455","tests/async_fn.rs":"6ae845ab6c508f9f2d1ea7ec3855971e8cff85afdae326593089d0a53c9c6dcf","tests/destructuring.rs":"26b9800678bad09e06512a113a54556e2fac3ecb15a18dcccefe105fb8911c26","tests/err.rs":"3beb5f065e57a578825c383a56b9d64fb89794a508018bf36e16f113557909b8","tests/fields.rs":"3882bd4e744d6b492f59beac7475e8bf4ff4ca8ad85c6951c305a22c78e75fae","tests/follows_from.rs":"5bc856923e87b34e0558959149118238fe668ac621f1748cc444c21c90a86647","tests/instrument.rs":"e3a90f2aef0d2f56c2c25018e2fbacf518e90ef6810930941f86740279252d03","tests/levels.rs":"80ffb684163a4d28c69c40e31a82609ac02daf922086bab8247bca125aec3c69","tests/names.rs":"5afd6c4d526588bcea3141c130a45a21872956495b6868a01b44ddff57749827","tests/parents.rs":"673d3f81eed6ba433f685ec53fd007c5dd957b97d32499d7ea1537e1f289cb2e","tests/ret.rs":"083b4ca456d766c469b2fff7608b59524b422941e5c5e84f07c1ce0d7b345c7a","tests/targets.rs":"95ce1ce1e2d29794062c5b3429d91c1bfaba5813251d5d8440c12cb2db6e11bf","tests/ui.rs":"c73c70fe7371998df077862092f40df7210b13496e119ab4e657a1848e42ab30","tests/ui/async_instrument.rs":"00fcde05841e8f9f6cc6f9434f8cc4baed5cf6e3ca73e8faddccbdace14e9485","tests/ui/async_instrument.stderr":"6f0457b5cd035a9bc82f5e98fb6d3a74177336ec6a19ba2e7cb9dbf3e67c3aff"},"package":"4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"}
\ No newline at end of file diff --git a/third_party/rust/tracing-attributes/CHANGELOG.md b/third_party/rust/tracing-attributes/CHANGELOG.md new file mode 100644 index 0000000000..42cb09b6a1 --- /dev/null +++ b/third_party/rust/tracing-attributes/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/third_party/rust/tracing-attributes/Cargo.toml b/third_party/rust/tracing-attributes/Cargo.toml new file mode 100644 index 0000000000..5639de0b3e --- /dev/null +++ b/third_party/rust/tracing-attributes/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/third_party/rust/tracing-attributes/LICENSE b/third_party/rust/tracing-attributes/LICENSE new file mode 100644 index 0000000000..cdb28b4b56 --- /dev/null +++ b/third_party/rust/tracing-attributes/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/third_party/rust/tracing-attributes/README.md b/third_party/rust/tracing-attributes/README.md new file mode 100644 index 0000000000..356b511f2a --- /dev/null +++ b/third_party/rust/tracing-attributes/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/third_party/rust/tracing-attributes/src/attr.rs b/third_party/rust/tracing-attributes/src/attr.rs new file mode 100644 index 0000000000..ff875e1797 --- /dev/null +++ b/third_party/rust/tracing-attributes/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/third_party/rust/tracing-attributes/src/expand.rs b/third_party/rust/tracing-attributes/src/expand.rs new file mode 100644 index 0000000000..7005b4423e --- /dev/null +++ b/third_party/rust/tracing-attributes/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 == ¶m) + }) + } 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/third_party/rust/tracing-attributes/src/lib.rs b/third_party/rust/tracing-attributes/src/lib.rs new file mode 100644 index 0000000000..f5974e4e52 --- /dev/null +++ b/third_party/rust/tracing-attributes/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/third_party/rust/tracing-attributes/tests/async_fn.rs b/third_party/rust/tracing-attributes/tests/async_fn.rs new file mode 100644 index 0000000000..c89963672c --- /dev/null +++ b/third_party/rust/tracing-attributes/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/third_party/rust/tracing-attributes/tests/destructuring.rs b/third_party/rust/tracing-attributes/tests/destructuring.rs new file mode 100644 index 0000000000..09cf1ad534 --- /dev/null +++ b/third_party/rust/tracing-attributes/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/third_party/rust/tracing-attributes/tests/err.rs b/third_party/rust/tracing-attributes/tests/err.rs new file mode 100644 index 0000000000..9e6d6b78c3 --- /dev/null +++ b/third_party/rust/tracing-attributes/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/third_party/rust/tracing-attributes/tests/fields.rs b/third_party/rust/tracing-attributes/tests/fields.rs new file mode 100644 index 0000000000..c178fbb3d3 --- /dev/null +++ b/third_party/rust/tracing-attributes/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/third_party/rust/tracing-attributes/tests/follows_from.rs b/third_party/rust/tracing-attributes/tests/follows_from.rs new file mode 100644 index 0000000000..da0eec6357 --- /dev/null +++ b/third_party/rust/tracing-attributes/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/third_party/rust/tracing-attributes/tests/instrument.rs b/third_party/rust/tracing-attributes/tests/instrument.rs new file mode 100644 index 0000000000..7686927488 --- /dev/null +++ b/third_party/rust/tracing-attributes/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/third_party/rust/tracing-attributes/tests/levels.rs b/third_party/rust/tracing-attributes/tests/levels.rs new file mode 100644 index 0000000000..b074ea4f28 --- /dev/null +++ b/third_party/rust/tracing-attributes/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/third_party/rust/tracing-attributes/tests/names.rs b/third_party/rust/tracing-attributes/tests/names.rs new file mode 100644 index 0000000000..d97dece9a1 --- /dev/null +++ b/third_party/rust/tracing-attributes/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/third_party/rust/tracing-attributes/tests/parents.rs b/third_party/rust/tracing-attributes/tests/parents.rs new file mode 100644 index 0000000000..7069b98ea5 --- /dev/null +++ b/third_party/rust/tracing-attributes/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/third_party/rust/tracing-attributes/tests/ret.rs b/third_party/rust/tracing-attributes/tests/ret.rs new file mode 100644 index 0000000000..cfd2de10d3 --- /dev/null +++ b/third_party/rust/tracing-attributes/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/third_party/rust/tracing-attributes/tests/targets.rs b/third_party/rust/tracing-attributes/tests/targets.rs new file mode 100644 index 0000000000..363f628f31 --- /dev/null +++ b/third_party/rust/tracing-attributes/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/third_party/rust/tracing-attributes/tests/ui.rs b/third_party/rust/tracing-attributes/tests/ui.rs new file mode 100644 index 0000000000..f11cc019eb --- /dev/null +++ b/third_party/rust/tracing-attributes/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/third_party/rust/tracing-attributes/tests/ui/async_instrument.rs b/third_party/rust/tracing-attributes/tests/ui/async_instrument.rs new file mode 100644 index 0000000000..5b245746a6 --- /dev/null +++ b/third_party/rust/tracing-attributes/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/third_party/rust/tracing-attributes/tests/ui/async_instrument.stderr b/third_party/rust/tracing-attributes/tests/ui/async_instrument.stderr new file mode 100644 index 0000000000..db6f6b4343 --- /dev/null +++ b/third_party/rust/tracing-attributes/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 `()` |