Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changelog.d/grok_keyvalue_nan.fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
The `keyvalue` filter in `parse_grok` and `parse_groks` no longer panics on unquoted `NaN` values
such as `ratio=NaN`. These values are now kept as strings.

authors: tessneau
37 changes: 35 additions & 2 deletions src/datadog/grok/filters/keyvalue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ fn parse_string(input: &str) -> SResult<'_, Value> {
}

fn parse_number(input: &str) -> SResult<'_, Value> {
map_res(terminated(double, eof), |v| {
map_res(terminated(double, eof), |v: f64| {
// can be safely converted to Integer without precision loss
if let Some(integer) = super::super::grok_filter::f64_to_i64_if_integral(v) {
// Check if it is a valid octal number(start with 0) - keep parsed as a decimal though.
Expand All @@ -286,7 +286,11 @@ fn parse_number(input: &str) -> SResult<'_, Value> {
Ok(Value::Integer(integer))
}
} else {
Ok(Value::Float(NotNan::new(v).expect("not a float")))
// `double` also accepts `nan`, which can't be represented as a `Value::Float`. Reject it
// so it's parsed as a string instead.
NotNan::new(v)
.map(Value::Float)
.map_err(|_| nom::Err::Error((input, nom::error::ErrorKind::Float)))
}
})
.parse(input)
Expand Down Expand Up @@ -363,4 +367,33 @@ mod tests {
parse_value(r"1.2", DEFAULT_QUOTES)
);
}

#[test]
fn test_parse_value_nan_as_string() {
for input in ["NaN", "nan", "NAN"] {
assert_eq!(
Ok(("", Value::from(input))),
parse_value(input, DEFAULT_QUOTES),
"{input} should be parsed as a string"
);
}
}

#[test]
fn test_parse_value_infinity_as_float() {
let inf = Value::Float(NotNan::new(f64::INFINITY).expect("infinity is not NaN"));
let neg_inf = Value::Float(NotNan::new(f64::NEG_INFINITY).expect("infinity is not NaN"));
for (input, expected) in [
("inf", &inf),
("Infinity", &inf),
("1e999", &inf),
("-1e999", &neg_inf),
] {
assert_eq!(
Ok(("", expected.clone())),
parse_value(input, DEFAULT_QUOTES),
"{input} should be parsed as a float"
);
}
}
}
29 changes: 29 additions & 0 deletions src/datadog/grok/parse_grok.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,35 @@ mod tests {
]);
}

#[test]
fn parses_keyvalue_nan_as_string() {
let inf = Value::Float(NotNan::new(f64::INFINITY).expect("infinity is not NaN"));
let neg_inf = Value::Float(NotNan::new(f64::NEG_INFINITY).expect("infinity is not NaN"));
test_full_grok(vec![
(
"%{data::keyvalue}",
"a=NaN b=nan c=NAN d=inf e=-1e999 f=1e999 g=1",
Ok(Value::from(btreemap! {
"a" => "NaN",
"b" => "nan",
"c" => "NAN",
"d" => inf.clone(),
"e" => neg_inf,
"f" => inf,
"g" => 1,
})),
),
(
r#"%{data::keyvalue(" = ")}"#,
"count = 0 avg = NaN",
Ok(Value::from(btreemap! {
"count" => 0,
"avg" => "NaN",
})),
),
]);
}

#[test]
fn parses_keyvalue_edge_cases() {
test_full_grok(vec![
Expand Down
Loading