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    diagnostic::Formatter,
8    value::Value,
9};
10
11use crate::{
12    conditions::{Condition, Conditional, ConditionalConfig},
13    config::LogNamespace,
14    event::{Event, TargetEvents, VrlTarget},
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).map_err(|diagnostics| {
68            Formatter::new(&self.source, diagnostics)
69                .colored()
70                .to_string()
71        })?;
72
73        if !program.final_type_info().result.is_boolean() {
74            return Err("VRL conditions must return a boolean.".into());
75        }
76
77        if !warnings.is_empty() {
78            let warnings = Formatter::new(&self.source, warnings).colored().to_string();
79            warn!(message = "VRL compilation warning.", %warnings);
80        }
81
82        match self.runtime {
83            VrlRuntime::Ast => Ok(Condition::Vrl(Vrl {
84                program,
85                source: self.source.clone(),
86            })),
87        }
88    }
89}
90
91#[derive(Debug, Clone)]
92pub struct Vrl {
93    pub(super) program: Program,
94    pub(super) source: String,
95}
96
97impl Vrl {
98    fn run(&self, event: Event) -> (Event, RuntimeResult) {
99        let log_namespace = event
100            .maybe_as_log()
101            .map(|log| log.namespace())
102            .unwrap_or(LogNamespace::Legacy);
103        let mut target = VrlTarget::new(event, self.program.info(), false);
104        // TODO: use timezone from remap config
105        let timezone = TimeZone::default();
106
107        let result = Runtime::default().resolve(&mut target, &self.program, &timezone);
108        let original_event = match target.into_events(log_namespace) {
109            TargetEvents::One(event) => event,
110            _ => panic!("Event was modified in a condition. This is an internal compiler error."),
111        };
112        (original_event, result)
113    }
114}
115
116impl Conditional for Vrl {
117    fn check(&self, event: Event) -> (bool, Event) {
118        let (event, result) = self.run(event);
119
120        let result = result
121            .map(|value| match value {
122                Value::Boolean(boolean) => boolean,
123                _ => panic!("VRL condition did not return a boolean type"),
124            })
125            .unwrap_or_else(|err| {
126                emit!(VrlConditionExecutionError {
127                    error: err.to_string().as_ref()
128                });
129                false
130            });
131        (result, event)
132    }
133
134    fn check_with_context(&self, event: Event) -> (Result<(), String>, Event) {
135        let (event, result) = self.run(event);
136
137        let value_result = result.map_err(|err| match err {
138            Terminate::Abort(err) => {
139                let err = Formatter::new(
140                    &self.source,
141                    vrl::diagnostic::Diagnostic::from(
142                        Box::new(err) as Box<dyn vrl::diagnostic::DiagnosticMessage>
143                    ),
144                )
145                .colored()
146                .to_string();
147                format!("source execution aborted: {err}")
148            }
149            Terminate::Error(err) => {
150                let err = Formatter::new(
151                    &self.source,
152                    vrl::diagnostic::Diagnostic::from(
153                        Box::new(err) as Box<dyn vrl::diagnostic::DiagnosticMessage>
154                    ),
155                )
156                .colored()
157                .to_string();
158                format!("source execution failed: {err}")
159            }
160        });
161
162        let value = match value_result {
163            Ok(value) => value,
164            Err(err) => {
165                return (Err(err), event);
166            }
167        };
168
169        let result = match value {
170            Value::Boolean(v) if v => Ok(()),
171            Value::Boolean(v) if !v => Err("source execution resolved to false".into()),
172            _ => Err("source execution resolved to a non-boolean value".into()),
173        };
174        (result, event)
175    }
176}
177
178#[cfg(test)]
179mod test {
180    use vector_lib::metric_tags;
181
182    use super::*;
183    use crate::{
184        event::{Metric, MetricKind, MetricValue},
185        log_event,
186    };
187
188    #[test]
189    fn generate_config() {
190        crate::test_util::test_generate_config::<VrlConfig>();
191    }
192
193    #[test]
194    fn check_vrl() {
195        let checks = vec![
196            (
197                log_event![],   // event
198                "true == true", // source
199                Ok(()),         // build result
200                Ok(()),         // check result
201            ),
202            (
203                log_event!["foo" => true, "bar" => false],
204                "to_bool(.bar || .foo) ?? false",
205                Ok(()),
206                Ok(()),
207            ),
208            (
209                log_event![],
210                "true == false",
211                Ok(()),
212                Err("source execution resolved to false"),
213            ),
214            // TODO: enable once we don't emit large diagnostics with colors when no tty is present.
215            // (
216            //     log_event![],
217            //     "null",
218            //     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"),
219            //     Ok(()),
220            // ),
221            // (
222            //     log_event!["foo" => "string"],
223            //     ".foo",
224            //     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"),
225            //     Ok(()),
226            // ),
227            // (
228            //     log_event![],
229            //     ".",
230            //     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"),
231            //     Ok(()),
232            // ),
233            (
234                Event::Metric(
235                    Metric::new(
236                        "zork",
237                        MetricKind::Incremental,
238                        MetricValue::Counter { value: 1.0 },
239                    )
240                    .with_namespace(Some("zerk"))
241                    .with_tags(Some(metric_tags!("host" => "zoobub"))),
242                ),
243                r#".name == "zork" && .tags.host == "zoobub" && .kind == "incremental""#,
244                Ok(()),
245                Ok(()),
246            ),
247            (
248                log_event![],
249                r#""i_return_a_string""#,
250                Err("VRL conditions must return a boolean.".into()),
251                Ok(()),
252            ),
253        ];
254
255        for (event, source, build, check) in checks {
256            let source = source.to_owned();
257            let config = VrlConfig {
258                source,
259                runtime: Default::default(),
260            };
261
262            assert_eq!(
263                config
264                    .build(&Default::default())
265                    .map(|_| ())
266                    .map_err(|e| e.to_string()),
267                build
268            );
269
270            if let Ok(cond) = config.build(&Default::default()) {
271                assert_eq!(
272                    cond.check_with_context(event.clone()).0,
273                    check.map_err(|e| e.to_string())
274                );
275            }
276        }
277    }
278}