vector/conditions/
vrl.rs

1use vector_lib::{TimeZone, compile_vrl, configurable::configurable_component, emit};
2use vrl::{
3    compiler::{
4        CompilationResult, CompileConfig, Program, TypeState, VrlRuntime,
5        runtime::{Runtime, RuntimeResult, Terminate},
6    },
7    value::Value,
8};
9
10use crate::{
11    conditions::{Condition, Conditional, ConditionalConfig},
12    config::LogNamespace,
13    event::{Event, TargetEvents, VrlTarget},
14    format_vrl_diagnostics,
15    internal_events::VrlConditionExecutionError,
16};
17
18/// A condition that uses the [Vector Remap Language](https://vector.dev/docs/reference/vrl) (VRL) [boolean expression](https://vector.dev/docs/reference/vrl#boolean-expressions) against an event.
19#[configurable_component]
20#[derive(Clone, Debug, Default, PartialEq, Eq)]
21pub struct VrlConfig {
22    /// The VRL boolean expression.
23    pub(crate) source: String,
24
25    #[configurable(derived, metadata(docs::hidden))]
26    #[serde(default, skip_serializing_if = "crate::serde::is_default")]
27    pub(crate) runtime: VrlRuntime,
28}
29
30impl_generate_config_from_default!(VrlConfig);
31
32impl ConditionalConfig for VrlConfig {
33    fn build(
34        &self,
35        enrichment_tables: &vector_lib::enrichment::TableRegistry,
36    ) -> crate::Result<Condition> {
37        // TODO(jean): re-add this to VRL
38        // let constraint = TypeConstraint {
39        //     allow_any: false,
40        //     type_def: TypeDef {
41        //         fallible: true,
42        //         kind: value::Kind::Boolean,
43        //         ..Default::default()
44        //     },
45        // };
46
47        let functions = vrl::stdlib::all()
48            .into_iter()
49            .chain(vector_lib::enrichment::vrl_functions());
50        #[cfg(feature = "sources-dnstap")]
51        let functions = functions.chain(dnstap_parser::vrl_functions());
52
53        let functions = functions
54            .chain(vector_vrl_functions::all())
55            .collect::<Vec<_>>();
56
57        let state = TypeState::default();
58
59        let mut config = CompileConfig::default();
60        config.set_custom(enrichment_tables.clone());
61        config.set_read_only();
62
63        let CompilationResult {
64            program,
65            warnings,
66            config: _,
67        } = compile_vrl(&self.source, &functions, &state, config)
68            .map_err(|diagnostics| format_vrl_diagnostics(&self.source, diagnostics))?;
69
70        if !program.final_type_info().result.is_boolean() {
71            return Err("VRL conditions must return a boolean.".into());
72        }
73
74        if !warnings.is_empty() {
75            let warnings = format_vrl_diagnostics(&self.source, warnings);
76            warn!(message = "VRL compilation warning.", %warnings);
77        }
78
79        match self.runtime {
80            VrlRuntime::Ast => Ok(Condition::Vrl(Vrl {
81                program,
82                source: self.source.clone(),
83            })),
84        }
85    }
86}
87
88#[derive(Debug, Clone)]
89pub struct Vrl {
90    pub(super) program: Program,
91    pub(super) source: String,
92}
93
94impl Vrl {
95    fn run(&self, event: Event) -> (Event, RuntimeResult) {
96        let log_namespace = event
97            .maybe_as_log()
98            .map(|log| log.namespace())
99            .unwrap_or(LogNamespace::Legacy);
100        let mut target = VrlTarget::new(event, self.program.info(), false);
101        // TODO: use timezone from remap config
102        let timezone = TimeZone::default();
103
104        let result = Runtime::default().resolve(&mut target, &self.program, &timezone);
105        let original_event = match target.into_events(log_namespace) {
106            TargetEvents::One(event) => event,
107            _ => panic!("Event was modified in a condition. This is an internal compiler error."),
108        };
109        (original_event, result)
110    }
111}
112
113impl Conditional for Vrl {
114    fn check(&self, event: Event) -> (bool, Event) {
115        let (event, result) = self.run(event);
116
117        let result = result
118            .map(|value| match value {
119                Value::Boolean(boolean) => boolean,
120                _ => panic!("VRL condition did not return a boolean type"),
121            })
122            .unwrap_or_else(|err| {
123                emit!(VrlConditionExecutionError {
124                    error: err.to_string().as_ref()
125                });
126                false
127            });
128        (result, event)
129    }
130
131    fn check_with_context(&self, event: Event) -> (Result<(), String>, Event) {
132        let (event, result) = self.run(event);
133
134        let value_result = result.map_err(|err| match err {
135            Terminate::Abort(err) => {
136                let err = format_vrl_diagnostics(
137                    &self.source,
138                    vrl::diagnostic::Diagnostic::from(
139                        Box::new(err) as Box<dyn vrl::diagnostic::DiagnosticMessage>
140                    ),
141                );
142                format!("source execution aborted: {err}")
143            }
144            Terminate::Error(err) => {
145                let err = format_vrl_diagnostics(
146                    &self.source,
147                    vrl::diagnostic::Diagnostic::from(
148                        Box::new(err) as Box<dyn vrl::diagnostic::DiagnosticMessage>
149                    ),
150                );
151                format!("source execution failed: {err}")
152            }
153        });
154
155        let value = match value_result {
156            Ok(value) => value,
157            Err(err) => {
158                return (Err(err), event);
159            }
160        };
161
162        let result = match value {
163            Value::Boolean(v) if v => Ok(()),
164            Value::Boolean(v) if !v => Err("source execution resolved to false".into()),
165            _ => Err("source execution resolved to a non-boolean value".into()),
166        };
167        (result, event)
168    }
169}
170
171#[cfg(test)]
172mod test {
173    use vector_lib::metric_tags;
174
175    use super::*;
176    use crate::{
177        event::{Metric, MetricKind, MetricValue},
178        log_event,
179    };
180
181    #[test]
182    fn generate_config() {
183        crate::test_util::test_generate_config::<VrlConfig>();
184    }
185
186    #[test]
187    fn check_vrl() {
188        let checks = vec![
189            (
190                log_event![],   // event
191                "true == true", // source
192                Ok(()),         // build result
193                Ok(()),         // check result
194            ),
195            (
196                log_event!["foo" => true, "bar" => false],
197                "to_bool(.bar || .foo) ?? false",
198                Ok(()),
199                Ok(()),
200            ),
201            (
202                log_event![],
203                "true == false",
204                Ok(()),
205                Err("source execution resolved to false"),
206            ),
207            // TODO: enable once we don't emit large diagnostics with colors when no tty is present.
208            // (
209            //     log_event![],
210            //     "null",
211            //     Err("\n\u{1b}[0m\u{1b}[1m\u{1b}[38;5;9merror\u{1b}[0m\u{1b}[1m: unexpected return value\u{1b}[0m\n  \u{1b}[0m\u{1b}[34m┌─\u{1b}[0m :1:1\n  \u{1b}[0m\u{1b}[34m│\u{1b}[0m\n\u{1b}[0m\u{1b}[34m1\u{1b}[0m \u{1b}[0m\u{1b}[34m│\u{1b}[0m \u{1b}[0m\u{1b}[31mnull\u{1b}[0m\n  \u{1b}[0m\u{1b}[34m│\u{1b}[0m \u{1b}[0m\u{1b}[31m^^^^\u{1b}[0m\n  \u{1b}[0m\u{1b}[34m│\u{1b}[0m \u{1b}[0m\u{1b}[31m│\u{1b}[0m\n  \u{1b}[0m\u{1b}[34m│\u{1b}[0m \u{1b}[0m\u{1b}[31mgot: null\u{1b}[0m\n  \u{1b}[0m\u{1b}[34m│\u{1b}[0m \u{1b}[0m\u{1b}[34mexpected: boolean\u{1b}[0m\n  \u{1b}[0m\u{1b}[34m│\u{1b}[0m\n  \u{1b}[0m\u{1b}[34m=\u{1b}[0m see language documentation at: https://vector.dev/docs/reference/vrl/\n\n"),
212            //     Ok(()),
213            // ),
214            // (
215            //     log_event!["foo" => "string"],
216            //     ".foo",
217            //     Err("\n\u{1b}[0m\u{1b}[1m\u{1b}[38;5;9merror\u{1b}[0m\u{1b}[1m: unexpected return value\u{1b}[0m\n  \u{1b}[0m\u{1b}[34m┌─\u{1b}[0m :1:1\n  \u{1b}[0m\u{1b}[34m│\u{1b}[0m\n\u{1b}[0m\u{1b}[34m1\u{1b}[0m \u{1b}[0m\u{1b}[34m│\u{1b}[0m \u{1b}[0m\u{1b}[31m.foo\u{1b}[0m\n  \u{1b}[0m\u{1b}[34m│\u{1b}[0m \u{1b}[0m\u{1b}[31m^^^^\u{1b}[0m\n  \u{1b}[0m\u{1b}[34m│\u{1b}[0m \u{1b}[0m\u{1b}[31m│\u{1b}[0m\n  \u{1b}[0m\u{1b}[34m│\u{1b}[0m \u{1b}[0m\u{1b}[31mgot: any\u{1b}[0m\n  \u{1b}[0m\u{1b}[34m│\u{1b}[0m \u{1b}[0m\u{1b}[34mexpected: boolean\u{1b}[0m\n  \u{1b}[0m\u{1b}[34m│\u{1b}[0m\n  \u{1b}[0m\u{1b}[34m=\u{1b}[0m see language documentation at: https://vector.dev/docs/reference/vrl/\n\n"),
218            //     Ok(()),
219            // ),
220            // (
221            //     log_event![],
222            //     ".",
223            //     Err("n\u{1b}[0m\u{1b}[1m\u{1b}[38;5;9merror\u{1b}[0m\u{1b}[1m: unexpected return value\u{1b}[0m\n  \u{1b}[0m\u{1b}[34m┌─\u{1b}[0m :1:1\n  \u{1b}[0m\u{1b}[34m│\u{1b}[0m\n\u{1b}[0m\u{1b}[34m1\u{1b}[0m \u{1b}[0m\u{1b}[34m│\u{1b}[0m \u{1b}[0m\u{1b}[31m.\u{1b}[0m\n  \u{1b}[0m\u{1b}[34m│\u{1b}[0m \u{1b}[0m\u{1b}[31m^\u{1b}[0m\n  \u{1b}[0m\u{1b}[34m│\u{1b}[0m \u{1b}[0m\u{1b}[31m│\u{1b}[0m\n  \u{1b}[0m\u{1b}[34m│\u{1b}[0m \u{1b}[0m\u{1b}[31mgot: any\u{1b}[0m\n  \u{1b}[0m\u{1b}[34m│\u{1b}[0m \u{1b}[0m\u{1b}[34mexpected: boolean\u{1b}[0m\n  \u{1b}[0m\u{1b}[34m│\u{1b}[0m\n  \u{1b}[0m\u{1b}[34m=\u{1b}[0m see language documentation at: https://vector.dev/docs/reference/vrl/\n\n"),
224            //     Ok(()),
225            // ),
226            (
227                Event::Metric(
228                    Metric::new(
229                        "zork",
230                        MetricKind::Incremental,
231                        MetricValue::Counter { value: 1.0 },
232                    )
233                    .with_namespace(Some("zerk"))
234                    .with_tags(Some(metric_tags!("host" => "zoobub"))),
235                ),
236                r#".name == "zork" && .tags.host == "zoobub" && .kind == "incremental""#,
237                Ok(()),
238                Ok(()),
239            ),
240            (
241                log_event![],
242                r#""i_return_a_string""#,
243                Err("VRL conditions must return a boolean.".into()),
244                Ok(()),
245            ),
246        ];
247
248        for (event, source, build, check) in checks {
249            let source = source.to_owned();
250            let config = VrlConfig {
251                source,
252                runtime: Default::default(),
253            };
254
255            assert_eq!(
256                config
257                    .build(&Default::default())
258                    .map(|_| ())
259                    .map_err(|e| e.to_string()),
260                build
261            );
262
263            if let Ok(cond) = config.build(&Default::default()) {
264                assert_eq!(
265                    cond.check_with_context(event.clone()).0,
266                    check.map_err(|e| e.to_string())
267                );
268            }
269        }
270    }
271}