tracing-attributes-0.1.27/.cargo_vcs_info.json0000644000000001600000000000100147330ustar { "git": { "sha1": "2502f19d934b092fc01bb7493eacbb16b4038bf3" }, "path_in_vcs": "tracing-attributes" }tracing-attributes-0.1.27/CHANGELOG.md000064400000000000000000000302621046102023000153420ustar 00000000000000# 0.1.27 (October 13, 2023) ### Changed - Bump minimum version of proc-macro2 to 1.0.60 ([#2732]) - Generate less dead code for async block return type hint ([#2709]) ### Fixed - Fix a compilation error in `#[instrument]` when the `"log"` feature is enabled ([#2599]) [#2732]: https://github.com/tokio-rs/tracing/pull/2732 [#2709]: https://github.com/tokio-rs/tracing/pull/2709 [#2599]: https://github.com/tokio-rs/tracing/pull/2599 # 0.1.26 (June 21th, 2023) This release of `tracing-attributes` fixes warnings due to `allow` attributes in generated code that allow lints which may not exist on earlier versions of rustc. ### Fixed - Allow `unknown_lints` in macro-generated code ([#2626]) Thanks to @mladedav for contributing to this release! # 0.1.25 (June 19th, 2023) This release of `tracing-attributes` fixes the Clippy lint [`let_with_type_underscore`] in code generated by the `#[instrument]` attribute in Rust 1.70+. ### Fixed - Allow [`clippy::let_with_type_underscore`] in macro-generated code ([#2609]) Thanks to @coolreader19 for contributing to this release! [#2609]: https://github.com/tokio-rs/tracing/pull/2609 [`let_with_type_underscore`]: http://rust-lang.github.io/rust-clippy/rust-1.70.0/index.html#let_with_type_underscore # 0.1.24 (April 24th, 2023) This release of `tracing-attributes` adds support for passing an optional `level` to the `err` and `ret` arguments to `#[instrument]`, allowing the level of the generated return-value event to be overridden. For example, ```rust #[instrument(err(level = "info"))] fn my_great_function() -> Result<(), &'static str> { // ... } ``` will emit an `INFO`-level event if the function returns an `Err`. In addition, this release updates the [`syn`] dependency to v2.x.x. ### Added - `level` argument to `err` and `ret` to override the level of the generated return value event ([#2335]) - Improved compiler error message when `#[instrument]` is added to a `const fn` ([#2418]) ### Changed - Updated `syn` dependency to 2.0 ([#2516]) ### Fixed - Fix `clippy::unreachable` warnings in `#[instrument]`-generated code ([#2356]) - Removed unused "visit" feature flag from `syn` dependency ([#2530]) ### Documented - Documented default level for `err` ([#2433]) - Improved documentation for levels in `#[instrument]` ([#2350]) Thanks to @nitnelave, @jsgf, @Abhicodes-crypto, @LukeMathWalker, @andrewpollack, @quad, @klensy, @davidpdrsn, and @dbidwell94 for contributign to this release! [`syn`]: https://crates.io/crates/syn [#2335]: https://github.com/tokio-rs/tracing/pull/2335 [#2418]: https://github.com/tokio-rs/tracing/pull/2418 [#2516]: https://github.com/tokio-rs/tracing/pull/2516 [#2356]: https://github.com/tokio-rs/tracing/pull/2356 [#2530]: https://github.com/tokio-rs/tracing/pull/2530 [#2433]: https://github.com/tokio-rs/tracing/pull/2433 [#2350]: https://github.com/tokio-rs/tracing/pull/2350 # 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 tracing-attributes-0.1.27/Cargo.toml0000644000000034320000000000100127360ustar # 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.56.0" name = "tracing-attributes" version = "0.1.27" authors = [ "Tokio Contributors ", "Eliza Weisman ", "David Barsky ", ] 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.0.60" [dependencies.quote] version = "1.0.20" [dependencies.syn] version = "2.0" features = [ "full", "parsing", "printing", "visit-mut", "clone-impls", "extra-traits", "proc-macro", ] default-features = false [dev-dependencies.async-trait] version = "0.1.67" [dev-dependencies.rustversion] version = "1.0.9" [dev-dependencies.tokio-test] version = "0.4.2" [dev-dependencies.tracing] version = "0.1.35" [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" tracing-attributes-0.1.27/Cargo.toml.orig000064400000000000000000000027411046102023000164210ustar 00000000000000[package] name = "tracing-attributes" # When releasing to crates.io: # - Remove path dependencies # - Update html_root_url. # - Update doc url # - Cargo.toml # - README.md # - Update CHANGELOG.md. # - Create "v0.1.x" git tag. version = "0.1.27" authors = [ "Tokio Contributors ", "Eliza Weisman ", "David Barsky ", ] repository = "https://github.com/tokio-rs/tracing" homepage = "https://tokio.rs" description = """ Procedural macro attributes for automatically instrumenting functions. """ categories = [ "development-tools::debugging", "development-tools::profiling", "asynchronous", ] keywords = ["logging", "tracing", "macro", "instrument", "log"] license = "MIT" readme = "README.md" edition = "2018" rust-version = "1.56.0" [lib] proc-macro = true [features] # This feature flag is no longer necessary. async-await = [] [dependencies] proc-macro2 = "1.0.60" syn = { version = "2.0", default-features = false, features = ["full", "parsing", "printing", "visit-mut", "clone-impls", "extra-traits", "proc-macro"] } quote = "1.0.20" [dev-dependencies] tracing = { path = "../tracing", version = "0.1.35" } tracing-mock = { path = "../tracing-mock", features = ["tokio-test"] } tracing-subscriber = { path = "../tracing-subscriber", version = "0.3.0", features = ["env-filter"] } tokio-test = "0.4.2" async-trait = "0.1.67" trybuild = "1.0.64" rustversion = "1.0.9" [badges] maintenance = { status = "experimental" } tracing-attributes-0.1.27/LICENSE000064400000000000000000000020461046102023000145350ustar 00000000000000Copyright (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. tracing-attributes-0.1.27/README.md000064400000000000000000000061271046102023000150130ustar 00000000000000![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.26 [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.56+][msrv]* [msrv]: #supported-rust-versions ## Usage First, add this to your `Cargo.toml`: ```toml [dependencies] tracing-attributes = "0.1.26" ``` 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.56. 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.69, the minimum supported version will not be increased past 1.66, 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. tracing-attributes-0.1.27/src/attr.rs000064400000000000000000000401571046102023000156440ustar 00000000000000use 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}; /// Arguments to `#[instrument(err(...))]` and `#[instrument(ret(...))]` which describe how the /// return value event should be emitted. #[derive(Clone, Default, Debug)] pub(crate) struct EventArgs { level: Option, pub(crate) mode: FormatMode, } #[derive(Clone, Default, Debug)] pub(crate) struct InstrumentArgs { level: Option, pub(crate) name: Option, target: Option, pub(crate) parent: Option, pub(crate) follows_from: Option, pub(crate) skips: HashSet, pub(crate) skip_all: bool, pub(crate) fields: Option, pub(crate) err_args: Option, pub(crate) ret_args: Option, /// Errors describing any unrecognized parse inputs that we skipped. parse_warnings: Vec, } impl InstrumentArgs { pub(crate) fn level(&self) -> Level { self.level.clone().unwrap_or(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 { 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::>()?.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::>()?.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::>()?; 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::>()?; 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::()?; 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::(); let err_args = EventArgs::parse(input)?; args.err_args = Some(err_args); } else if lookahead.peek(kw::ret) { let _ = input.parse::()?; let ret_args = EventArgs::parse(input)?; args.ret_args = Some(ret_args); } else if lookahead.peek(Token![,]) { let _ = input.parse::()?; } 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::(); } } Ok(args) } } impl EventArgs { pub(crate) fn level(&self, default: Level) -> Level { self.level.clone().unwrap_or(default) } } impl Parse for EventArgs { fn parse(input: ParseStream<'_>) -> syn::Result { if !input.peek(syn::token::Paren) { return Ok(Self::default()); } let content; let _ = syn::parenthesized!(content in input); let mut result = Self::default(); let mut parse_one_arg = || { let lookahead = content.lookahead1(); if lookahead.peek(kw::level) { if result.level.is_some() { return Err(content.error("expected only a single `level` argument")); } result.level = Some(content.parse()?); } else if result.mode != FormatMode::default() { return Err(content.error("expected only a single format argument")); } else if let Some(ident) = content.parse::>()? { match ident.to_string().as_str() { "Debug" => result.mode = FormatMode::Debug, "Display" => result.mode = FormatMode::Display, _ => return Err(syn::Error::new( ident.span(), "unknown event formatting mode, expected either `Debug` or `Display`", )), } } Ok(()) }; parse_one_arg()?; if !content.is_empty() { if content.lookahead1().peek(Token![,]) { let _ = content.parse::()?; parse_one_arg()?; } else { return Err(content.error("expected `,` or `)`")); } } Ok(result) } } struct StrArg { value: LitStr, _p: std::marker::PhantomData, } impl Parse for StrArg { fn parse(input: ParseStream<'_>) -> syn::Result { let _ = input.parse::()?; let _ = input.parse::()?; let value = input.parse()?; Ok(Self { value, _p: std::marker::PhantomData, }) } } struct ExprArg { value: Expr, _p: std::marker::PhantomData, } impl Parse for ExprArg { fn parse(input: ParseStream<'_>) -> syn::Result { let _ = input.parse::()?; let _ = input.parse::()?; let value = input.parse()?; Ok(Self { value, _p: std::marker::PhantomData, }) } } struct Skips(HashSet); impl Parse for Skips { fn parse(input: ParseStream<'_>) -> syn::Result { let _ = input.parse::(); let content; let _ = syn::parenthesized!(content in input); let names = content.parse_terminated(Ident::parse_any, Token![,])?; 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 } } #[derive(Clone, Debug)] pub(crate) struct Fields(pub(crate) Punctuated); #[derive(Clone, Debug)] pub(crate) struct Field { pub(crate) name: Punctuated, pub(crate) value: Option, 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 { let _ = input.parse::(); let content; let _ = syn::parenthesized!(content in input); let fields = content.parse_terminated(Field::parse, Token![,])?; 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 { let mut kind = FieldKind::Value; if input.peek(Token![%]) { input.parse::()?; kind = FieldKind::Display; } else if input.peek(Token![?]) { input.parse::()?; kind = FieldKind::Debug; }; let name = Punctuated::parse_separated_nonempty_with(input, Ident::parse_any)?; let value = if input.peek(Token![=]) { input.parse::()?; if input.peek(Token![%]) { input.parse::()?; kind = FieldKind::Display; } else if input.peek(Token![?]) { input.parse::()?; 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)] pub(crate) enum Level { Trace, Debug, Info, Warn, Error, Path(Path), } impl Parse for Level { fn parse(input: ParseStream<'_>) -> syn::Result { let _ = input.parse::()?; let _ = input.parse::()?; let lookahead = input.lookahead1(); if lookahead.peek(LitStr) { let str: LitStr = input.parse()?; match str.value() { s if s.eq_ignore_ascii_case("trace") => Ok(Level::Trace), s if s.eq_ignore_ascii_case("debug") => Ok(Level::Debug), s if s.eq_ignore_ascii_case("info") => Ok(Level::Info), s if s.eq_ignore_ascii_case("warn") => Ok(Level::Warn), s if s.eq_ignore_ascii_case("error") => Ok(Level::Error), _ => Err(input.error( "unknown verbosity level, expected one of \"trace\", \ \"debug\", \"info\", \"warn\", or \"error\", or a number 1-5", )), } } else if lookahead.peek(LitInt) { fn is_level(lit: &LitInt, expected: u64) -> bool { match lit.base10_parse::() { Ok(value) => value == expected, Err(_) => false, } } let int: LitInt = input.parse()?; match &int { i if is_level(i, 1) => Ok(Level::Trace), i if is_level(i, 2) => Ok(Level::Debug), i if is_level(i, 3) => Ok(Level::Info), i if is_level(i, 4) => Ok(Level::Warn), i if is_level(i, 5) => Ok(Level::Error), _ => Err(input.error( "unknown verbosity level, expected one of \"trace\", \ \"debug\", \"info\", \"warn\", or \"error\", or a number 1-5", )), } } else if lookahead.peek(Ident) { Ok(Self::Path(input.parse()?)) } else { Err(lookahead.error()) } } } impl ToTokens for Level { fn to_tokens(&self, tokens: &mut TokenStream) { match self { Level::Trace => tokens.extend(quote!(tracing::Level::TRACE)), Level::Debug => tokens.extend(quote!(tracing::Level::DEBUG)), Level::Info => tokens.extend(quote!(tracing::Level::INFO)), Level::Warn => tokens.extend(quote!(tracing::Level::WARN)), Level::Error => tokens.extend(quote!(tracing::Level::ERROR)), Level::Path(ref pat) => tokens.extend(quote!(#pat)), } } } 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); } tracing-attributes-0.1.27/src/expand.rs000064400000000000000000000677241046102023000161620ustar 00000000000000use 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, Level}, 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( unknown_lints, unreachable_code, clippy::diverging_sub_expression, clippy::let_unit_value, clippy::unreachable, clippy::let_with_type_underscore, clippy::empty_loop )] if false { let __tracing_attr_fake_return: #return_type = loop {}; 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( block: &B, params: &Punctuated, 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 args_level = args.level(); let level = args_level.clone(); 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_args { Some(event_args) => { let level_tokens = event_args.level(Level::Error); match event_args.mode { FormatMode::Default | FormatMode::Display => Some(quote!( tracing::event!(target: #target, #level_tokens, error = %e) )), FormatMode::Debug => Some(quote!( tracing::event!(target: #target, #level_tokens, error = ?e) )), } } _ => None, }; let ret_event = match args.ret_args { Some(event_args) => { let level_tokens = event_args.level(args_level); match event_args.mode { FormatMode::Display => Some(quote!( tracing::event!(target: #target, #level_tokens, return = %x) )), FormatMode::Default | FormatMode::Debug => Some(quote!( tracing::event!(target: #target, #level_tokens, 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::if_log_enabled!(#level, {true} else {false}) { __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> { 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 { 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, 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 { // 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, _semi) = 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 { // let's rewrite some statements! let mut out_stmts: Vec = 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 } tracing-attributes-0.1.27/src/lib.rs000064400000000000000000000537051046102023000154430ustar 00000000000000//! 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.56+][msrv]* //! //! [msrv]: #supported-rust-versions //! //! ## Usage //! //! In the `Cargo.toml`: //! //! ```toml //! [dependencies] //! tracing-attributes = "0.1.24" //! ``` //! //! 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::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.56. 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.69, the minimum supported version will not be //! increased past 1.66, 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_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, 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::{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 overridden, 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, also_large: HashMap) { /// // ... /// } /// ``` /// /// Skipping the `self` parameter: /// /// ``` /// # use tracing_attributes::instrument; /// #[derive(Debug)] /// struct MyType { /// data: Vec, // 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) can be passed to /// to the generated span through the `fields` argument on the /// `#[instrument]` macro. Strings, integers or boolean literals are accepted values /// for each field. The name of the field must be a single valid Rust /// identifier, nested (dotted) field names are not supported. 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. /// /// 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 { pub(super) _b: std::marker::PhantomData } /// # pub struct Request { _b: B } /// # impl std::fmt::Debug for Request { /// # fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { /// # f.pad("request") /// # } /// # } /// # impl Request { /// # 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(req: http::Request) -> http::Response { /// // ... 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, /// } /// /// 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; /// # use tracing::Level; /// #[instrument(level = Level::DEBUG)] /// pub fn my_function() { /// // ... /// } /// ``` /// Levels can be specified either with [`Level`] constants, literal strings /// (e.g., `"debug"`, `"info"`) or numerically (1—5, corresponding to [`Level::TRACE`]—[`Level::ERROR`]). /// /// 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>>` /// 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 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. /// /// It's also possible to override the level for the `ret` event independently: /// /// ``` /// # use tracing_attributes::instrument; /// # use tracing::Level; /// #[instrument(ret(level = Level::WARN))] /// fn my_function() -> i32 { /// 42 /// } /// ``` /// /// **Note**: if the function returns a `Result`, `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` and `E` implements `std::fmt::Display`, adding /// `err` or `err(Display)` will emit error events when the function returns `Err`: /// /// ``` /// # use tracing_attributes::instrument; /// #[instrument(err)] /// fn my_function(arg: usize) -> Result<(), std::io::Error> { /// Ok(()) /// } /// ``` /// /// The level of the error value event defaults to `ERROR`. /// /// Similarly, overriding the level of the `err` event : /// /// ``` /// # use tracing_attributes::instrument; /// # use tracing::Level; /// #[instrument(err(level = Level::INFO))] /// 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::()))] /// async fn foo(&self, arg: usize) {} /// } /// ``` /// /// `const fn` cannot be instrumented, and will result in a compilation failure: /// /// ```compile_fail /// # use tracing_attributes::instrument; /// #[instrument] /// const fn my_const_function() {} /// ``` /// /// [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 /// [`Level`]: https://docs.rs/tracing/latest/tracing/struct.Level.html /// [`Level::TRACE`]: https://docs.rs/tracing/latest/tracing/struct.Level.html#associatedconstant.TRACE /// [`Level::ERROR`]: https://docs.rs/tracing/latest/tracing/struct.Level.html#associatedconstant.ERROR #[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 { let input = syn::parse::(item)?; let instrumented_function_name = input.sig.ident.to_string(); if input.sig.constness.is_some() { return Ok(quote! { compile_error!("the `#[instrument]` attribute may not be used with `const fn`s") } .into()); } // 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, inner_attrs: Vec, 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 { 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 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, inner_attrs: &'a Vec, vis: &'a Visibility, sig: &'a Signature, block: &'a B, } tracing-attributes-0.1.27/tests/async_fn.rs000064400000000000000000000333701046102023000170440ustar 00000000000000use 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, ()> { 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, &'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::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> { async { Ok(()) } } #[test] fn async_fn_only_enters_for_polls() { let (subscriber, handle) = subscriber::mock() .new_span(expect::span().named("test_async_fn")) .enter(expect::span().named("test_async_fn")) .event(expect::event().with_fields(expect::field("awaiting").with_value(&true))) .exit(expect::span().named("test_async_fn")) .enter(expect::span().named("test_async_fn")) .exit(expect::span().named("test_async_fn")) .enter(expect::span().named("test_async_fn")) .exit(expect::span().named("test_async_fn")) .drop_span(expect::span().named("test_async_fn")) .only() .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 = expect::span().named("test_async_fns_nested"); let span2 = expect::span().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(expect::event().with_fields(expect::field("nested").with_value(&true))) .exit(span2.clone()) .enter(span2.clone()) .exit(span2.clone()) .drop_span(span2) .exit(span.clone()) .enter(span.clone()) .exit(span.clone()) .drop_span(span) .only() .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 = expect::span().named("foo"); let span2 = expect::span().named("bar"); let span3 = expect::span().named("baz"); let (subscriber, handle) = subscriber::mock() .new_span( span.clone() .with_field(expect::field("self")) .with_field(expect::field("v")), ) .enter(span.clone()) .new_span(span3.clone()) .enter(span3.clone()) .event(expect::event().with_fields(expect::field("val").with_value(&2u64))) .exit(span3.clone()) .enter(span3.clone()) .exit(span3.clone()) .drop_span(span3) .new_span(span2.clone().with_field(expect::field("self"))) .enter(span2.clone()) .event(expect::event().with_fields(expect::field("val").with_value(&5u64))) .exit(span2.clone()) .enter(span2.clone()) .exit(span2.clone()) .drop_span(span2) .exit(span.clone()) .enter(span.clone()) .exit(span.clone()) .drop_span(span) .only() .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 = expect::span().named("call"); let (subscriber, handle) = subscriber::mock() .new_span( span.clone().with_field( expect::field("_v") .with_value(&5usize) .and(expect::field("test").with_value(&tracing::field::debug(10))) .and(expect::field("val").with_value(&42u64)) .and(expect::field("val2").with_value(&42u64)), ), ) .enter(span.clone()) .exit(span.clone()) .enter(span.clone()) .exit(span.clone()) .drop_span(span) .only() .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::()))] fn sync_fun(&self) -> Pin + 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::()))] async fn call() {} #[instrument(fields(Self=std::any::type_name::()))] async fn call_with_self(&self) { self.sync_fun().await; } #[instrument(fields(Self=std::any::type_name::()))] async fn call_with_mut_self(&mut self) {} } //let span = span::mock().named("call"); let span2 = expect::span().named("call_with_self"); let span3 = expect::span().named("call_with_mut_self"); let span4 = expect::span().named("sync_fun"); let (subscriber, handle) = subscriber::mock() /*.new_span(span.clone() .with_field( expect::field("Self").with_value(&"TestImpler"))) .enter(span.clone()) .exit(span.clone()) .drop_span(span)*/ .new_span( span2 .clone() .with_field(expect::field("Self").with_value(&std::any::type_name::())), ) .enter(span2.clone()) .new_span( span4 .clone() .with_field(expect::field("Self").with_value(&std::any::type_name::())), ) .enter(span4.clone()) .exit(span4.clone()) .enter(span4.clone()) .exit(span4) .exit(span2.clone()) .enter(span2.clone()) .exit(span2.clone()) .drop_span(span2) .new_span( span3 .clone() .with_field(expect::field("Self").with_value(&std::any::type_name::())), ) .enter(span3.clone()) .exit(span3.clone()) .enter(span3.clone()) .exit(span3.clone()) .drop_span(span3) .only() .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> + Send + Sync>> { // ... let metrics = self.metrics.clone(); // ... Box::pin(async move { // ... metrics // cannot find value `metrics` in this scope }) } } let span = expect::span().named("call"); let (subscriber, handle) = subscriber::mock() .new_span(span.clone()) .enter(span.clone()) .exit(span.clone()) .enter(span.clone()) .exit(span.clone()) .drop_span(span) .only() .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 { async { tracing::trace!(poll = true); } } let span = expect::span().named("manual_impl_future"); let poll_event = || expect::event().with_fields(expect::field("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()) .enter(span.clone()) .exit(span.clone()) .drop_span(span) .only() .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::pin(async { tracing::trace!(poll = true); }) } let span = expect::span().named("manual_box_pin"); let poll_event = || expect::event().with_fields(expect::field("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()) .enter(span.clone()) .exit(span.clone()) .drop_span(span) .only() .run_with_handle(); with_default(subscriber, || { block_on_future(async { manual_box_pin().await; }); }); handle.assert_finished(); } tracing-attributes-0.1.27/tests/destructuring.rs000064400000000000000000000120571046102023000201450ustar 00000000000000use 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 = expect::span().named("my_fn"); let (subscriber, handle) = subscriber::mock() .new_span( span.clone().with_field( expect::field("arg1") .with_value(&format_args!("1")) .and(expect::field("arg2").with_value(&format_args!("2"))) .only(), ), ) .enter(span.clone()) .exit(span.clone()) .drop_span(span) .only() .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 = expect::span().named("my_fn"); let (subscriber, handle) = subscriber::mock() .new_span( span.clone().with_field( expect::field("arg1") .with_value(&format_args!("1")) .and(expect::field("arg2").with_value(&format_args!("2"))) .and(expect::field("arg3").with_value(&format_args!("3"))) .and(expect::field("arg4").with_value(&format_args!("4"))) .only(), ), ) .enter(span.clone()) .exit(span.clone()) .drop_span(span) .only() .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 = expect::span().named("my_fn"); let (subscriber, handle) = subscriber::mock() .new_span( span.clone() .with_field(expect::field("arg1").with_value(&1usize).only()), ) .enter(span.clone()) .exit(span.clone()) .drop_span(span) .only() .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 = expect::span().named("my_fn"); let (subscriber, handle) = subscriber::mock() .new_span( span.clone().with_field( expect::field("arg1") .with_value(&format_args!("1")) .and(expect::field("arg2").with_value(&format_args!("2"))) .only(), ), ) .enter(span.clone()) .exit(span.clone()) .drop_span(span) .only() .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 = expect::span().named("my_fn"); let (subscriber, handle) = subscriber::mock() .new_span( span.clone().with_field( expect::field("arg1") .with_value(&format_args!("1")) .and(expect::field("arg2").with_value(&format_args!("2"))) .only(), ), ) .enter(span.clone()) .exit(span.clone()) .drop_span(span) .only() .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 = expect::span().named("my_fn"); let (subscriber, handle) = subscriber::mock() .new_span( span.clone().with_field( expect::field("arg1") .with_value(&format_args!("1")) .and(expect::field("arg2").with_value(&format_args!("2"))) .and(expect::field("arg3").with_value(&format_args!("3"))) .and(expect::field("arg4").with_value(&format_args!("4"))) .only(), ), ) .enter(span.clone()) .exit(span.clone()) .drop_span(span) .only() .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(); } tracing-attributes-0.1.27/tests/err.rs000064400000000000000000000222441046102023000160320ustar 00000000000000use 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::try_from(1234) } #[instrument(err)] fn err_suspicious_else() -> Result { {} u8::try_from(1234) } #[test] fn test() { let span = expect::span().named("err"); let (subscriber, handle) = subscriber::mock() .new_span(span.clone()) .enter(span.clone()) .event(expect::event().at_level(Level::ERROR)) .exit(span.clone()) .drop_span(span) .only() .run_with_handle(); with_default(subscriber, || err().ok()); handle.assert_finished(); } #[instrument(err)] fn err_early_return() -> Result { u8::try_from(1234)?; Ok(5) } #[test] fn test_early_return() { let span = expect::span().named("err_early_return"); let (subscriber, handle) = subscriber::mock() .new_span(span.clone()) .enter(span.clone()) .event(expect::event().at_level(Level::ERROR)) .exit(span.clone()) .drop_span(span) .only() .run_with_handle(); with_default(subscriber, || err_early_return().ok()); handle.assert_finished(); } #[instrument(err)] async fn err_async(polls: usize) -> Result { let future = PollN::new_ok(polls); tracing::trace!(awaiting = true); future.await.ok(); u8::try_from(1234) } #[test] fn test_async() { let span = expect::span().named("err_async"); let (subscriber, handle) = subscriber::mock() .new_span(span.clone()) .enter(span.clone()) .event( expect::event() .with_fields(expect::field("awaiting").with_value(&true)) .at_level(Level::TRACE), ) .exit(span.clone()) .enter(span.clone()) .event(expect::event().at_level(Level::ERROR)) .exit(span.clone()) .enter(span.clone()) .exit(span.clone()) .drop_span(span) .only() .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 = expect::span().named("err_mut"); let (subscriber, handle) = subscriber::mock() .new_span(span.clone()) .enter(span.clone()) .event(expect::event().at_level(Level::ERROR)) .exit(span.clone()) .drop_span(span) .only() .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 = expect::span().named("err_mut_async"); let (subscriber, handle) = subscriber::mock() .new_span(span.clone()) .enter(span.clone()) .event( expect::event() .with_fields(expect::field("awaiting").with_value(&true)) .at_level(Level::TRACE), ) .exit(span.clone()) .enter(span.clone()) .event(expect::event().at_level(Level::ERROR)) .exit(span.clone()) .enter(span.clone()) .exit(span.clone()) .drop_span(span) .only() .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, String> { Ok(0..x) } let span = expect::span().named("returns_impl_trait"); let (subscriber, handle) = subscriber::mock() .new_span( span.clone() .with_field(expect::field("x").with_value(&10usize).only()), ) .enter(span.clone()) .exit(span.clone()) .drop_span(span) .only() .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::try_from(1234) } #[test] fn test_err_dbg() { let span = expect::span().named("err_dbg"); let (subscriber, handle) = subscriber::mock() .new_span(span.clone()) .enter(span.clone()) .event( expect::event().at_level(Level::ERROR).with_fields( expect::field("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) .only() .run_with_handle(); with_default(subscriber, || err_dbg().ok()); handle.assert_finished(); } #[test] fn test_err_display_default() { let span = expect::span().named("err"); let (subscriber, handle) = subscriber::mock() .new_span(span.clone()) .enter(span.clone()) .event( expect::event().at_level(Level::ERROR).with_fields( expect::field("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) .only() .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 = expect::span().named("error_span").with_target("my_target"); let (subscriber, handle) = subscriber::mock() .new_span(span.clone()) .enter(span.clone()) .event( expect::event() .at_level(Level::ERROR) .with_target("my_target"), ) .exit(span.clone()) .drop_span(span) .only() .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(); } #[instrument(err(level = "info"))] fn err_info() -> Result { u8::try_from(1234) } #[test] fn test_err_info() { let span = expect::span().named("err_info"); let (subscriber, handle) = subscriber::mock() .new_span(span.clone()) .enter(span.clone()) .event(expect::event().at_level(Level::INFO)) .exit(span.clone()) .drop_span(span) .only() .run_with_handle(); with_default(subscriber, || err_info().ok()); handle.assert_finished(); } #[instrument(err(Debug, level = "info"))] fn err_dbg_info() -> Result { u8::try_from(1234) } #[test] fn test_err_dbg_info() { let span = expect::span().named("err_dbg_info"); let (subscriber, handle) = subscriber::mock() .new_span(span.clone()) .enter(span.clone()) .event( expect::event().at_level(Level::INFO).with_fields( expect::field("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) .only() .run_with_handle(); with_default(subscriber, || err_dbg_info().ok()); handle.assert_finished(); } #[instrument(level = "warn", err(level = "info"))] fn err_warn_info() -> Result { u8::try_from(1234) } #[test] fn test_err_warn_info() { let span = expect::span().named("err_warn_info").at_level(Level::WARN); let (subscriber, handle) = subscriber::mock() .new_span(span.clone()) .enter(span.clone()) .event(expect::event().at_level(Level::INFO)) .exit(span.clone()) .drop_span(span) .only() .run_with_handle(); with_default(subscriber, || err_warn_info().ok()); handle.assert_finished(); } tracing-attributes-0.1.27/tests/fields.rs000064400000000000000000000074451046102023000165160ustar 00000000000000use tracing::subscriber::with_default; use tracing_attributes::instrument; use tracing_mock::{expect, span::NewSpan, subscriber}; #[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 = expect::span().with_field( expect::field("foo") .with_value(&"bar") .and(expect::field("dsa").with_value(&true)) .and(expect::field("num").with_value(&1)) .only(), ); run_test(span, || { fn_no_param(); }); } #[test] fn expr_field() { let span = expect::span().with_field( expect::field("s") .with_value(&"hello world") .and(expect::field("len").with_value(&"hello world".len())) .only(), ); run_test(span, || { fn_expr_field("hello world"); }); } #[test] fn two_expr_fields() { let span = expect::span().with_field( expect::field("s") .with_value(&"hello world") .and(expect::field("s.len").with_value(&"hello world".len())) .and(expect::field("s.is_empty").with_value(&false)) .only(), ); run_test(span, || { fn_two_expr_fields("hello world"); }); } #[test] fn clashy_expr_field() { let span = expect::span().with_field( // Overriding the `s` field should record `s` as a `Display` value, // rather than as a `Debug` value. expect::field("s") .with_value(&tracing::field::display("hello world")) .and(expect::field("s.len").with_value(&"hello world".len())) .only(), ); run_test(span, || { fn_clashy_expr_field("hello world"); }); let span = expect::span().with_field(expect::field("s").with_value(&"s").only()); run_test(span, || { fn_clashy_expr_field2("hello world"); }); } #[test] fn self_expr_field() { let span = expect::span().with_field(expect::field("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 = expect::span().with_field( expect::field("foo") .with_value(&"bar") .and(expect::field("param").with_value(&1u32)) .only(), ); run_test(span, || { fn_param(1); }); } #[test] fn empty_field() { let span = expect::span().with_field(expect::field("foo").with_value(&"bar").only()); run_test(span, || { fn_empty_field(); }); } #[test] fn string_field() { let span = expect::span().with_field(expect::field("s").with_value(&"hello world").only()); run_test(span, || { fn_string(String::from("hello world")); }); } fn run_test T, T>(span: NewSpan, fun: F) { let (subscriber, handle) = subscriber::mock() .new_span(span) .enter(expect::span()) .exit(expect::span()) .only() .run_with_handle(); with_default(subscriber, fun); handle.assert_finished(); } tracing-attributes-0.1.27/tests/follows_from.rs000064400000000000000000000064371046102023000177600ustar 00000000000000use 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>>) {} #[instrument(follows_from = causes, skip(causes))] async fn with_follows_from_async(causes: impl IntoIterator>>) {} #[instrument(follows_from = [&Span::current()])] fn follows_from_current() {} #[test] fn follows_from_sync_test() { let cause_a = expect::span().named("cause_a"); let cause_b = expect::span().named("cause_b"); let cause_c = expect::span().named("cause_c"); let consequence = expect::span().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) .only() .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 = expect::span().named("cause_a"); let cause_b = expect::span().named("cause_b"); let cause_c = expect::span().named("cause_c"); let consequence = expect::span().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.clone()) .enter(consequence.clone()) .exit(consequence) .only() .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 = expect::span().named("cause"); let consequence = expect::span().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) .only() .run_with_handle(); with_default(subscriber, || { tracing::span!(Level::TRACE, "cause").in_scope(follows_from_current) }); handle.assert_finished(); } tracing-attributes-0.1.27/tests/instrument.rs000064400000000000000000000141431046102023000174510ustar 00000000000000use 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 = Level::DEBUG, target = "my_target")] fn my_other_fn() {} let span = expect::span() .named("my_fn") .at_level(Level::DEBUG) .with_target("my_target"); let span2 = expect::span() .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) .only() .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 = expect::span() .named("my_fn") .at_level(Level::DEBUG) .with_target("my_target"); let span2 = expect::span() .named("my_fn") .at_level(Level::DEBUG) .with_target("my_target"); let (subscriber, handle) = subscriber::mock() .new_span( span.clone().with_field( expect::field("arg1") .with_value(&2usize) .and(expect::field("arg2").with_value(&false)) .only(), ), ) .enter(span.clone()) .exit(span.clone()) .drop_span(span) .new_span( span2.clone().with_field( expect::field("arg1") .with_value(&3usize) .and(expect::field("arg2").with_value(&true)) .only(), ), ) .enter(span2.clone()) .exit(span2.clone()) .drop_span(span2) .only() .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 = expect::span() .named("my_fn") .at_level(Level::DEBUG) .with_target("my_target"); let span2 = expect::span() .named("my_fn") .at_level(Level::DEBUG) .with_target("my_target"); let span3 = expect::span() .named("my_fn2") .at_level(Level::DEBUG) .with_target("my_target"); let (subscriber, handle) = subscriber::mock() .new_span( span.clone() .with_field(expect::field("arg1").with_value(&2usize).only()), ) .enter(span.clone()) .exit(span.clone()) .drop_span(span) .new_span( span2 .clone() .with_field(expect::field("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) .only() .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(arg1: S, arg2: T) where S: std::fmt::Debug, { } let span = expect::span().named("my_fn"); let (subscriber, handle) = subscriber::mock() .new_span( span.clone().with_field( expect::field("arg1") .with_value(&format_args!("Foo")) .and(expect::field("arg2").with_value(&format_args!("false"))), ), ) .enter(span.clone()) .exit(span.clone()) .drop_span(span) .only() .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 = expect::span().named("my_fn"); let (subscriber, handle) = subscriber::mock() .new_span( span.clone().with_field( expect::field("self") .with_value(&format_args!("Foo")) .and(expect::field("arg1").with_value(&42usize)), ), ) .enter(span.clone()) .exit(span.clone()) .drop_span(span) .only() .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 { 0..x } let span = expect::span().named("returns_impl_trait"); let (subscriber, handle) = subscriber::mock() .new_span( span.clone() .with_field(expect::field("x").with_value(&10usize).only()), ) .enter(span.clone()) .exit(span.clone()) .drop_span(span) .only() .run_with_handle(); with_default(subscriber, || { for _ in returns_impl_trait(10) { // nop } }); handle.assert_finished(); } tracing-attributes-0.1.27/tests/levels.rs000064400000000000000000000113771046102023000165410ustar 00000000000000use 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(expect::span().named("trace").at_level(Level::TRACE)) .enter(expect::span().named("trace").at_level(Level::TRACE)) .exit(expect::span().named("trace").at_level(Level::TRACE)) .new_span(expect::span().named("debug").at_level(Level::DEBUG)) .enter(expect::span().named("debug").at_level(Level::DEBUG)) .exit(expect::span().named("debug").at_level(Level::DEBUG)) .new_span(expect::span().named("info").at_level(Level::INFO)) .enter(expect::span().named("info").at_level(Level::INFO)) .exit(expect::span().named("info").at_level(Level::INFO)) .new_span(expect::span().named("warn").at_level(Level::WARN)) .enter(expect::span().named("warn").at_level(Level::WARN)) .exit(expect::span().named("warn").at_level(Level::WARN)) .new_span(expect::span().named("error").at_level(Level::ERROR)) .enter(expect::span().named("error").at_level(Level::ERROR)) .exit(expect::span().named("error").at_level(Level::ERROR)) .only() .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(expect::span().named("trace").at_level(Level::TRACE)) .enter(expect::span().named("trace").at_level(Level::TRACE)) .exit(expect::span().named("trace").at_level(Level::TRACE)) .new_span(expect::span().named("debug").at_level(Level::DEBUG)) .enter(expect::span().named("debug").at_level(Level::DEBUG)) .exit(expect::span().named("debug").at_level(Level::DEBUG)) .new_span(expect::span().named("info").at_level(Level::INFO)) .enter(expect::span().named("info").at_level(Level::INFO)) .exit(expect::span().named("info").at_level(Level::INFO)) .new_span(expect::span().named("warn").at_level(Level::WARN)) .enter(expect::span().named("warn").at_level(Level::WARN)) .exit(expect::span().named("warn").at_level(Level::WARN)) .new_span(expect::span().named("error").at_level(Level::ERROR)) .enter(expect::span().named("error").at_level(Level::ERROR)) .exit(expect::span().named("error").at_level(Level::ERROR)) .only() .run_with_handle(); with_default(subscriber, || { trace(); debug(); info(); warn(); error(); }); handle.assert_finished(); } #[test] fn enum_levels() { #[instrument(level = Level::TRACE)] fn trace() {} #[instrument(level = Level::DEBUG)] fn debug() {} #[instrument(level = tracing::Level::INFO)] fn info() {} #[instrument(level = Level::WARN)] fn warn() {} #[instrument(level = Level::ERROR)] fn error() {} let (subscriber, handle) = subscriber::mock() .new_span(expect::span().named("trace").at_level(Level::TRACE)) .enter(expect::span().named("trace").at_level(Level::TRACE)) .exit(expect::span().named("trace").at_level(Level::TRACE)) .new_span(expect::span().named("debug").at_level(Level::DEBUG)) .enter(expect::span().named("debug").at_level(Level::DEBUG)) .exit(expect::span().named("debug").at_level(Level::DEBUG)) .new_span(expect::span().named("info").at_level(Level::INFO)) .enter(expect::span().named("info").at_level(Level::INFO)) .exit(expect::span().named("info").at_level(Level::INFO)) .new_span(expect::span().named("warn").at_level(Level::WARN)) .enter(expect::span().named("warn").at_level(Level::WARN)) .exit(expect::span().named("warn").at_level(Level::WARN)) .new_span(expect::span().named("error").at_level(Level::ERROR)) .enter(expect::span().named("error").at_level(Level::ERROR)) .exit(expect::span().named("error").at_level(Level::ERROR)) .only() .run_with_handle(); with_default(subscriber, || { trace(); debug(); info(); warn(); error(); }); handle.assert_finished(); } tracing-attributes-0.1.27/tests/names.rs000064400000000000000000000030641046102023000163440ustar 00000000000000use 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(expect::span().named("default_name")) .enter(expect::span().named("default_name")) .exit(expect::span().named("default_name")) .only() .run_with_handle(); with_default(subscriber, || { default_name(); }); handle.assert_finished(); } #[test] fn custom_name_test() { let (subscriber, handle) = subscriber::mock() .new_span(expect::span().named("my_name")) .enter(expect::span().named("my_name")) .exit(expect::span().named("my_name")) .only() .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(expect::span().named("my_other_name")) .enter(expect::span().named("my_other_name")) .exit(expect::span().named("my_other_name")) .only() .run_with_handle(); with_default(subscriber, || { custom_name_no_equals(); }); handle.assert_finished(); } tracing-attributes-0.1.27/tests/parents.rs000064400000000000000000000055011046102023000167130ustar 00000000000000use 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

