mirror of
https://github.com/tokio-rs/tokio.git
synced 2025-09-28 12:10:37 +00:00

## Motivation In `tokio-trace`, field values may be recorded as either a subset of Rust primitive types or as `fmt::Display` and `fmt::Debug` implementations. Currently, `tokio-trace` provides the `field::display` and `field::debug` functions which wrap a type with a type that implements `Value` using the wrapped type's `fmt::Display` or `fmt::Debug` implementation. However, importing and using these functions adds unnecessary boilerplate. In #1081, @jonhoo suggested adding shorthand syntax to the macros, similar to that used by the `slog` crate, as a solution for the wordiness of the current API. ## Solution This branch adds `?` and `%` sigils to field values in the span and event macros, which expand to the `field::debug` and `field::display` wrappers, respectively. The shorthand sigils may be used in any position where the macros take a field value. For example: ```rust trace_span!("foo", my_field = ?something, ...); // shorthand for `debug` info!(foo = %value, bar = false, ...) // shorthand for `display` ``` Adding this shorthand required a fairly large change to how field key-value pairs are handled by the macros --- since `%foo` and `%foo` are not valid Rust expressions, we can no longer match repeated `$ident = $expr` patterns, and must now match field lists as repeated token trees. The inner helper macros for constructing `FieldSet`s and `ValueSet`s have to parse the token trees recursively. This added a decent chunk of complexity, but fortunately we have a large number of compile tests for the macros and I'm quite confident that all existing invocations will still work. Closes #1081 Signed-off-by: Eliza Weisman <eliza@buoyant.io>
252 lines
6.5 KiB
Rust
252 lines
6.5 KiB
Rust
#[macro_use]
|
|
extern crate tokio_trace;
|
|
mod support;
|
|
|
|
use self::support::*;
|
|
|
|
use tokio_trace::{
|
|
field::{debug, display},
|
|
subscriber::with_default,
|
|
Level,
|
|
};
|
|
|
|
#[test]
|
|
fn event_without_message() {
|
|
let (subscriber, handle) = subscriber::mock()
|
|
.event(
|
|
event::mock().with_fields(
|
|
field::mock("answer")
|
|
.with_value(&42)
|
|
.and(
|
|
field::mock("to_question")
|
|
.with_value(&"life, the universe, and everything"),
|
|
)
|
|
.only(),
|
|
),
|
|
)
|
|
.done()
|
|
.run_with_handle();
|
|
|
|
with_default(subscriber, || {
|
|
info!(
|
|
answer = 42,
|
|
to_question = "life, the universe, and everything"
|
|
);
|
|
});
|
|
|
|
handle.assert_finished();
|
|
}
|
|
|
|
#[test]
|
|
fn event_with_message() {
|
|
let (subscriber, handle) = subscriber::mock()
|
|
.event(event::mock().with_fields(field::mock("message").with_value(
|
|
&tokio_trace::field::debug(format_args!(
|
|
"hello from my event! yak shaved = {:?}",
|
|
true
|
|
)),
|
|
)))
|
|
.done()
|
|
.run_with_handle();
|
|
|
|
with_default(subscriber, || {
|
|
debug!("hello from my event! yak shaved = {:?}", true);
|
|
});
|
|
|
|
handle.assert_finished();
|
|
}
|
|
|
|
#[test]
|
|
fn one_with_everything() {
|
|
let (subscriber, handle) = subscriber::mock()
|
|
.event(
|
|
event::mock()
|
|
.with_fields(
|
|
field::mock("message")
|
|
.with_value(&tokio_trace::field::debug(format_args!(
|
|
"{:#x} make me one with{what:.>20}",
|
|
4277009102u64,
|
|
what = "everything"
|
|
)))
|
|
.and(field::mock("foo").with_value(&666))
|
|
.and(field::mock("bar").with_value(&false))
|
|
.only(),
|
|
)
|
|
.at_level(Level::ERROR)
|
|
.with_target("whatever"),
|
|
)
|
|
.done()
|
|
.run_with_handle();
|
|
|
|
with_default(subscriber, || {
|
|
event!(
|
|
target: "whatever",
|
|
Level::ERROR,
|
|
{ foo = 666, bar = false },
|
|
"{:#x} make me one with{what:.>20}", 4277009102u64, what = "everything"
|
|
);
|
|
});
|
|
|
|
handle.assert_finished();
|
|
}
|
|
|
|
#[test]
|
|
fn moved_field() {
|
|
let (subscriber, handle) = subscriber::mock()
|
|
.event(
|
|
event::mock().with_fields(
|
|
field::mock("foo")
|
|
.with_value(&display("hello from my event"))
|
|
.only(),
|
|
),
|
|
)
|
|
.done()
|
|
.run_with_handle();
|
|
with_default(subscriber, || {
|
|
let from = "my event";
|
|
event!(Level::INFO, foo = display(format!("hello from {}", from)))
|
|
});
|
|
|
|
handle.assert_finished();
|
|
}
|
|
|
|
#[test]
|
|
fn dotted_field_name() {
|
|
let (subscriber, handle) = subscriber::mock()
|
|
.event(
|
|
event::mock().with_fields(
|
|
field::mock("foo.bar")
|
|
.with_value(&true)
|
|
.and(field::mock("foo.baz").with_value(&false))
|
|
.only(),
|
|
),
|
|
)
|
|
.done()
|
|
.run_with_handle();
|
|
with_default(subscriber, || {
|
|
event!(Level::INFO, foo.bar = true, foo.baz = false);
|
|
});
|
|
|
|
handle.assert_finished();
|
|
}
|
|
|
|
#[test]
|
|
fn borrowed_field() {
|
|
let (subscriber, handle) = subscriber::mock()
|
|
.event(
|
|
event::mock().with_fields(
|
|
field::mock("foo")
|
|
.with_value(&display("hello from my event"))
|
|
.only(),
|
|
),
|
|
)
|
|
.done()
|
|
.run_with_handle();
|
|
with_default(subscriber, || {
|
|
let from = "my event";
|
|
let mut message = format!("hello from {}", from);
|
|
event!(Level::INFO, foo = display(&message));
|
|
message.push_str(", which happened!");
|
|
});
|
|
|
|
handle.assert_finished();
|
|
}
|
|
|
|
#[test]
|
|
// If emitting log instrumentation, this gets moved anyway, breaking the test.
|
|
#[cfg(not(feature = "log"))]
|
|
fn move_field_out_of_struct() {
|
|
use tokio_trace::field::debug;
|
|
|
|
#[derive(Debug)]
|
|
struct Position {
|
|
x: f32,
|
|
y: f32,
|
|
}
|
|
|
|
let pos = Position {
|
|
x: 3.234,
|
|
y: -1.223,
|
|
};
|
|
let (subscriber, handle) = subscriber::mock()
|
|
.event(
|
|
event::mock().with_fields(
|
|
field::mock("x")
|
|
.with_value(&debug(3.234))
|
|
.and(field::mock("y").with_value(&debug(-1.223)))
|
|
.only(),
|
|
),
|
|
)
|
|
.event(event::mock().with_fields(field::mock("position").with_value(&debug(&pos))))
|
|
.done()
|
|
.run_with_handle();
|
|
|
|
with_default(subscriber, || {
|
|
let pos = Position {
|
|
x: 3.234,
|
|
y: -1.223,
|
|
};
|
|
debug!(x = debug(pos.x), y = debug(pos.y));
|
|
debug!(target: "app_events", { position = debug(pos) }, "New position");
|
|
});
|
|
handle.assert_finished();
|
|
}
|
|
|
|
#[test]
|
|
fn display_shorthand() {
|
|
let (subscriber, handle) = subscriber::mock()
|
|
.event(
|
|
event::mock().with_fields(
|
|
field::mock("my_field")
|
|
.with_value(&display("hello world"))
|
|
.only(),
|
|
),
|
|
)
|
|
.done()
|
|
.run_with_handle();
|
|
with_default(subscriber, || {
|
|
event!(Level::TRACE, my_field = %"hello world");
|
|
});
|
|
|
|
handle.assert_finished();
|
|
}
|
|
|
|
#[test]
|
|
fn debug_shorthand() {
|
|
let (subscriber, handle) = subscriber::mock()
|
|
.event(
|
|
event::mock().with_fields(
|
|
field::mock("my_field")
|
|
.with_value(&debug("hello world"))
|
|
.only(),
|
|
),
|
|
)
|
|
.done()
|
|
.run_with_handle();
|
|
with_default(subscriber, || {
|
|
event!(Level::TRACE, my_field = ?"hello world");
|
|
});
|
|
|
|
handle.assert_finished();
|
|
}
|
|
|
|
#[test]
|
|
fn both_shorthands() {
|
|
let (subscriber, handle) = subscriber::mock()
|
|
.event(
|
|
event::mock().with_fields(
|
|
field::mock("display_field")
|
|
.with_value(&display("hello world"))
|
|
.and(field::mock("debug_field").with_value(&debug("hello world")))
|
|
.only(),
|
|
),
|
|
)
|
|
.done()
|
|
.run_with_handle();
|
|
with_default(subscriber, || {
|
|
event!(Level::TRACE, display_field = %"hello world", debug_field = ?"hello world");
|
|
});
|
|
|
|
handle.assert_finished();
|
|
}
|