vector/conditions/
vrl.rs

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