tracing: add record_all! macro for recording multiple values in one call (#3227)

## Motivation

Currently, Span.record_all() is part of the public API and accepts
ValueSet as a parameter. However, constructing a ValueSet is both
verbose and undocumented, making it not so practical.

## Solution

To make recording multiple values easier, we introduce a new macro:
record_all!, which wraps the Span.record_all() function.
As we don't intend anyone to call Span.record_all() directly, we hide
it from the documentation. We reference the new macro from Span.record()
doc comment instead.

The new record_all! macro supports optional formatting sigils % and ?,
ensuring a consistent DevEx with the other value-recording macros.

Co-authored-by: Hayden Stainsby <hds@caffeineconcepts.com>
This commit is contained in:
Matilda Smeds 2025-03-27 12:16:29 +01:00 committed by Hayden Stainsby
parent 37caa910ac
commit 0a5dc8e950
3 changed files with 82 additions and 1 deletions

View File

@ -130,6 +130,36 @@ macro_rules! span {
};
}
/// Records multiple values on a span in a single call. As with recording
/// individual values, all fields must be declared when the span is created.
///
/// This macro supports two optional sigils:
/// - `%` uses the Display implementation.
/// - `?` uses the Debug implementation.
///
/// For more details, see the [top-level documentation][lib].
///
/// [lib]: tracing/#recording-fields
///
/// # Examples
///
/// ```
/// # use tracing::{field, info_span, record_all};
/// let span = info_span!("my span", field1 = field::Empty, field2 = field::Empty, field3 = field::Empty).entered();
/// record_all!(span, field1 = ?"1", field2 = %"2", field3 = 3);
/// ```
#[macro_export]
macro_rules! record_all {
($span:expr, $($fields:tt)*) => {
if let Some(meta) = $span.metadata() {
$span.record_all(&$crate::valueset!(
meta.fields(),
$($fields)*
));
}
};
}
/// Constructs a span at the trace level.
///
/// [Fields] and [attributes] are set using the same syntax as the [`span!`]

View File

@ -1182,6 +1182,11 @@ impl Span {
/// span.record("parting", "you will be remembered");
/// ```
///
/// <div class="example-wrap" style="display:inline-block">
/// <pre class="ignore" style="white-space:normal;font:inherit;">
/// **Note**: To record several values in just one call, see the [`record_all!`](crate::record_all!) macro.
/// </pre></div>
///
/// [`field::Empty`]: super::field::Empty
/// [`Metadata`]: super::Metadata
pub fn record<Q: field::AsField + ?Sized, V: field::Value>(
@ -1203,6 +1208,7 @@ impl Span {
}
/// Records all the fields in the provided `ValueSet`.
#[doc(hidden)]
pub fn record_all(&self, values: &field::ValueSet<'_>) -> &Self {
let record = Record::new(values);
if let Some(ref inner) = self.inner {

View File

@ -7,7 +7,8 @@ use std::thread;
use tracing::{
error_span,
field::{debug, display},
field::{debug, display, Empty},
record_all,
subscriber::with_default,
Level, Span,
};
@ -612,6 +613,50 @@ fn record_new_values_for_fields() {
handle.assert_finished();
}
/// Tests record_all! macro, which is a wrapper for Span.record_all().
/// Placed here instead of tests/macros.rs, because it uses tracing_mock, which
/// requires std lib. Other macro tests exclude std lib to verify the macros do
/// not dependend on it.
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[test]
fn record_all_macro_records_new_values_for_fields() {
let (subscriber, handle) = subscriber::mock()
.new_span(
expect::span()
.named("foo")
.with_fields(expect::field("bar")),
)
.record(
expect::span().named("foo"),
expect::field("bar")
.with_value(&5)
.and(expect::field("baz").with_value(&"BAZ"))
.and(expect::field("qux").with_value(&display("qux")))
.and(expect::field("quux").with_value(&debug("QuuX")))
.only(),
)
.enter(expect::span().named("foo"))
.exit(expect::span().named("foo"))
.drop_span(expect::span().named("foo"))
.only()
.run_with_handle();
with_default(subscriber, || {
let span = tracing::span!(
Level::TRACE,
"foo",
bar = 1,
baz = 2,
qux = Empty,
quux = Empty
);
record_all!(span, bar = 5, baz = "BAZ", qux = %"qux", quux = ?"QuuX");
span.in_scope(|| {})
});
handle.assert_finished();
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[test]
fn new_span_with_target_and_log_level() {