Skip to content

Commit

Permalink
fix(parse_grok): catch panic from onig on above limit retry matching
Browse files Browse the repository at this point in the history
  • Loading branch information
tessneau committed Oct 16, 2024
1 parent 3295458 commit 20a8da9
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 54 deletions.
21 changes: 17 additions & 4 deletions src/datadog/grok/grok.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@
include!(concat!(env!("OUT_DIR"), "/patterns.rs"));

use std::collections::{btree_map, BTreeMap};
use std::panic;
use std::sync::Arc;

use super::parse_grok::Error as GrokRuntimeError;

use onig::{Captures, Regex};
use thiserror::Error;

Expand Down Expand Up @@ -107,10 +110,20 @@ impl Pattern {

/// Matches this compiled `Pattern` against the text and returns the matches.
#[inline]
pub fn match_against<'a>(&'a self, text: &'a str) -> Option<Matches<'a>> {
self.regex
.captures(text)
.map(|cap| Matches::new(cap, &self.names))
pub fn match_against<'a>(
&'a self,
text: &'a str,
) -> Result<Option<Matches<'a>>, GrokRuntimeError> {
let result = panic::catch_unwind(|| self.regex.captures(text));

match result {
Ok(Some(cap)) => Ok(Some(Matches::new(cap, &self.names))),
Ok(None) => Ok(None),
// https://github.com/rust-onig/rust-onig/issues/178
Err(_) => Err(GrokRuntimeError::FailedToMatch(
"Regex search error, try simplifying your regex to decrease the amount of match retries".into(),
)),
}
}
}

Expand Down
104 changes: 54 additions & 50 deletions src/datadog/grok/parse_grok.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
use crate::path::parse_value_path;
use crate::value::{ObjectMap, Value};
use std::collections::BTreeMap;
use tracing::warn;

use super::{
grok_filter::apply_filter,
parse_grok_rules::{GrokField, GrokRule},
};
use crate::path::parse_value_path;
use crate::value::{ObjectMap, Value};
use std::collections::BTreeMap;
use tracing::{error, warn};

#[derive(thiserror::Error, Debug, PartialEq, Eq)]
pub enum Error {
#[error("failed to apply filter '{}' to '{}'", .0, .1)]
FailedToApplyFilter(String, String),
#[error("value does not match any rule")]
NoMatch,
#[error("failure occurred during match of the pattern against the value: '{}'", .0)]
FailedToMatch(String),
}

/// Parses a given source field value by applying the list of grok rules until the first match found.
Expand All @@ -32,23 +33,25 @@ pub fn parse_grok(source_field: &str, grok_rules: &[GrokRule]) -> Result<Value,
/// Possible errors:
/// - FailedToApplyFilter - matches the rule, but there was a runtime error while applying on of the filters
/// - NoMatch - this rule does not match a given string
/// - FailedToMatch - there was a runtime error while matching the compiled pattern against the source
fn apply_grok_rule(source: &str, grok_rule: &GrokRule) -> Result<Value, Error> {
let mut parsed = Value::Object(BTreeMap::new());

if let Some(ref matches) = grok_rule.pattern.match_against(source) {
for (name, match_str) in matches.iter() {
if match_str.is_empty() {
continue;
}
match grok_rule.pattern.match_against(source) {
Ok(Some(matches)) => {
for (name, match_str) in matches.iter() {
if match_str.is_empty() {
continue;
}

let mut value = Some(Value::from(match_str));
let mut value = Some(Value::from(match_str));

if let Some(GrokField {
lookup: field,
filters,
}) = grok_rule.fields.get(name)
{
filters.iter().for_each(|filter| {
if let Some(GrokField {
lookup: field,
filters,
}) = grok_rule.fields.get(name)
{
filters.iter().for_each(|filter| {
if let Some(ref mut v) = value {
value = match apply_filter(v, filter) {
Ok(Value::Null) => None,
Expand All @@ -62,43 +65,44 @@ fn apply_grok_rule(source: &str, grok_rule: &GrokRule) -> Result<Value, Error> {
}
});

if let Some(value) = value {
match value {
// root-level maps must be merged
Value::Object(map) if field.is_root() => {
parsed.as_object_mut().expect("root is object").extend(map);
}
// anything else at the root leve must be ignored
_ if field.is_root() => {}
// otherwise just apply VRL lookup insert logic
_ => match parsed.get(field).cloned() {
Some(Value::Array(mut values)) => {
values.push(value);
parsed.insert(field, values);
}
Some(v) => {
parsed.insert(field, Value::Array(vec![v, value]));
if let Some(value) = value {
match value {
// root-level maps must be merged
Value::Object(map) if field.is_root() => {
parsed.as_object_mut().expect("root is object").extend(map);
}
None => {
parsed.insert(field, value);
}
},
};
// anything else at the root leve must be ignored
_ if field.is_root() => {}
// otherwise just apply VRL lookup insert logic
_ => match parsed.get(field).cloned() {
Some(Value::Array(mut values)) => {
values.push(value);
parsed.insert(field, values);
}
Some(v) => {
parsed.insert(field, Value::Array(vec![v, value]));
}
None => {
parsed.insert(field, value);
}
},
};
}
} else {
// this must be a regex named capturing group (?<name>group),
// where name can only be alphanumeric - thus we do not need to parse field names(no nested fields)
parsed
.as_object_mut()
.expect("parsed value is not an object")
.insert(name.to_string().into(), value.into());
}
} else {
// this must be a regex named capturing group (?<name>group),
// where name can only be alphanumeric - thus we do not need to parse field names(no nested fields)
parsed
.as_object_mut()
.expect("parsed value is not an object")
.insert(name.to_string().into(), value.into());
}
}

postprocess_value(&mut parsed);
Ok(parsed)
} else {
Err(Error::NoMatch)
postprocess_value(&mut parsed);
Ok(parsed)
}
Ok(None) => Err(Error::NoMatch),
Err(e) => Err(e),
}
}

Expand Down

0 comments on commit 20a8da9

Please sign in to comment.