diff --git a/tracing/src/lib.rs b/tracing/src/lib.rs index 0b403f3b2..94957f516 100644 --- a/tracing/src/lib.rs +++ b/tracing/src/lib.rs @@ -1002,12 +1002,12 @@ pub mod span; pub mod __macro_support { pub use crate::callsite::{Callsite, Registration}; use crate::{collect::Interest, Metadata}; - use core::fmt; use core::sync::atomic::{AtomicU8, Ordering}; + use core::{fmt, mem}; // Re-export the `core` functions that are used in macros. This allows // a crate to be named `core` and avoid name clashes. // See here: https://github.com/tokio-rs/tracing/issues/2761 - pub use core::{concat, file, format_args, iter::Iterator, line, option::Option}; + pub use core::{concat, file, format_args, iter::Iterator, line, option::Option, stringify}; /// Callsite implementation used by macro-generated code. /// @@ -1195,6 +1195,66 @@ pub mod __macro_support { .finish() } } + + /// Implementation detail used for constructing FieldSet names from raw + /// identifiers. In `info!(..., r#type = "...")` the macro would end up + /// constructing a name equivalent to `FieldName(*b"type")`. + pub struct FieldName([u8; N]); + + impl FieldName { + /// Convert `"prefix.r#keyword.suffix"` to `b"prefix.keyword.suffix"`. + pub const fn new(input: &str) -> Self { + let input = input.as_bytes(); + let mut output = [0u8; N]; + let mut read = 0; + let mut write = 0; + while read < input.len() { + if read + 1 < input.len() && input[read] == b'r' && input[read + 1] == b'#' { + read += 2; + } + output[write] = input[read]; + read += 1; + write += 1; + } + assert!(write == N); + Self(output) + } + + pub const fn as_str(&self) -> &str { + // SAFETY: Because of the private visibility of self.0, it must have + // been computed by Self::new. So these bytes are all of the bytes + // of some original valid UTF-8 string, but with "r#" substrings + // removed, which cannot have produced invalid UTF-8. + unsafe { mem::transmute(self.0.as_slice()) } + } + } + + impl FieldName<0> { + /// For `"prefix.r#keyword.suffix"` compute `"prefix.keyword.suffix".len()`. + pub const fn len(input: &str) -> usize { + // Count occurrences of "r#" + let mut raw = 0; + + let mut i = 0; + while i < input.len() { + if input.as_bytes()[i] == b'#' { + raw += 1; + } + i += 1; + } + + input.len() - 2 * raw + } + } + + impl fmt::Debug for FieldName { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter + .debug_tuple("FieldName") + .field(&self.as_str()) + .finish() + } + } } #[cfg(feature = "log")] diff --git a/tracing/src/macros.rs b/tracing/src/macros.rs index b0791d63f..c8284d858 100644 --- a/tracing/src/macros.rs +++ b/tracing/src/macros.rs @@ -3069,9 +3069,12 @@ macro_rules! level_to_log { #[doc(hidden)] #[macro_export] macro_rules! __tracing_stringify { - ($($t:tt)*) => { - stringify!($($t)*) - }; + ($($k:ident).+) => {{ + const NAME: $crate::__macro_support::FieldName<{ + $crate::__macro_support::FieldName::len($crate::__macro_support::stringify!($($k).+)) + }> = $crate::__macro_support::FieldName::new($crate::__macro_support::stringify!($($k).+)); + NAME.as_str() + }}; } #[cfg(not(feature = "log"))] diff --git a/tracing/tests/event.rs b/tracing/tests/event.rs index 6b15a8d98..7c1d2a934 100644 --- a/tracing/tests/event.rs +++ b/tracing/tests/event.rs @@ -514,7 +514,7 @@ fn keyword_ident_in_field_name() { #[test] fn raw_ident_in_field_name() { let (collector, handle) = collector::mock() - .event(expect::event().with_fields(expect::field("this.r#type").with_value(&"Value"))) + .event(expect::event().with_fields(expect::field("this.type").with_value(&"Value"))) .only() .run_with_handle();