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#[configurable_component]
20#[derive(Clone, Debug, Default, PartialEq, Eq)]
21pub struct VrlConfig {
22 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 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 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![], "true == true", Ok(()), Ok(()), ),
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 (
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}