Skip to content

Commit c90e5c8

Browse files
maxburkehawkw
authored andcommitted
core: add support for visiting floating point values (#1507)
## Motivation Tracing is a really useful framework but a lack of floating point value support for the core visitors means these get coerced unnecessarily to strings. ## Solution This change adds support for floating point (`f64`) visitors.
1 parent d394efa commit c90e5c8

File tree

9 files changed

+192
-7
lines changed

9 files changed

+192
-7
lines changed

tracing-core/src/field.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
//! will contain any fields attached to each event.
1717
//!
1818
//! `tracing` represents values as either one of a set of Rust primitives
19-
//! (`i64`, `u64`, `bool`, and `&str`) or using a `fmt::Display` or `fmt::Debug`
20-
//! implementation. `Subscriber`s are provided these primitive value types as
21-
//! `dyn Value` trait objects.
19+
//! (`i64`, `u64`, `f64`, `bool`, and `&str`) or using a `fmt::Display` or
20+
//! `fmt::Debug` implementation. `Subscriber`s are provided these primitive
21+
//! value types as `dyn Value` trait objects.
2222
//!
2323
//! These trait objects can be formatted using `fmt::Debug`, but may also be
2424
//! recorded as typed data by calling the [`Value::record`] method on these
@@ -184,6 +184,11 @@ pub struct Iter {
184184
/// [`Event`]: ../event/struct.Event.html
185185
/// [`ValueSet`]: struct.ValueSet.html
186186
pub trait Visit {
187+
/// Visit a double-precision floating point value.
188+
fn record_f64(&mut self, field: &Field, value: f64) {
189+
self.record_debug(field, &value)
190+
}
191+
187192
/// Visit a signed 64-bit integer value.
188193
fn record_i64(&mut self, field: &Field, value: i64) {
189194
self.record_debug(field, &value)
@@ -335,6 +340,12 @@ macro_rules! ty_to_nonzero {
335340
}
336341

337342
macro_rules! impl_one_value {
343+
(f32, $op:expr, $record:ident) => {
344+
impl_one_value!(normal, f32, $op, $record);
345+
};
346+
(f64, $op:expr, $record:ident) => {
347+
impl_one_value!(normal, f64, $op, $record);
348+
};
338349
(bool, $op:expr, $record:ident) => {
339350
impl_one_value!(normal, bool, $op, $record);
340351
};
@@ -387,7 +398,8 @@ impl_values! {
387398
record_u64(usize, u32, u16, u8 as u64),
388399
record_i64(i64),
389400
record_i64(isize, i32, i16, i8 as i64),
390-
record_bool(bool)
401+
record_bool(bool),
402+
record_f64(f64, f32 as f64)
391403
}
392404

393405
impl<T: crate::sealed::Sealed> crate::sealed::Sealed for Wrapping<T> {}

tracing-serde/src/lib.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,12 @@ where
384384
}
385385
}
386386

387+
fn record_f64(&mut self, field: &Field, value: f64) {
388+
if self.state.is_ok() {
389+
self.state = self.serializer.serialize_entry(field.name(), &value)
390+
}
391+
}
392+
387393
fn record_str(&mut self, field: &Field, value: &str) {
388394
if self.state.is_ok() {
389395
self.state = self.serializer.serialize_entry(field.name(), &value)
@@ -430,6 +436,12 @@ where
430436
}
431437
}
432438

439+
fn record_f64(&mut self, field: &Field, value: f64) {
440+
if self.state.is_ok() {
441+
self.state = self.serializer.serialize_field(field.name(), &value)
442+
}
443+
}
444+
433445
fn record_str(&mut self, field: &Field, value: &str) {
434446
if self.state.is_ok() {
435447
self.state = self.serializer.serialize_field(field.name(), &value)

tracing-subscriber/src/field/debug.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ impl<V> Visit for Alt<V>
3838
where
3939
V: Visit,
4040
{
41+
#[inline]
42+
fn record_f64(&mut self, field: &Field, value: f64) {
43+
self.0.record_f64(field, value)
44+
}
45+
4146
#[inline]
4247
fn record_i64(&mut self, field: &Field, value: i64) {
4348
self.0.record_i64(field, value)

tracing-subscriber/src/field/display.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ impl<V> Visit for Messages<V>
4040
where
4141
V: Visit,
4242
{
43+
#[inline]
44+
fn record_f64(&mut self, field: &Field, value: f64) {
45+
self.0.record_f64(field, value)
46+
}
47+
4348
#[inline]
4449
fn record_i64(&mut self, field: &Field, value: i64) {
4550
self.0.record_i64(field, value)

tracing-subscriber/src/filter/env/field.rs

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,77 @@ pub(crate) struct MatchVisitor<'a> {
3636
inner: &'a SpanMatch,
3737
}
3838

39-
#[derive(Debug, Clone, PartialOrd, Ord, Eq, PartialEq)]
39+
#[derive(Debug, Clone)]
4040
pub(crate) enum ValueMatch {
4141
Bool(bool),
42+
F64(f64),
4243
U64(u64),
4344
I64(i64),
45+
NaN,
4446
Pat(Box<MatchPattern>),
4547
}
4648

49+
impl Eq for ValueMatch {}
50+
51+
impl PartialEq for ValueMatch {
52+
fn eq(&self, other: &Self) -> bool {
53+
use ValueMatch::*;
54+
match (self, other) {
55+
(Bool(a), Bool(b)) => a.eq(b),
56+
(F64(a), F64(b)) => {
57+
debug_assert!(!a.is_nan());
58+
debug_assert!(!b.is_nan());
59+
60+
a.eq(b)
61+
}
62+
(U64(a), U64(b)) => a.eq(b),
63+
(I64(a), I64(b)) => a.eq(b),
64+
(NaN, NaN) => true,
65+
(Pat(a), Pat(b)) => a.eq(b),
66+
_ => false,
67+
}
68+
}
69+
}
70+
71+
impl Ord for ValueMatch {
72+
fn cmp(&self, other: &Self) -> Ordering {
73+
use ValueMatch::*;
74+
match (self, other) {
75+
(Bool(this), Bool(that)) => this.cmp(that),
76+
(Bool(_), _) => Ordering::Less,
77+
78+
(F64(this), F64(that)) => this
79+
.partial_cmp(that)
80+
.expect("`ValueMatch::F64` may not contain `NaN` values"),
81+
(F64(_), Bool(_)) => Ordering::Greater,
82+
(F64(_), _) => Ordering::Less,
83+
84+
(NaN, NaN) => Ordering::Equal,
85+
(NaN, Bool(_)) | (NaN, F64(_)) => Ordering::Greater,
86+
(NaN, _) => Ordering::Less,
87+
88+
(U64(this), U64(that)) => this.cmp(that),
89+
(U64(_), Bool(_)) | (U64(_), F64(_)) | (U64(_), NaN) => Ordering::Greater,
90+
(U64(_), _) => Ordering::Less,
91+
92+
(I64(this), I64(that)) => this.cmp(that),
93+
(I64(_), Bool(_)) | (I64(_), F64(_)) | (I64(_), NaN) | (I64(_), U64(_)) => {
94+
Ordering::Greater
95+
}
96+
(I64(_), _) => Ordering::Less,
97+
98+
(Pat(this), Pat(that)) => this.cmp(that),
99+
(Pat(_), _) => Ordering::Greater,
100+
}
101+
}
102+
}
103+
104+
impl PartialOrd for ValueMatch {
105+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
106+
Some(self.cmp(other))
107+
}
108+
}
109+
47110
#[derive(Debug, Clone)]
48111
pub(crate) struct MatchPattern {
49112
pub(crate) matcher: Pattern,
@@ -127,13 +190,22 @@ impl PartialOrd for Match {
127190

128191
// === impl ValueMatch ===
129192

193+
fn value_match_f64(v: f64) -> ValueMatch {
194+
if v.is_nan() {
195+
ValueMatch::NaN
196+
} else {
197+
ValueMatch::F64(v)
198+
}
199+
}
200+
130201
impl FromStr for ValueMatch {
131202
type Err = matchers::Error;
132203
fn from_str(s: &str) -> Result<Self, Self::Err> {
133204
s.parse::<bool>()
134205
.map(ValueMatch::Bool)
135206
.or_else(|_| s.parse::<u64>().map(ValueMatch::U64))
136207
.or_else(|_| s.parse::<i64>().map(ValueMatch::I64))
208+
.or_else(|_| s.parse::<f64>().map(value_match_f64))
137209
.or_else(|_| {
138210
s.parse::<MatchPattern>()
139211
.map(|p| ValueMatch::Pat(Box::new(p)))
@@ -145,6 +217,8 @@ impl fmt::Display for ValueMatch {
145217
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146218
match self {
147219
ValueMatch::Bool(ref inner) => fmt::Display::fmt(inner, f),
220+
ValueMatch::F64(ref inner) => fmt::Display::fmt(inner, f),
221+
ValueMatch::NaN => fmt::Display::fmt(&f64::NAN, f),
148222
ValueMatch::I64(ref inner) => fmt::Display::fmt(inner, f),
149223
ValueMatch::U64(ref inner) => fmt::Display::fmt(inner, f),
150224
ValueMatch::Pat(ref inner) => fmt::Display::fmt(inner, f),
@@ -275,6 +349,18 @@ impl SpanMatch {
275349
}
276350

277351
impl<'a> Visit for MatchVisitor<'a> {
352+
fn record_f64(&mut self, field: &Field, value: f64) {
353+
match self.inner.fields.get(field) {
354+
Some((ValueMatch::NaN, ref matched)) if value.is_nan() => {
355+
matched.store(true, Release);
356+
}
357+
Some((ValueMatch::F64(ref e), ref matched)) if (value - *e).abs() < f64::EPSILON => {
358+
matched.store(true, Release);
359+
}
360+
_ => {}
361+
}
362+
}
363+
278364
fn record_i64(&mut self, field: &Field, value: i64) {
279365
use std::convert::TryInto;
280366

tracing-subscriber/src/fmt/format/json.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,12 @@ impl<'a> crate::field::VisitOutput<fmt::Result> for JsonVisitor<'a> {
431431
}
432432

433433
impl<'a> field::Visit for JsonVisitor<'a> {
434+
/// Visit a double precision floating point value.
435+
fn record_f64(&mut self, field: &Field, value: f64) {
436+
self.values
437+
.insert(field.name(), serde_json::Value::from(value));
438+
}
439+
434440
/// Visit a signed 64-bit integer value.
435441
fn record_i64(&mut self, field: &Field, value: i64) {
436442
self.values

tracing/tests/event.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ fn one_with_everything() {
145145
)))
146146
.and(field::mock("foo").with_value(&666))
147147
.and(field::mock("bar").with_value(&false))
148+
.and(field::mock("like_a_butterfly").with_value(&42.0))
148149
.only(),
149150
)
150151
.at_level(Level::ERROR)
@@ -157,7 +158,7 @@ fn one_with_everything() {
157158
event!(
158159
target: "whatever",
159160
Level::ERROR,
160-
{ foo = 666, bar = false },
161+
{ foo = 666, bar = false, like_a_butterfly = 42.0 },
161162
"{:#x} make me one with{what:.>20}", 4_277_009_102u64, what = "everything"
162163
);
163164
});

tracing/tests/span.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,28 @@ fn move_field_out_of_struct() {
458458
handle.assert_finished();
459459
}
460460

461+
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
462+
#[test]
463+
fn float_values() {
464+
let (collector, handle) = collector::mock()
465+
.new_span(
466+
span::mock().named("foo").with_field(
467+
field::mock("x")
468+
.with_value(&3.234)
469+
.and(field::mock("y").with_value(&-1.223))
470+
.only(),
471+
),
472+
)
473+
.run_with_handle();
474+
475+
with_default(collector, || {
476+
let foo = span!(Level::TRACE, "foo", x = 3.234, y = -1.223);
477+
foo.in_scope(|| {});
478+
});
479+
480+
handle.assert_finished();
481+
}
482+
461483
// TODO(#1138): determine a new syntax for uninitialized span fields, and
462484
// re-enable these.
463485
/*

tracing/tests/support/field.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ pub struct MockField {
1919
value: MockValue,
2020
}
2121

22-
#[derive(Debug, Eq, PartialEq)]
22+
#[derive(Debug)]
2323
pub enum MockValue {
24+
F64(f64),
2425
I64(i64),
2526
U64(u64),
2627
Bool(bool),
@@ -29,6 +30,31 @@ pub enum MockValue {
2930
Any,
3031
}
3132

33+
impl Eq for MockValue {}
34+
35+
impl PartialEq for MockValue {
36+
fn eq(&self, other: &Self) -> bool {
37+
use MockValue::*;
38+
39+
match (self, other) {
40+
(F64(a), F64(b)) => {
41+
debug_assert!(!a.is_nan());
42+
debug_assert!(!b.is_nan());
43+
44+
a.eq(b)
45+
}
46+
(I64(a), I64(b)) => a.eq(b),
47+
(U64(a), U64(b)) => a.eq(b),
48+
(Bool(a), Bool(b)) => a.eq(b),
49+
(Str(a), Str(b)) => a.eq(b),
50+
(Debug(a), Debug(b)) => a.eq(b),
51+
(Any, _) => true,
52+
(_, Any) => true,
53+
_ => false,
54+
}
55+
}
56+
}
57+
3258
pub fn mock<K>(name: K) -> MockField
3359
where
3460
String: From<K>,
@@ -120,6 +146,7 @@ impl Expect {
120146
impl fmt::Display for MockValue {
121147
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122148
match self {
149+
MockValue::F64(v) => write!(f, "f64 = {:?}", v),
123150
MockValue::I64(v) => write!(f, "i64 = {:?}", v),
124151
MockValue::U64(v) => write!(f, "u64 = {:?}", v),
125152
MockValue::Bool(v) => write!(f, "bool = {:?}", v),
@@ -136,6 +163,11 @@ pub struct CheckVisitor<'a> {
136163
}
137164

138165
impl<'a> Visit for CheckVisitor<'a> {
166+
fn record_f64(&mut self, field: &Field, value: f64) {
167+
self.expect
168+
.compare_or_panic(field.name(), &value, &self.ctx[..])
169+
}
170+
139171
fn record_i64(&mut self, field: &Field, value: i64) {
140172
self.expect
141173
.compare_or_panic(field.name(), &value, &self.ctx[..])
@@ -180,6 +212,10 @@ impl<'a> From<&'a dyn Value> for MockValue {
180212
}
181213

182214
impl Visit for MockValueBuilder {
215+
fn record_f64(&mut self, _: &Field, value: f64) {
216+
self.value = Some(MockValue::F64(value));
217+
}
218+
183219
fn record_i64(&mut self, _: &Field, value: i64) {
184220
self.value = Some(MockValue::I64(value));
185221
}

0 commit comments

Comments
 (0)