Eliza Weisman b2c53987d9
trace: Add shorthand for field::display and field::debug (#1088)
## 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>
2019-05-21 10:31:48 -07:00

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();
}