tracing: add support literals for field names (#790)

## Motivation

Some opentracing systems use fields with : in the names, which are not
legal rust identifiers. We could special case :, but then we have
complicated double-nested patterns (repeated idents separated by : and a
nested repeat separated by .), and this may by a case of always being
one-step-behind as more cases turn up; so this patch instead just gets
in front and lets users put in whatever they want: as they are not rust
identifiers, they miss out on some niceness. 

## Solution

This permits : in field names but also potentially anything
stringifiable, which may be overly liberal.

Signed-off-by: Robert Collins <robert.collins@cognite.com>
This commit is contained in:
Robert Collins 2020-07-30 12:36:44 +12:00 committed by GitHub
parent 8195a32718
commit 32cf418c3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 107 additions and 1 deletions

View File

@ -133,7 +133,7 @@ use tracing::{debug, error, info, span, warn, Level};
// the `#[tracing::instrument]` attribute creates and enters a span
// every time the instrumented function is called. The span is named after the
// the function or method. Paramaters passed to the function are recorded as fields.
// the function or method. Parameters passed to the function are recorded as fields.
#[tracing::instrument]
pub fn shave(yak: usize) -> Result<(), Box<dyn Error + 'static>> {
// this creates an event at the DEBUG level with two fields:

View File

@ -10,6 +10,8 @@ This directory contains a collection of examples that demonstrate the use of the
`slog-term`'s `Compact` formatter.
- **tracing-attributes**:
+ `attrs-basic`: A simple example of the `#[instrument]` attribute.
+ `attrs-literal-field-names`: Demonstrates using literal field names rather
than rust tokens..
+ `attrs-args`: An example implementing a simple recursive calculation of
Fibonacci numbers, to demonstrate how the `#[instrument]` attribute can
record function arguments.

View File

@ -0,0 +1,22 @@
#![deny(rust_2018_idioms)]
use tracing::{debug, span, Level};
use tracing_attributes::instrument;
#[instrument]
#[inline]
fn suggest_band() -> String {
debug!("Suggesting a band.");
String::from("Wild Pink")
}
fn main() {
let subscriber = tracing_subscriber::fmt()
.with_env_filter("attrs_literal_field_names=trace")
.finish();
tracing::subscriber::with_default(subscriber, || {
let span = span!(Level::TRACE, "get_band_rec", "guid:x-request-id" = "abcdef");
let _enter = span.enter();
suggest_band();
});
}

View File

@ -287,6 +287,19 @@
//! # }
//!```
//!
//! Fields with names that are not Rust identifiers, or with names that are Rust reserved words,
//! may be created using quoted string literals. However, this may not be used with the local
//! variable shorthand.
//! ```
//! # use tracing::{span, Level};
//! # fn main() {
//! // records an event with fields whose names are not Rust identifiers
//! // - "guid:x-request-id", containing a `:`, with the value "abcdef"
//! // - "type", which is a reserved word, with the value "request"
//! span!(Level::TRACE, "api", "guid:x-request-id" = "abcdef", "type" = "request");
//! # }
//!```
//!
//! The `?` sigil is shorthand that specifies a field should be recorded using
//! its [`fmt::Debug`] implementation:
//! ```

View File

@ -1959,6 +1959,48 @@ macro_rules! valueset {
$next,
)
};
// Handle literal names
(@ { $(,)* $($out:expr),* }, $next:expr, $k:literal = ?$val:expr, $($rest:tt)*) => {
$crate::valueset!(
@ { $($out),*, (&$next, Some(&debug(&$val) as &Value)) },
$next,
$($rest)*
)
};
(@ { $(,)* $($out:expr),* }, $next:expr, $k:literal = %$val:expr, $($rest:tt)*) => {
$crate::valueset!(
@ { $($out),*, (&$next, Some(&display(&$val) as &Value)) },
$next,
$($rest)*
)
};
(@ { $(,)* $($out:expr),* }, $next:expr, $k:literal = $val:expr, $($rest:tt)*) => {
$crate::valueset!(
@ { $($out),*, (&$next, Some(&$val as &Value)) },
$next,
$($rest)*
)
};
(@ { $(,)* $($out:expr),* }, $next:expr, $k:literal = ?$val:expr) => {
$crate::valueset!(
@ { $($out),*, (&$next, Some(&debug(&$val) as &Value)) },
$next,
)
};
(@ { $(,)* $($out:expr),* }, $next:expr, $k:literal = %$val:expr) => {
$crate::valueset!(
@ { $($out),*, (&$next, Some(&display(&$val) as &Value)) },
$next,
)
};
(@ { $(,)* $($out:expr),* }, $next:expr, $k:literal = $val:expr) => {
$crate::valueset!(
@ { $($out),*, (&$next, Some(&$val as &Value)) },
$next,
)
};
// Remainder is unparseable, but exists --- must be format args!
(@ { $(,)* $($out:expr),* }, $next:expr, $($rest:tt)+) => {
$crate::valueset!(@ { (&$next, Some(&format_args!($($rest)+) as &Value)), $($out),* }, $next, )
@ -2017,6 +2059,17 @@ macro_rules! fieldset {
$crate::fieldset!(@ { $($out),*, $crate::__tracing_stringify!($($k).+) } $($rest)*)
};
// Handle literal names
(@ { $(,)* $($out:expr),* } $k:literal = ?$val:expr, $($rest:tt)*) => {
$crate::fieldset!(@ { $($out),*, $k } $($rest)*)
};
(@ { $(,)* $($out:expr),* } $k:literal = %$val:expr, $($rest:tt)*) => {
$crate::fieldset!(@ { $($out),*, $k } $($rest)*)
};
(@ { $(,)* $($out:expr),* } $k:literal = $val:expr, $($rest:tt)*) => {
$crate::fieldset!(@ { $($out),*, $k } $($rest)*)
};
// Remainder is unparseable, but exists --- must be format args!
(@ { $(,)* $($out:expr),* } $($rest:tt)+) => {
$crate::fieldset!(@ { "message", $($out),*, })

View File

@ -262,6 +262,22 @@ fn error_span_with_parent() {
error_span!(parent: &p, "bar",);
}
#[test]
fn span_with_non_rust_symbol() {
span!(Level::TRACE, "non-rust", "guid:x-request-id" = ?"abcdef", "more {}", 42);
span!(Level::TRACE, "non-rust", "guid:x-request-id" = %"abcdef", "more {}", 51);
span!(
Level::TRACE,
"non-rust",
"guid:x-request-id" = "abcdef",
"more {}",
60
);
span!(Level::TRACE, "non-rust", "guid:x-request-id" = ?"abcdef");
span!(Level::TRACE, "non-rust", "guid:x-request-id" = %"abcdef");
span!(Level::TRACE, "non-rust", "guid:x-request-id" = "abcdef");
}
#[test]
fn event() {
event!(Level::DEBUG, foo = ?3, bar.baz = %2, quux = false);