Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhancement: Add support for negating conditions in check_fields #2514

Merged
merged 3 commits into from May 4, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 11 additions & 0 deletions .meta/_partials/fields/_conditions_options.toml.erb
Expand Up @@ -12,6 +12,17 @@ is_log = "Returns true if the event is a log."
is_metric = "Returns true if the event is a metric."


[<%= namespace %>."`[field-name]`.not_`[condition]`"]
type = "any"
examples = [
{ "message.not_contains" = "some phrase to ignore" }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a few more examples here?

]
common = false
relevant_when = {type = "check_fields"}
description = """\
Check if the given `[condition]` does not match.\
"""

[<%= namespace %>."`[field-name]`.eq"]
type = "string"
examples = [
Expand Down
16 changes: 16 additions & 0 deletions config/vector.spec.toml
Expand Up @@ -1627,6 +1627,14 @@ dns_servers = ["0.0.0.0:53"]
# * relevant when type = "check_fields"
"method.neq" = "POST"

# Check if the given `[condition]` does not match.
#
# * optional
# * no default
# * type: any
# * relevant when type = "check_fields"
"message.not_contains" = "some phrase to ignore"

# Checks whether a string field contains a string argument.
#
# * optional
Expand Down Expand Up @@ -2513,6 +2521,14 @@ require('custom_module')
# * relevant when type = "check_fields"
"method.neq" = "POST"

# Check if the given `[condition]` does not match.
#
# * optional
# * no default
# * type: any
# * relevant when type = "check_fields"
"message.not_contains" = "some phrase to ignore"

# Checks whether a string field contains a string argument.
#
# * optional
Expand Down
48 changes: 47 additions & 1 deletion src/conditions/check_fields.rs
Expand Up @@ -298,6 +298,30 @@ impl CheckFieldsPredicate for ExistsPredicate {

//------------------------------------------------------------------------------

#[derive(Debug)]
struct NegatePredicate {
subpred: Box<dyn CheckFieldsPredicate>,
}

impl NegatePredicate {
pub fn new(
predicate: &str,
target: String,
arg: &CheckFieldsPredicateArg,
) -> Result<Box<dyn CheckFieldsPredicate>, String> {
let subpred = build_predicate(predicate, target, arg)?;
Ok(Box::new(Self { subpred }))
}
}

impl CheckFieldsPredicate for NegatePredicate {
fn check(&self, event: &Event) -> bool {
!self.subpred.check(event)
}
}

//------------------------------------------------------------------------------

fn build_predicate(
predicate: &str,
target: String,
Expand All @@ -318,6 +342,7 @@ fn build_predicate(
"ends_with" => EndsWithPredicate::new(target, arg),
"exists" => ExistsPredicate::new(target, arg),
"regex" => RegexPredicate::new(target, arg),
_ if predicate.starts_with("not_") => NegatePredicate::new(&predicate[4..], target, arg),
_ => Err(format!("predicate type '{}' not recognized", predicate)),
}
}
Expand Down Expand Up @@ -436,7 +461,7 @@ mod test {
(".nah", "predicate not found in check_fields value '.nah', format must be <target>.<predicate>"),
("", "predicate not found in check_fields value '', format must be <target>.<predicate>"),
("what.", "predicate not found in check_fields value 'what.', format must be <target>.<predicate>"),
("foo.not_real", "predicate type 'not_real' not recognized"),
("foo.nix_real", "predicate type 'nix_real' not recognized"),
];

let mut aggregated_preds: IndexMap<String, CheckFieldsPredicateArg> = IndexMap::new();
Expand Down Expand Up @@ -800,4 +825,25 @@ mod test {
Err("predicates failed: [ bar.exists: false ]".to_owned())
);
}

#[test]
fn negate_predicate() {
let mut preds: IndexMap<String, CheckFieldsPredicateArg> = IndexMap::new();
preds.insert(
"foo.not_exists".into(),
CheckFieldsPredicateArg::Boolean(true),
);
let cond = CheckFieldsConfig { predicates: preds }.build().unwrap();

let mut event = Event::from("ignored field");
assert_eq!(cond.check(&event), true);
assert_eq!(cond.check_with_context(&event), Ok(()));

event.as_mut_log().insert("foo", "not ignored");
assert_eq!(cond.check(&event), false);
assert_eq!(
cond.check_with_context(&event),
Err("predicates failed: [ foo.not_exists: true ]".into())
);
}
}
27 changes: 26 additions & 1 deletion website/docs/reference/tests.md
@@ -1,5 +1,5 @@
---
last_modified_on: "2020-04-29"
last_modified_on: "2020-04-30"
title: Unit Tests
description: Vector's unit test configuration options, allowing you to unit test your Vector configuration files.
status: beta
Expand Down Expand Up @@ -115,6 +115,7 @@ vector test /etc/vector/*.toml
conditions."message.eq" = "this is the content to match against" # example
conditions."host.exists" = true # example
conditions."method.neq" = "POST" # example
conditions."message.not_contains" = "some phrase to ignore" # example
conditions."message.contains" = "foo" # example
conditions."environment.ends_with" = "-staging" # example
conditions."message.regex" = " (any|of|these|five|words) " # example
Expand Down Expand Up @@ -695,6 +696,30 @@ Check whether a fields contents does not match the value specified.



</Field>
<Field
common={false}
defaultValue={null}
enumValues={null}
examples={[{"message.not_contains":"some phrase to ignore"}]}
groups={[]}
name={"`[field-name]`.not_`[condition]`"}
path={"outputs.conditions"}
relevantWhen={{"type":"check_fields"}}
required={false}
templateable={false}
type={"any"}
unit={null}
warnings={[]}
>

##### `[field-name]`.not_`[condition]`

Check if the given `[condition]` does not match.




</Field>
<Field
common={true}
Expand Down
27 changes: 26 additions & 1 deletion website/docs/reference/transforms/filter.md
@@ -1,5 +1,5 @@
---
last_modified_on: "2020-04-29"
last_modified_on: "2020-04-30"
component_title: "Filter"
description: "The Vector `filter` transform accepts and outputs `log` and `metric` events, allowing you to select events based on a set of logical conditions."
event_types: ["log","metric"]
Expand Down Expand Up @@ -66,6 +66,7 @@ on a set of logical conditions.
condition."message.eq" = "this is the content to match against" # example
condition."host.exists" = true # example
condition."method.neq" = "POST" # example
condition."message.not_contains" = "some phrase to ignore" # example
condition."message.contains" = "foo" # example
condition."environment.ends_with" = "-staging" # example
condition."message.regex" = " (any|of|these|five|words) " # example
Expand Down Expand Up @@ -196,6 +197,30 @@ Check whether a fields contents does not match the value specified.



</Field>
<Field
common={false}
defaultValue={null}
enumValues={null}
examples={[{"message.not_contains":"some phrase to ignore"}]}
groups={[]}
name={"`[field-name]`.not_`[condition]`"}
path={"condition"}
relevantWhen={{"type":"check_fields"}}
required={false}
templateable={false}
type={"any"}
unit={null}
warnings={[]}
>

#### `[field-name]`.not_`[condition]`

Check if the given `[condition]` does not match.




</Field>
<Field
common={true}
Expand Down
27 changes: 26 additions & 1 deletion website/docs/reference/transforms/swimlanes.md
@@ -1,5 +1,5 @@
---
last_modified_on: "2020-04-29"
last_modified_on: "2020-04-30"
component_title: "Swimlanes"
description: "The Vector `swimlanes` transform accepts and outputs `log` events, allowing you to route events across parallel streams using logical filters."
event_types: ["log"]
Expand Down Expand Up @@ -67,6 +67,7 @@ events across parallel streams using logical filters.
"message.eq" = "this is the content to match against" # example
"host.exists" = true # example
"method.neq" = "POST" # example
"message.not_contains" = "some phrase to ignore" # example
"message.contains" = "foo" # example
"environment.ends_with" = "-staging" # example
"message.regex" = " (any|of|these|five|words) " # example
Expand Down Expand Up @@ -221,6 +222,30 @@ Check whether a fields contents does not match the value specified.



</Field>
<Field
common={false}
defaultValue={null}
enumValues={null}
examples={[{"message.not_contains":"some phrase to ignore"}]}
groups={[]}
name={"`[field-name]`.not_`[condition]`"}
path={"lanes.`[swimlane-id]`"}
relevantWhen={{"type":"check_fields"}}
required={false}
templateable={false}
type={"any"}
unit={null}
warnings={[]}
>

##### `[field-name]`.not_`[condition]`

Check if the given `[condition]` does not match.




</Field>
<Field
common={true}
Expand Down