(parent_span: P) where P: Into>, { } #[test] fn default_parent_test() { let contextual_parent = expect::span().named("contextual_parent"); let child = expect::span().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) .only() .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 = expect::span().named("contextual_parent"); let explicit_parent = expect::span().named("explicit_parent"); let child = expect::span().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) .only() .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(); } tracing-attributes-0.1.27/tests/ret.rs000064400000000000000000000174311046102023000160360ustar 00000000000000use 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 = expect::span().named("ret"); let (subscriber, handle) = subscriber::mock() .new_span(span.clone()) .enter(span.clone()) .event( expect::event() .with_fields(expect::field("return").with_value(&tracing::field::debug(42))) .at_level(Level::INFO), ) .exit(span.clone()) .drop_span(span) .only() .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 = expect::span() .named("ret_with_target") .with_target("my_target"); let (subscriber, handle) = subscriber::mock() .new_span(span.clone()) .enter(span.clone()) .event( expect::event() .with_fields(expect::field("return").with_value(&tracing::field::debug(42))) .at_level(Level::INFO) .with_target("my_target"), ) .exit(span.clone()) .drop_span(span) .only() .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 = expect::span().named("ret_warn"); let (subscriber, handle) = subscriber::mock() .new_span(span.clone()) .enter(span.clone()) .event( expect::event() .with_fields(expect::field("return").with_value(&tracing::field::debug(42))) .at_level(Level::WARN), ) .exit(span.clone()) .drop_span(span) .only() .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 = expect::span().named("ret_mut"); let (subscriber, handle) = subscriber::mock() .new_span(span.clone()) .enter(span.clone()) .event( expect::event() .with_fields(expect::field("a").with_value(&tracing::field::display(2))) .at_level(Level::INFO), ) .event( expect::event() .with_fields(expect::field("return").with_value(&tracing::field::debug(2))) .at_level(Level::INFO), ) .exit(span.clone()) .drop_span(span) .only() .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 = expect::span().named("ret_async"); let (subscriber, handle) = subscriber::mock() .new_span(span.clone()) .enter(span.clone()) .event( expect::event() .with_fields(expect::field("return").with_value(&tracing::field::debug(42))) .at_level(Level::INFO), ) .exit(span.clone()) .enter(span.clone()) .exit(span.clone()) .drop_span(span) .only() .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 = expect::span().named("ret_impl_type"); let (subscriber, handle) = subscriber::mock() .new_span(span.clone()) .enter(span.clone()) .event( expect::event() .with_fields(expect::field("return").with_value(&tracing::field::debug(42))) .at_level(Level::INFO), ) .exit(span.clone()) .drop_span(span) .only() .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 = expect::span().named("ret_display"); let (subscriber, handle) = subscriber::mock() .new_span(span.clone()) .enter(span.clone()) .event( expect::event() .with_fields(expect::field("return").with_value(&tracing::field::display(42))) .at_level(Level::INFO), ) .exit(span.clone()) .drop_span(span) .only() .run_with_handle(); with_default(subscriber, ret_display); handle.assert_finished(); } #[instrument(err, ret)] fn ret_and_err() -> Result { u8::try_from(1234) } #[test] fn test_ret_and_err() { let span = expect::span().named("ret_and_err"); let (subscriber, handle) = subscriber::mock() .new_span(span.clone()) .enter(span.clone()) .event( expect::event() .with_fields( expect::field("error") .with_value(&tracing::field::display(u8::try_from(1234).unwrap_err())) .only(), ) .at_level(Level::ERROR), ) .exit(span.clone()) .drop_span(span) .only() .run_with_handle(); with_default(subscriber, || ret_and_err().ok()); handle.assert_finished(); } #[instrument(err, ret)] fn ret_and_ok() -> Result { u8::try_from(123) } #[test] fn test_ret_and_ok() { let span = expect::span().named("ret_and_ok"); let (subscriber, handle) = subscriber::mock() .new_span(span.clone()) .enter(span.clone()) .event( expect::event() .with_fields( expect::field("return") .with_value(&tracing::field::debug(u8::try_from(123).unwrap())) .only(), ) .at_level(Level::INFO), ) .exit(span.clone()) .drop_span(span) .only() .run_with_handle(); with_default(subscriber, || ret_and_ok().ok()); handle.assert_finished(); } #[instrument(level = "warn", ret(level = "info"))] fn ret_warn_info() -> i32 { 42 } #[test] fn test_warn_info() { let span = expect::span().named("ret_warn_info").at_level(Level::WARN); let (subscriber, handle) = subscriber::mock() .new_span(span.clone()) .enter(span.clone()) .event( expect::event() .with_fields(expect::field("return").with_value(&tracing::field::debug(42))) .at_level(Level::INFO), ) .exit(span.clone()) .drop_span(span) .only() .run_with_handle(); with_default(subscriber, ret_warn_info); handle.assert_finished(); } #[instrument(ret(level = "warn", Debug))] fn ret_dbg_warn() -> i32 { 42 } #[test] fn test_dbg_warn() { let span = expect::span().named("ret_dbg_warn").at_level(Level::INFO); let (subscriber, handle) = subscriber::mock() .new_span(span.clone()) .enter(span.clone()) .event( expect::event() .with_fields(expect::field("return").with_value(&tracing::field::debug(42))) .at_level(Level::WARN), ) .exit(span.clone()) .drop_span(span) .only() .run_with_handle(); with_default(subscriber, ret_dbg_warn); handle.assert_finished(); } tracing-attributes-0.1.27/tests/targets.rs000064400000000000000000000051251046102023000167120ustar 00000000000000use 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( expect::span() .named("default_target") .with_target(module_path!()), ) .enter( expect::span() .named("default_target") .with_target(module_path!()), ) .exit( expect::span() .named("default_target") .with_target(module_path!()), ) .new_span( expect::span() .named("default_target") .with_target(my_mod::MODULE_PATH), ) .enter( expect::span() .named("default_target") .with_target(my_mod::MODULE_PATH), ) .exit( expect::span() .named("default_target") .with_target(my_mod::MODULE_PATH), ) .only() .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( expect::span() .named("custom_target") .with_target("my_target"), ) .enter( expect::span() .named("custom_target") .with_target("my_target"), ) .exit( expect::span() .named("custom_target") .with_target("my_target"), ) .new_span( expect::span() .named("custom_target") .with_target("my_other_target"), ) .enter( expect::span() .named("custom_target") .with_target("my_other_target"), ) .exit( expect::span() .named("custom_target") .with_target("my_other_target"), ) .only() .run_with_handle(); with_default(subscriber, || { custom_target(); my_mod::custom_target(); }); handle.assert_finished(); } tracing-attributes-0.1.27/tests/ui/async_instrument.rs000064400000000000000000000013051046102023000212570ustar 00000000000000#![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); #[tracing::instrument] async fn mismatch_with_opaque() -> Wrapper { "" } #[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() {} tracing-attributes-0.1.27/tests/ui/async_instrument.stderr000064400000000000000000000062121046102023000221400ustar 00000000000000error[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 `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 `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 { | ^^^^^^^ 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 `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 to return this value 44 | | } | |_^ expected `i32`, found `()` tracing-attributes-0.1.27/tests/ui/const_instrument.rs000064400000000000000000000001351046102023000212700ustar 00000000000000#![allow(unreachable_code)] #[tracing::instrument] const fn unit() { "" } fn main() {} tracing-attributes-0.1.27/tests/ui/const_instrument.stderr000064400000000000000000000012031046102023000221440ustar 00000000000000error: macros that expand to items must be delimited with braces or followed by a semicolon --> tests/ui/const_instrument.rs:3:1 | 3 | #[tracing::instrument] | ^^^^^^^^^^^^^^^^^^^^^^ | = note: this error originates in the attribute macro `tracing::instrument` (in Nightly builds, run with -Z macro-backtrace for more info) error: the `#[instrument]` attribute may not be used with `const fn`s --> tests/ui/const_instrument.rs:3:1 | 3 | #[tracing::instrument] | ^^^^^^^^^^^^^^^^^^^^^^ | = note: this error originates in the attribute macro `tracing::instrument` (in Nightly builds, run with -Z macro-backtrace for more info) tracing-attributes-0.1.27/tests/ui.rs000064400000000000000000000005611046102023000156550ustar 00000000000000// 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"); } #[rustversion::stable] #[test] fn const_instrument() { let t = trybuild::TestCases::new(); t.compile_fail("tests/ui/const_instrument.rs"); }