1#![allow(clippy::let_underscore_must_use)]
3
4use std::{
5 collections::{BTreeMap, HashMap},
6 fs::File,
7 io::{self, Read},
8 path::PathBuf,
9 sync::Mutex,
10};
11
12use snafu::{ResultExt, Snafu};
13use vector_lib::{
14 TimeZone,
15 codecs::MetricTagValues,
16 compile_vrl,
17 config::LogNamespace,
18 configurable::configurable_component,
19 enrichment::TableRegistry,
20 lookup::{PathPrefix, metadata_path, owned_value_path},
21 schema::Definition,
22};
23use vector_vrl_functions::set_semantic_meaning::MeaningList;
24use vector_vrl_metrics::MetricsStorage;
25use vrl::{
26 compiler::{
27 CompileConfig, ExpressionError, Program, TypeState, VrlRuntime,
28 runtime::{Runtime, Terminate},
29 state::ExternalEnv,
30 },
31 diagnostic::{DiagnosticMessage, Note},
32 path,
33 path::ValuePath,
34 value::{Kind, Value},
35};
36
37use crate::{
38 Result,
39 config::{
40 ComponentKey, DataType, Input, OutputId, TransformConfig, TransformContext,
41 TransformOutput, log_schema,
42 },
43 event::{Event, TargetEvents, VrlTarget},
44 format_vrl_diagnostics,
45 internal_events::{RemapMappingAbort, RemapMappingError},
46 schema,
47 transforms::{SyncTransform, Transform, TransformOutputsBuf},
48};
49
50const DROPPED: &str = "dropped";
51type CacheKey = (TableRegistry, schema::Definition);
52type CacheValue = (Program, String, MeaningList);
53
54#[configurable_component(transform(
56 "remap",
57 "Modify your observability data as it passes through your topology using Vector Remap Language (VRL)."
58))]
59#[derive(Derivative)]
60#[serde(deny_unknown_fields)]
61#[derivative(Default, Debug)]
62pub struct RemapConfig {
63 #[configurable(metadata(
69 docs::examples = ". = parse_json!(.message)\n.new_field = \"new value\"\n.status = to_int!(.status)\n.duration = parse_duration!(.duration, \"s\")\n.new_name = del(.old_name)",
70 docs::syntax_override = "remap_program"
71 ))]
72 pub source: Option<String>,
73
74 #[configurable(metadata(docs::examples = "./my/program.vrl"))]
82 pub file: Option<PathBuf>,
83
84 #[configurable(metadata(docs::examples = "['./my/program.vrl', './my/program2.vrl']"))]
92 pub files: Option<Vec<PathBuf>>,
93
94 #[serde(default)]
101 pub metric_tag_values: MetricTagValues,
102
103 #[serde(default)]
112 #[configurable(metadata(docs::advanced))]
113 pub timezone: Option<TimeZone>,
114
115 #[serde(default = "crate::serde::default_false")]
126 #[configurable(metadata(docs::human_name = "Drop Event on Error"))]
127 pub drop_on_error: bool,
128
129 #[serde(default = "crate::serde::default_true")]
140 #[configurable(metadata(docs::human_name = "Drop Event on Abort"))]
141 pub drop_on_abort: bool,
142
143 #[serde(default = "crate::serde::default_false")]
153 #[configurable(metadata(docs::human_name = "Reroute Dropped Events"))]
154 pub reroute_dropped: bool,
155
156 #[configurable(derived, metadata(docs::hidden))]
157 #[serde(default)]
158 pub runtime: VrlRuntime,
159
160 #[configurable(derived, metadata(docs::hidden))]
161 #[serde(skip)]
162 #[derivative(Debug = "ignore")]
163 pub cache: Mutex<Vec<(CacheKey, std::result::Result<CacheValue, String>)>>,
167}
168
169impl Clone for RemapConfig {
170 fn clone(&self) -> Self {
171 Self {
172 source: self.source.clone(),
173 file: self.file.clone(),
174 files: self.files.clone(),
175 metric_tag_values: self.metric_tag_values,
176 timezone: self.timezone,
177 drop_on_error: self.drop_on_error,
178 drop_on_abort: self.drop_on_abort,
179 reroute_dropped: self.reroute_dropped,
180 runtime: self.runtime,
181 cache: Mutex::new(Default::default()),
182 }
183 }
184}
185
186impl RemapConfig {
187 fn compile_vrl_program(
188 &self,
189 enrichment_tables: TableRegistry,
190 metrics_storage: MetricsStorage,
191 merged_schema_definition: schema::Definition,
192 ) -> Result<(Program, String, MeaningList)> {
193 if let Some((_, res)) = self
194 .cache
195 .lock()
196 .expect("Data poisoned")
197 .iter()
198 .find(|v| v.0.0 == enrichment_tables && v.0.1 == merged_schema_definition)
199 {
200 return res.clone().map_err(Into::into);
201 }
202
203 let source = match (&self.source, &self.file, &self.files) {
204 (Some(source), None, None) => source.to_owned(),
205 (None, Some(path), None) => Self::read_file(path)?,
206 (None, None, Some(paths)) => {
207 let mut combined_source = String::new();
208 for path in paths {
209 let content = Self::read_file(path)?;
210 combined_source.push_str(&content);
211 combined_source.push('\n');
212 }
213 combined_source
214 }
215 _ => return Err(Box::new(BuildError::SourceAndOrFileOrFiles)),
216 };
217
218 let state = TypeState {
219 local: Default::default(),
220 external: ExternalEnv::new_with_kind(
221 merged_schema_definition.event_kind().clone(),
222 merged_schema_definition.metadata_kind().clone(),
223 ),
224 };
225 let mut config = CompileConfig::default();
226
227 config.set_custom(enrichment_tables.clone());
228 config.set_custom(metrics_storage);
229 config.set_custom(MeaningList::default());
230
231 let res = compile_vrl(&source, &vector_vrl_functions::all(), &state, config)
232 .map_err(|diagnostics| format_vrl_diagnostics(&source, diagnostics))
233 .map(|result| {
234 (
235 result.program,
236 format_vrl_diagnostics(&source, result.warnings),
237 result.config.get_custom::<MeaningList>().unwrap().clone(),
238 )
239 });
240
241 self.cache
242 .lock()
243 .expect("Data poisoned")
244 .push(((enrichment_tables, merged_schema_definition), res.clone()));
245
246 res.map_err(Into::into)
247 }
248
249 fn read_file(path: &PathBuf) -> Result<String> {
250 let mut buffer = String::new();
251 File::open(path)
252 .with_context(|_| FileOpenFailedSnafu { path })?
253 .read_to_string(&mut buffer)
254 .with_context(|_| FileReadFailedSnafu { path })?;
255 Ok(buffer)
256 }
257}
258
259impl_generate_config_from_default!(RemapConfig);
260
261#[async_trait::async_trait]
262#[typetag::serde(name = "remap")]
263impl TransformConfig for RemapConfig {
264 async fn build(&self, context: &TransformContext) -> Result<Transform> {
265 let (transform, warnings) = match self.runtime {
266 VrlRuntime::Ast => {
267 let (remap, warnings) = Remap::new_ast(self.clone(), context)?;
268 (Transform::synchronous(remap), warnings)
269 }
270 };
271
272 if !warnings.is_empty() {
277 warn!(message = "VRL compilation warning.", %warnings);
278 }
279
280 Ok(transform)
281 }
282
283 fn validate_env(&self, context: &TransformContext) -> std::result::Result<(), Vec<String>> {
284 self.compile_vrl_program(
285 context.enrichment_tables.clone(),
286 context.metrics_storage.clone(),
287 context.merged_schema_definition.clone(),
288 )
289 .map(|_| ())
290 .map_err(|e| vec![e.to_string()])
291 }
292
293 fn input(&self) -> Input {
294 Input::all()
295 }
296
297 fn outputs(
298 &self,
299 context: &TransformContext,
300 input_definitions: &[(OutputId, schema::Definition)],
301 ) -> Vec<TransformOutput> {
302 let merged_definition: Definition = input_definitions
303 .iter()
304 .map(|(_output, definition)| definition.clone())
305 .reduce(Definition::merge)
306 .unwrap_or_else(Definition::any);
307
308 let compiled = self
312 .compile_vrl_program(
313 context.enrichment_tables.clone(),
314 context.metrics_storage.clone(),
315 merged_definition,
316 )
317 .map(|(program, _, meaning_list)| (program.final_type_info().state, meaning_list.0))
318 .map_err(|_| ());
319
320 let mut dropped_definitions = HashMap::new();
321 let mut default_definitions = HashMap::new();
322
323 for (output_id, input_definition) in input_definitions {
324 let default_definition = compiled
325 .clone()
326 .map(|(state, meaning)| {
327 let mut new_type_def = Definition::new(
328 state.external.target_kind().clone(),
329 state.external.metadata_kind().clone(),
330 input_definition.log_namespaces().clone(),
331 );
332
333 for (id, path) in input_definition.meanings() {
334 new_type_def.try_with_meaning(path.clone(), id).ok();
338 }
339
340 for (id, path) in meaning {
342 new_type_def = new_type_def.with_meaning(path, &id);
344 }
345 new_type_def
346 })
347 .unwrap_or_else(|_| {
348 Definition::new_with_default_metadata(
349 Kind::never(),
351 input_definition.log_namespaces().clone(),
352 )
353 });
354
355 let dropped_definition = Definition::combine_log_namespaces(
358 input_definition.log_namespaces(),
359 input_definition.clone().with_event_field(
360 log_schema().metadata_key().expect("valid metadata key"),
361 Kind::object(BTreeMap::from([
362 ("reason".into(), Kind::bytes()),
363 ("message".into(), Kind::bytes()),
364 ("component_id".into(), Kind::bytes()),
365 ("component_type".into(), Kind::bytes()),
366 ("component_kind".into(), Kind::bytes()),
367 ])),
368 Some("metadata"),
369 ),
370 input_definition
371 .clone()
372 .with_metadata_field(&owned_value_path!("reason"), Kind::bytes(), None)
373 .with_metadata_field(&owned_value_path!("message"), Kind::bytes(), None)
374 .with_metadata_field(&owned_value_path!("component_id"), Kind::bytes(), None)
375 .with_metadata_field(&owned_value_path!("component_type"), Kind::bytes(), None)
376 .with_metadata_field(&owned_value_path!("component_kind"), Kind::bytes(), None),
377 );
378
379 default_definitions.insert(
380 output_id.clone(),
381 VrlTarget::modify_schema_definition_for_into_events(default_definition),
382 );
383 dropped_definitions.insert(
384 output_id.clone(),
385 VrlTarget::modify_schema_definition_for_into_events(dropped_definition),
386 );
387 }
388
389 let default_output = TransformOutput::new(DataType::all_bits(), default_definitions);
390
391 if self.reroute_dropped {
392 vec![
393 default_output,
394 TransformOutput::new(DataType::all_bits(), dropped_definitions).with_port(DROPPED),
395 ]
396 } else {
397 vec![default_output]
398 }
399 }
400
401 fn enable_concurrency(&self) -> bool {
402 true
403 }
404
405 fn files_to_watch(&self) -> Vec<&PathBuf> {
406 self.file
407 .iter()
408 .chain(self.files.iter().flatten())
409 .collect()
410 }
411}
412
413#[derive(Debug, Clone)]
414pub struct Remap<Runner>
415where
416 Runner: VrlRunner,
417{
418 component_key: Option<ComponentKey>,
419 program: Program,
420 timezone: TimeZone,
421 drop_on_error: bool,
422 drop_on_abort: bool,
423 reroute_dropped: bool,
424 runner: Runner,
425 metric_tag_values: MetricTagValues,
426}
427
428pub trait VrlRunner {
429 fn run(
430 &mut self,
431 target: &mut VrlTarget,
432 program: &Program,
433 timezone: &TimeZone,
434 ) -> std::result::Result<Value, Terminate>;
435}
436
437#[derive(Debug)]
438pub struct AstRunner {
439 pub runtime: Runtime,
440}
441
442impl Clone for AstRunner {
443 fn clone(&self) -> Self {
444 Self {
445 runtime: Runtime::default(),
446 }
447 }
448}
449
450impl VrlRunner for AstRunner {
451 fn run(
452 &mut self,
453 target: &mut VrlTarget,
454 program: &Program,
455 timezone: &TimeZone,
456 ) -> std::result::Result<Value, Terminate> {
457 let result = self.runtime.resolve(target, program, timezone);
458 self.runtime.clear();
459 result
460 }
461}
462
463impl Remap<AstRunner> {
464 pub fn new_ast(
465 config: RemapConfig,
466 context: &TransformContext,
467 ) -> crate::Result<(Self, String)> {
468 let (program, warnings, _) = config.compile_vrl_program(
469 context.enrichment_tables.clone(),
470 context.metrics_storage.clone(),
471 context.merged_schema_definition.clone(),
472 )?;
473
474 let runtime = Runtime::default();
475 let runner = AstRunner { runtime };
476
477 Self::new(config, context, program, runner).map(|remap| (remap, warnings))
478 }
479}
480
481impl<Runner> Remap<Runner>
482where
483 Runner: VrlRunner,
484{
485 fn new(
486 config: RemapConfig,
487 context: &TransformContext,
488 program: Program,
489 runner: Runner,
490 ) -> crate::Result<Self> {
491 Ok(Remap {
492 component_key: context.key.clone(),
493 program,
494 timezone: config
495 .timezone
496 .unwrap_or_else(|| context.globals.timezone()),
497 drop_on_error: config.drop_on_error,
498 drop_on_abort: config.drop_on_abort,
499 reroute_dropped: config.reroute_dropped,
500 runner,
501 metric_tag_values: config.metric_tag_values,
502 })
503 }
504
505 #[cfg(test)]
506 const fn runner(&self) -> &Runner {
507 &self.runner
508 }
509
510 fn dropped_data(&self, reason: &str, error: ExpressionError) -> serde_json::Value {
511 let message = error
512 .notes()
513 .iter()
514 .rfind(|note| matches!(note, Note::UserErrorMessage(_)))
515 .map(|note| note.to_string())
516 .unwrap_or_else(|| error.to_string());
517 serde_json::json!({
518 "reason": reason,
519 "message": message,
520 "component_id": self.component_key,
521 "component_type": "remap",
522 "component_kind": "transform",
523 })
524 }
525
526 fn annotate_dropped(&self, event: &mut Event, reason: &str, error: ExpressionError) {
527 match event {
528 Event::Log(log) => match log.namespace() {
529 LogNamespace::Legacy => {
530 if let Some(metadata_key) = log_schema().metadata_key() {
531 log.insert(
532 (PathPrefix::Event, metadata_key.concat(path!("dropped"))),
533 self.dropped_data(reason, error),
534 );
535 }
536 }
537 LogNamespace::Vector => {
538 log.insert(
539 metadata_path!("vector", "dropped"),
540 self.dropped_data(reason, error),
541 );
542 }
543 },
544 Event::Metric(metric) => {
545 if let Some(metadata_key) = log_schema().metadata_key() {
546 metric.replace_tag(format!("{metadata_key}.dropped.reason"), reason.into());
547 metric.replace_tag(
548 format!("{metadata_key}.dropped.component_id"),
549 self.component_key
550 .as_ref()
551 .map(ToString::to_string)
552 .unwrap_or_default(),
553 );
554 metric.replace_tag(
555 format!("{metadata_key}.dropped.component_type"),
556 "remap".into(),
557 );
558 metric.replace_tag(
559 format!("{metadata_key}.dropped.component_kind"),
560 "transform".into(),
561 );
562 }
563 }
564 Event::Trace(trace) => {
565 trace.maybe_insert(log_schema().metadata_key_target_path(), || {
566 self.dropped_data(reason, error).into()
567 });
568 }
569 }
570 }
571
572 fn run_vrl(&mut self, target: &mut VrlTarget) -> std::result::Result<Value, Terminate> {
573 self.runner.run(target, &self.program, &self.timezone)
574 }
575}
576
577impl<Runner> SyncTransform for Remap<Runner>
578where
579 Runner: VrlRunner + Clone + Send + Sync,
580{
581 fn transform(&mut self, event: Event, output: &mut TransformOutputsBuf) {
582 let forward_on_error = !self.drop_on_error || self.reroute_dropped;
593 let forward_on_abort = !self.drop_on_abort || self.reroute_dropped;
594 let original_event = if (self.program.info().fallible && forward_on_error)
595 || (self.program.info().abortable && forward_on_abort)
596 {
597 Some(event.clone())
598 } else {
599 None
600 };
601
602 let log_namespace = event
603 .maybe_as_log()
604 .map(|log| log.namespace())
605 .unwrap_or(LogNamespace::Legacy);
606
607 let mut target = VrlTarget::new(
608 event,
609 self.program.info(),
610 match self.metric_tag_values {
611 MetricTagValues::Single => false,
612 MetricTagValues::Full => true,
613 },
614 );
615 let result = self.run_vrl(&mut target);
616
617 match result {
618 Ok(_) => match target.into_events(log_namespace) {
619 TargetEvents::One(event) => push_default(event, output),
620 TargetEvents::Logs(events) => events.for_each(|event| push_default(event, output)),
621 TargetEvents::Traces(events) => {
622 events.for_each(|event| push_default(event, output))
623 }
624 },
625 Err(reason) => {
626 let (reason, error, drop) = match reason {
627 Terminate::Abort(error) => {
628 if !self.reroute_dropped {
629 emit!(RemapMappingAbort {
630 event_dropped: self.drop_on_abort,
631 });
632 }
633 ("abort", error, self.drop_on_abort)
634 }
635 Terminate::Error(error) => {
636 if !self.reroute_dropped {
637 emit!(RemapMappingError {
638 error: error.to_string(),
639 event_dropped: self.drop_on_error,
640 });
641 }
642 ("error", error, self.drop_on_error)
643 }
644 };
645
646 if !drop {
647 let event = original_event.expect("event will be set");
648
649 push_default(event, output);
650 } else if self.reroute_dropped {
651 let mut event = original_event.expect("event will be set");
652
653 self.annotate_dropped(&mut event, reason, error);
654 push_dropped(event, output);
655 }
656 }
657 }
658 }
659}
660
661#[inline]
662fn push_default(event: Event, output: &mut TransformOutputsBuf) {
663 output.push(None, event)
664}
665
666#[inline]
667fn push_dropped(event: Event, output: &mut TransformOutputsBuf) {
668 output.push(Some(DROPPED), event);
669}
670
671#[derive(Debug, Snafu)]
672pub enum BuildError {
673 #[snafu(display("must provide exactly one of `source` or `file` or `files` configuration"))]
674 SourceAndOrFileOrFiles,
675
676 #[snafu(display("Could not open vrl program {:?}: {}", path, source))]
677 FileOpenFailed { path: PathBuf, source: io::Error },
678 #[snafu(display("Could not read vrl program {:?}: {}", path, source))]
679 FileReadFailed { path: PathBuf, source: io::Error },
680}
681
682#[cfg(test)]
683mod tests {
684 use std::{
685 collections::{HashMap, HashSet},
686 sync::Arc,
687 };
688
689 use chrono::DateTime;
690 use indoc::{formatdoc, indoc};
691 use tokio::sync::mpsc;
692 use tokio_stream::wrappers::ReceiverStream;
693 use vector_lib::{config::GlobalOptions, event::EventMetadata, metric_tags};
694 use vrl::{btreemap, event_path, value::kind::Collection};
695
696 use super::*;
697 use crate::{
698 config::{ConfigBuilder, build_unit_tests},
699 event::{
700 LogEvent, Metric, Value,
701 metric::{MetricKind, MetricValue},
702 },
703 metrics::Controller,
704 schema,
705 test_util::components::{
706 COMPONENT_MULTIPLE_OUTPUTS_TESTS, assert_transform_compliance, init_test,
707 },
708 transforms::{OutputBuffer, test::create_topology},
709 };
710
711 fn test_default_schema_definition() -> schema::Definition {
712 schema::Definition::empty_legacy_namespace().with_event_field(
713 &owned_value_path!("a default field"),
714 Kind::integer().or_bytes(),
715 Some("default"),
716 )
717 }
718
719 fn test_dropped_schema_definition() -> schema::Definition {
720 schema::Definition::empty_legacy_namespace().with_event_field(
721 &owned_value_path!("a dropped field"),
722 Kind::boolean().or_null(),
723 Some("dropped"),
724 )
725 }
726
727 fn remap(config: RemapConfig) -> Result<Remap<AstRunner>> {
728 let schema_definitions = HashMap::from([
729 (
730 None,
731 [("source".into(), test_default_schema_definition())].into(),
732 ),
733 (
734 Some(DROPPED.to_owned()),
735 [("source".into(), test_dropped_schema_definition())].into(),
736 ),
737 ]);
738
739 Remap::new_ast(config, &TransformContext::new_test(schema_definitions))
740 .map(|(remap, _)| remap)
741 }
742
743 #[test]
744 fn generate_config() {
745 crate::test_util::test_generate_config::<RemapConfig>();
746 }
747
748 #[test]
749 fn config_missing_source_and_file() {
750 let config = RemapConfig {
751 source: None,
752 file: None,
753 ..Default::default()
754 };
755
756 let err = remap(config).unwrap_err().to_string();
757 assert_eq!(
758 &err,
759 "must provide exactly one of `source` or `file` or `files` configuration"
760 )
761 }
762
763 #[test]
764 fn config_both_source_and_file() {
765 let config = RemapConfig {
766 source: Some("".to_owned()),
767 file: Some("".into()),
768 ..Default::default()
769 };
770
771 let err = remap(config).unwrap_err().to_string();
772 assert_eq!(
773 &err,
774 "must provide exactly one of `source` or `file` or `files` configuration"
775 )
776 }
777
778 fn get_field_string(event: &Event, field: &str) -> String {
779 event
780 .as_log()
781 .get(&vrl::path::parse_target_path(field).unwrap())
782 .unwrap()
783 .to_string_lossy()
784 .into_owned()
785 }
786
787 #[test]
788 fn check_remap_doesnt_share_state_between_events() {
789 let conf = RemapConfig {
790 source: Some(".foo = .sentinel".to_string()),
791 file: None,
792 drop_on_error: true,
793 drop_on_abort: false,
794 ..Default::default()
795 };
796 let mut tform = remap(conf).unwrap();
797 assert!(tform.runner().runtime.is_empty());
798
799 let event1 = {
800 let mut event1 = LogEvent::from("event1");
801 event1.insert(vrl::event_path!("sentinel"), "bar");
802 Event::from(event1)
803 };
804 let result1 = transform_one(&mut tform, event1).unwrap();
805 assert_eq!(get_field_string(&result1, "message"), "event1");
806 assert_eq!(get_field_string(&result1, "foo"), "bar");
807 assert!(tform.runner().runtime.is_empty());
808
809 let event2 = {
810 let event2 = LogEvent::from("event2");
811 Event::from(event2)
812 };
813 let result2 = transform_one(&mut tform, event2).unwrap();
814 assert_eq!(get_field_string(&result2, "message"), "event2");
815 assert_eq!(result2.as_log().get(event_path!("foo")), Some(&Value::Null));
816 assert!(tform.runner().runtime.is_empty());
817 }
818
819 #[test]
820 fn remap_return_raw_string_vector_namespace() {
821 let initial_definition = Definition::default_for_namespace(&[LogNamespace::Vector].into());
822
823 let event = {
824 let mut metadata = EventMetadata::default()
825 .with_schema_definition(&Arc::new(initial_definition.clone()));
826 metadata
828 .value_mut()
829 .insert(&owned_value_path!("vector"), BTreeMap::new());
830
831 let mut event = LogEvent::new_with_metadata(metadata);
832 event.insert(event_path!("copy_from"), "buz");
833 Event::from(event)
834 };
835
836 let conf = RemapConfig {
837 source: Some(r#" . = "root string";"#.to_string()),
838 file: None,
839 drop_on_error: true,
840 drop_on_abort: false,
841 ..Default::default()
842 };
843 let mut tform = remap(conf.clone()).unwrap();
844 let result = transform_one(&mut tform, event).unwrap();
845 assert_eq!(get_field_string(&result, "."), "root string");
846
847 let mut outputs = conf.outputs(
848 &Default::default(),
849 &[(OutputId::dummy(), initial_definition)],
850 );
851
852 assert_eq!(outputs.len(), 1);
853 let output = outputs.pop().unwrap();
854 assert_eq!(output.port, None);
855 let actual_schema_def = output.schema_definitions(true)[&OutputId::dummy()].clone();
856 let expected_schema =
857 Definition::new(Kind::bytes(), Kind::any_object(), [LogNamespace::Vector]);
858 assert_eq!(actual_schema_def, expected_schema);
859 }
860
861 #[test]
862 fn check_remap_adds() {
863 let event = {
864 let mut event = LogEvent::from("augment me");
865 event.insert(event_path!("copy_from"), "buz");
866 Event::from(event)
867 };
868
869 let conf = RemapConfig {
870 source: Some(
871 r#" .foo = "bar"
872 .bar = "baz"
873 .copy = .copy_from
874"#
875 .to_string(),
876 ),
877 file: None,
878 drop_on_error: true,
879 drop_on_abort: false,
880 ..Default::default()
881 };
882 let mut tform = remap(conf).unwrap();
883 let result = transform_one(&mut tform, event).unwrap();
884 assert_eq!(get_field_string(&result, "message"), "augment me");
885 assert_eq!(get_field_string(&result, "copy_from"), "buz");
886 assert_eq!(get_field_string(&result, "foo"), "bar");
887 assert_eq!(get_field_string(&result, "bar"), "baz");
888 assert_eq!(get_field_string(&result, "copy"), "buz");
889 }
890
891 #[test]
892 fn check_remap_emits_multiple() {
893 let event = {
894 let mut event = LogEvent::from("augment me");
895 event.insert(
896 event_path!("events"),
897 vec![btreemap!("message" => "foo"), btreemap!("message" => "bar")],
898 );
899 Event::from(event)
900 };
901
902 let conf = RemapConfig {
903 source: Some(
904 indoc! {r"
905 . = .events
906 "}
907 .to_owned(),
908 ),
909 file: None,
910 drop_on_error: true,
911 drop_on_abort: false,
912 ..Default::default()
913 };
914 let mut tform = remap(conf).unwrap();
915
916 let out = collect_outputs(&mut tform, event);
917 assert_eq!(2, out.primary.len());
918 let mut result = out.primary.into_events();
919
920 let r = result.next().unwrap();
921 assert_eq!(get_field_string(&r, "message"), "foo");
922 let r = result.next().unwrap();
923 assert_eq!(get_field_string(&r, "message"), "bar");
924 }
925
926 #[test]
927 fn check_remap_error() {
928 let event = {
929 let mut event = Event::Log(LogEvent::from("augment me"));
930 event.as_mut_log().insert(event_path!("bar"), "is a string");
931 event
932 };
933
934 let conf = RemapConfig {
935 source: Some(formatdoc! {r#"
936 .foo = "foo"
937 .not_an_int = int!(.bar)
938 .baz = 12
939 "#}),
940 file: None,
941 drop_on_error: false,
942 drop_on_abort: false,
943 ..Default::default()
944 };
945 let mut tform = remap(conf).unwrap();
946
947 let event = transform_one(&mut tform, event).unwrap();
948
949 assert_eq!(
950 event.as_log().get(event_path!("bar")),
951 Some(&Value::from("is a string"))
952 );
953 assert!(event.as_log().get(event_path!("foo")).is_none());
954 assert!(event.as_log().get(event_path!("baz")).is_none());
955 }
956
957 #[test]
958 fn check_remap_error_drop() {
959 let event = {
960 let mut event = Event::Log(LogEvent::from("augment me"));
961 event.as_mut_log().insert(event_path!("bar"), "is a string");
962 event
963 };
964
965 let conf = RemapConfig {
966 source: Some(formatdoc! {r#"
967 .foo = "foo"
968 .not_an_int = int!(.bar)
969 .baz = 12
970 "#}),
971 file: None,
972 drop_on_error: true,
973 drop_on_abort: false,
974 ..Default::default()
975 };
976 let mut tform = remap(conf).unwrap();
977
978 assert!(transform_one(&mut tform, event).is_none())
979 }
980
981 #[test]
982 fn check_remap_error_infallible() {
983 let event = {
984 let mut event = Event::Log(LogEvent::from("augment me"));
985 event.as_mut_log().insert(event_path!("bar"), "is a string");
986 event
987 };
988
989 let conf = RemapConfig {
990 source: Some(formatdoc! {r#"
991 .foo = "foo"
992 .baz = 12
993 "#}),
994 file: None,
995 drop_on_error: false,
996 drop_on_abort: false,
997 ..Default::default()
998 };
999 let mut tform = remap(conf).unwrap();
1000
1001 let event = transform_one(&mut tform, event).unwrap();
1002
1003 assert_eq!(
1004 event.as_log().get(event_path!("foo")),
1005 Some(&Value::from("foo"))
1006 );
1007 assert_eq!(
1008 event.as_log().get(event_path!("bar")),
1009 Some(&Value::from("is a string"))
1010 );
1011 assert_eq!(
1012 event.as_log().get(event_path!("baz")),
1013 Some(&Value::from(12))
1014 );
1015 }
1016
1017 #[test]
1018 fn check_remap_abort() {
1019 let event = {
1020 let mut event = Event::Log(LogEvent::from("augment me"));
1021 event.as_mut_log().insert(event_path!("bar"), "is a string");
1022 event
1023 };
1024
1025 let conf = RemapConfig {
1026 source: Some(formatdoc! {r#"
1027 .foo = "foo"
1028 abort
1029 .baz = 12
1030 "#}),
1031 file: None,
1032 drop_on_error: false,
1033 drop_on_abort: false,
1034 ..Default::default()
1035 };
1036 let mut tform = remap(conf).unwrap();
1037
1038 let event = transform_one(&mut tform, event).unwrap();
1039
1040 assert_eq!(
1041 event.as_log().get(event_path!("bar")),
1042 Some(&Value::from("is a string"))
1043 );
1044 assert!(event.as_log().get(event_path!("foo")).is_none());
1045 assert!(event.as_log().get(event_path!("baz")).is_none());
1046 }
1047
1048 #[test]
1049 fn check_remap_abort_drop() {
1050 let event = {
1051 let mut event = Event::Log(LogEvent::from("augment me"));
1052 event.as_mut_log().insert(event_path!("bar"), "is a string");
1053 event
1054 };
1055
1056 let conf = RemapConfig {
1057 source: Some(formatdoc! {r#"
1058 .foo = "foo"
1059 abort
1060 .baz = 12
1061 "#}),
1062 file: None,
1063 drop_on_error: false,
1064 drop_on_abort: true,
1065 ..Default::default()
1066 };
1067 let mut tform = remap(conf).unwrap();
1068
1069 assert!(transform_one(&mut tform, event).is_none())
1070 }
1071
1072 #[test]
1073 fn check_remap_metric() {
1074 let metric = Event::Metric(Metric::new(
1075 "counter",
1076 MetricKind::Absolute,
1077 MetricValue::Counter { value: 1.0 },
1078 ));
1079 let metadata = metric.metadata().clone();
1080
1081 let conf = RemapConfig {
1082 source: Some(
1083 r#".tags.host = "zoobub"
1084 .name = "zork"
1085 .namespace = "zerk"
1086 .kind = "incremental""#
1087 .to_string(),
1088 ),
1089 file: None,
1090 drop_on_error: true,
1091 drop_on_abort: false,
1092 ..Default::default()
1093 };
1094 let mut tform = remap(conf).unwrap();
1095
1096 let result = transform_one(&mut tform, metric).unwrap();
1097 assert_eq!(
1098 result,
1099 Event::Metric(
1100 Metric::new_with_metadata(
1101 "zork",
1102 MetricKind::Incremental,
1103 MetricValue::Counter { value: 1.0 },
1104 metadata
1107 )
1108 .with_namespace(Some("zerk"))
1109 .with_tags(Some(metric_tags! {
1110 "host" => "zoobub",
1111 }))
1112 )
1113 );
1114 }
1115
1116 #[test]
1117 fn remap_timezone_fallback() {
1118 let error = Event::from_json_value(
1119 serde_json::json!({"timestamp": "2022-12-27 00:00:00"}),
1120 LogNamespace::Legacy,
1121 )
1122 .unwrap();
1123 let conf = RemapConfig {
1124 source: Some(formatdoc! {r#"
1125 .timestamp = parse_timestamp!(.timestamp, format: "%Y-%m-%d %H:%M:%S")
1126 "#}),
1127 drop_on_error: true,
1128 drop_on_abort: true,
1129 reroute_dropped: true,
1130 ..Default::default()
1131 };
1132 let context = TransformContext {
1133 key: Some(ComponentKey::from("remapper")),
1134 globals: GlobalOptions {
1135 timezone: Some(TimeZone::parse("America/Los_Angeles").unwrap()),
1136 ..Default::default()
1137 },
1138 ..Default::default()
1139 };
1140 let mut tform = Remap::new_ast(conf, &context).unwrap().0;
1141
1142 let output = transform_one_fallible(&mut tform, error).unwrap();
1143 let log = output.as_log();
1144 assert_eq!(
1145 log["timestamp"],
1146 DateTime::<chrono::Utc>::from(
1147 DateTime::parse_from_rfc3339("2022-12-27T00:00:00-08:00").unwrap()
1148 )
1149 .into()
1150 );
1151 }
1152
1153 #[test]
1154 fn remap_timezone_override() {
1155 let error = Event::from_json_value(
1156 serde_json::json!({"timestamp": "2022-12-27 00:00:00"}),
1157 LogNamespace::Legacy,
1158 )
1159 .unwrap();
1160 let conf = RemapConfig {
1161 source: Some(formatdoc! {r#"
1162 .timestamp = parse_timestamp!(.timestamp, format: "%Y-%m-%d %H:%M:%S")
1163 "#}),
1164 drop_on_error: true,
1165 drop_on_abort: true,
1166 reroute_dropped: true,
1167 timezone: Some(TimeZone::parse("America/Los_Angeles").unwrap()),
1168 ..Default::default()
1169 };
1170 let context = TransformContext {
1171 key: Some(ComponentKey::from("remapper")),
1172 globals: GlobalOptions {
1173 timezone: Some(TimeZone::parse("Etc/UTC").unwrap()),
1174 ..Default::default()
1175 },
1176 ..Default::default()
1177 };
1178 let mut tform = Remap::new_ast(conf, &context).unwrap().0;
1179
1180 let output = transform_one_fallible(&mut tform, error).unwrap();
1181 let log = output.as_log();
1182 assert_eq!(
1183 log["timestamp"],
1184 DateTime::<chrono::Utc>::from(
1185 DateTime::parse_from_rfc3339("2022-12-27T00:00:00-08:00").unwrap()
1186 )
1187 .into()
1188 );
1189 }
1190
1191 #[test]
1192 fn check_remap_branching() {
1193 let happy =
1194 Event::from_json_value(serde_json::json!({"hello": "world"}), LogNamespace::Legacy)
1195 .unwrap();
1196 let abort = Event::from_json_value(
1197 serde_json::json!({"hello": "goodbye"}),
1198 LogNamespace::Legacy,
1199 )
1200 .unwrap();
1201 let error =
1202 Event::from_json_value(serde_json::json!({"hello": 42}), LogNamespace::Legacy).unwrap();
1203
1204 let happy_metric = {
1205 let mut metric = Metric::new(
1206 "counter",
1207 MetricKind::Absolute,
1208 MetricValue::Counter { value: 1.0 },
1209 );
1210 metric.replace_tag("hello".into(), "world".into());
1211 Event::Metric(metric)
1212 };
1213
1214 let abort_metric = {
1215 let mut metric = Metric::new(
1216 "counter",
1217 MetricKind::Absolute,
1218 MetricValue::Counter { value: 1.0 },
1219 );
1220 metric.replace_tag("hello".into(), "goodbye".into());
1221 Event::Metric(metric)
1222 };
1223
1224 let error_metric = {
1225 let mut metric = Metric::new(
1226 "counter",
1227 MetricKind::Absolute,
1228 MetricValue::Counter { value: 1.0 },
1229 );
1230 metric.replace_tag("not_hello".into(), "oops".into());
1231 Event::Metric(metric)
1232 };
1233
1234 let conf = RemapConfig {
1235 source: Some(formatdoc! {r#"
1236 if exists(.tags) {{
1237 # metrics
1238 .tags.foo = "bar"
1239 if string!(.tags.hello) == "goodbye" {{
1240 abort
1241 }}
1242 }} else {{
1243 # logs
1244 .foo = "bar"
1245 if string(.hello) == "goodbye" {{
1246 abort
1247 }}
1248 }}
1249 "#}),
1250 drop_on_error: true,
1251 drop_on_abort: true,
1252 reroute_dropped: true,
1253 ..Default::default()
1254 };
1255 let schema_definitions = HashMap::from([
1256 (
1257 None,
1258 [("source".into(), test_default_schema_definition())].into(),
1259 ),
1260 (
1261 Some(DROPPED.to_owned()),
1262 [("source".into(), test_dropped_schema_definition())].into(),
1263 ),
1264 ]);
1265 let context = TransformContext {
1266 key: Some(ComponentKey::from("remapper")),
1267 schema_definitions,
1268 merged_schema_definition: schema::Definition::new_with_default_metadata(
1269 Kind::any_object(),
1270 [LogNamespace::Legacy],
1271 )
1272 .with_event_field(&owned_value_path!("hello"), Kind::bytes(), None),
1273 ..Default::default()
1274 };
1275 let mut tform = Remap::new_ast(conf, &context).unwrap().0;
1276
1277 let output = transform_one_fallible(&mut tform, happy).unwrap();
1278 let log = output.as_log();
1279 assert_eq!(log["hello"], "world".into());
1280 assert_eq!(log["foo"], "bar".into());
1281 assert!(!log.contains(event_path!("metadata")));
1282
1283 let output = transform_one_fallible(&mut tform, abort).unwrap_err();
1284 let log = output.as_log();
1285 assert_eq!(log["hello"], "goodbye".into());
1286 assert!(!log.contains(event_path!("foo")));
1287 assert_eq!(
1288 log["metadata"],
1289 serde_json::json!({
1290 "dropped": {
1291 "reason": "abort",
1292 "message": "aborted",
1293 "component_id": "remapper",
1294 "component_type": "remap",
1295 "component_kind": "transform",
1296 }
1297 })
1298 .try_into()
1299 .unwrap()
1300 );
1301
1302 let output = transform_one_fallible(&mut tform, error).unwrap_err();
1303 let log = output.as_log();
1304 assert_eq!(log["hello"], 42.into());
1305 assert!(!log.contains(event_path!("foo")));
1306 assert_eq!(
1307 log["metadata"],
1308 serde_json::json!({
1309 "dropped": {
1310 "reason": "error",
1311 "message": "function call error for \"string\" at (160:174): expected string, got integer",
1312 "component_id": "remapper",
1313 "component_type": "remap",
1314 "component_kind": "transform",
1315 }
1316 })
1317 .try_into()
1318 .unwrap()
1319 );
1320
1321 let output = transform_one_fallible(&mut tform, happy_metric).unwrap();
1322 similar_asserts::assert_eq!(
1323 output,
1324 Event::Metric(
1325 Metric::new_with_metadata(
1326 "counter",
1327 MetricKind::Absolute,
1328 MetricValue::Counter { value: 1.0 },
1329 EventMetadata::default()
1332 .with_schema_definition(output.metadata().schema_definition()),
1333 )
1334 .with_tags(Some(metric_tags! {
1335 "hello" => "world",
1336 "foo" => "bar",
1337 }))
1338 )
1339 );
1340
1341 let output = transform_one_fallible(&mut tform, abort_metric).unwrap_err();
1342 similar_asserts::assert_eq!(
1343 output,
1344 Event::Metric(
1345 Metric::new_with_metadata(
1346 "counter",
1347 MetricKind::Absolute,
1348 MetricValue::Counter { value: 1.0 },
1349 EventMetadata::default()
1352 .with_schema_definition(output.metadata().schema_definition()),
1353 )
1354 .with_tags(Some(metric_tags! {
1355 "hello" => "goodbye",
1356 "metadata.dropped.reason" => "abort",
1357 "metadata.dropped.component_id" => "remapper",
1358 "metadata.dropped.component_type" => "remap",
1359 "metadata.dropped.component_kind" => "transform",
1360 }))
1361 )
1362 );
1363
1364 let output = transform_one_fallible(&mut tform, error_metric).unwrap_err();
1365 similar_asserts::assert_eq!(
1366 output,
1367 Event::Metric(
1368 Metric::new_with_metadata(
1369 "counter",
1370 MetricKind::Absolute,
1371 MetricValue::Counter { value: 1.0 },
1372 EventMetadata::default()
1375 .with_schema_definition(output.metadata().schema_definition()),
1376 )
1377 .with_tags(Some(metric_tags! {
1378 "not_hello" => "oops",
1379 "metadata.dropped.reason" => "error",
1380 "metadata.dropped.component_id" => "remapper",
1381 "metadata.dropped.component_type" => "remap",
1382 "metadata.dropped.component_kind" => "transform",
1383 }))
1384 )
1385 );
1386 }
1387
1388 #[test]
1389 fn check_remap_branching_assert_with_message() {
1390 let error_trigger_assert_custom_message =
1391 Event::from_json_value(serde_json::json!({"hello": 42}), LogNamespace::Legacy).unwrap();
1392 let error_trigger_default_assert_message =
1393 Event::from_json_value(serde_json::json!({"hello": 0}), LogNamespace::Legacy).unwrap();
1394 let conf = RemapConfig {
1395 source: Some(formatdoc! {r#"
1396 assert_eq!(.hello, 0, "custom message here")
1397 assert_eq!(.hello, 1)
1398 "#}),
1399 drop_on_error: true,
1400 drop_on_abort: true,
1401 reroute_dropped: true,
1402 ..Default::default()
1403 };
1404 let context = TransformContext {
1405 key: Some(ComponentKey::from("remapper")),
1406 ..Default::default()
1407 };
1408 let mut tform = Remap::new_ast(conf, &context).unwrap().0;
1409
1410 let output =
1411 transform_one_fallible(&mut tform, error_trigger_assert_custom_message).unwrap_err();
1412 let log = output.as_log();
1413 assert_eq!(log["hello"], 42.into());
1414 assert!(!log.contains(event_path!("foo")));
1415 assert_eq!(
1416 log["metadata"],
1417 serde_json::json!({
1418 "dropped": {
1419 "reason": "error",
1420 "message": "custom message here",
1421 "component_id": "remapper",
1422 "component_type": "remap",
1423 "component_kind": "transform",
1424 }
1425 })
1426 .try_into()
1427 .unwrap()
1428 );
1429
1430 let output =
1431 transform_one_fallible(&mut tform, error_trigger_default_assert_message).unwrap_err();
1432 let log = output.as_log();
1433 assert_eq!(log["hello"], 0.into());
1434 assert!(!log.contains(event_path!("foo")));
1435 assert_eq!(
1436 log["metadata"],
1437 serde_json::json!({
1438 "dropped": {
1439 "reason": "error",
1440 "message": "function call error for \"assert_eq\" at (45:66): assertion failed: 0 == 1",
1441 "component_id": "remapper",
1442 "component_type": "remap",
1443 "component_kind": "transform",
1444 }
1445 })
1446 .try_into()
1447 .unwrap()
1448 );
1449 }
1450
1451 #[test]
1452 fn check_remap_branching_abort_with_message() {
1453 let error =
1454 Event::from_json_value(serde_json::json!({"hello": 42}), LogNamespace::Legacy).unwrap();
1455 let conf = RemapConfig {
1456 source: Some(formatdoc! {r#"
1457 abort "custom message here"
1458 "#}),
1459 drop_on_error: true,
1460 drop_on_abort: true,
1461 reroute_dropped: true,
1462 ..Default::default()
1463 };
1464 let context = TransformContext {
1465 key: Some(ComponentKey::from("remapper")),
1466 ..Default::default()
1467 };
1468 let mut tform = Remap::new_ast(conf, &context).unwrap().0;
1469
1470 let output = transform_one_fallible(&mut tform, error).unwrap_err();
1471 let log = output.as_log();
1472 assert_eq!(log["hello"], 42.into());
1473 assert!(!log.contains(event_path!("foo")));
1474 assert_eq!(
1475 log["metadata"],
1476 serde_json::json!({
1477 "dropped": {
1478 "reason": "abort",
1479 "message": "custom message here",
1480 "component_id": "remapper",
1481 "component_type": "remap",
1482 "component_kind": "transform",
1483 }
1484 })
1485 .try_into()
1486 .unwrap()
1487 );
1488 }
1489
1490 #[test]
1491 fn check_remap_branching_disabled() {
1492 let happy =
1493 Event::from_json_value(serde_json::json!({"hello": "world"}), LogNamespace::Legacy)
1494 .unwrap();
1495 let abort = Event::from_json_value(
1496 serde_json::json!({"hello": "goodbye"}),
1497 LogNamespace::Legacy,
1498 )
1499 .unwrap();
1500 let error =
1501 Event::from_json_value(serde_json::json!({"hello": 42}), LogNamespace::Legacy).unwrap();
1502
1503 let conf = RemapConfig {
1504 source: Some(formatdoc! {r#"
1505 if exists(.tags) {{
1506 # metrics
1507 .tags.foo = "bar"
1508 if string!(.tags.hello) == "goodbye" {{
1509 abort
1510 }}
1511 }} else {{
1512 # logs
1513 .foo = "bar"
1514 if string!(.hello) == "goodbye" {{
1515 abort
1516 }}
1517 }}
1518 "#}),
1519 drop_on_error: true,
1520 drop_on_abort: true,
1521 reroute_dropped: false,
1522 ..Default::default()
1523 };
1524
1525 let schema_definition = schema::Definition::new_with_default_metadata(
1526 Kind::any_object(),
1527 [LogNamespace::Legacy],
1528 )
1529 .with_event_field(&owned_value_path!("foo"), Kind::any(), None)
1530 .with_event_field(&owned_value_path!("tags"), Kind::any(), None);
1531
1532 assert_eq!(
1533 conf.outputs(
1534 &Default::default(),
1535 &[(
1536 "test".into(),
1537 schema::Definition::new_with_default_metadata(
1538 Kind::any_object(),
1539 [LogNamespace::Legacy]
1540 )
1541 )],
1542 ),
1543 vec![TransformOutput::new(
1544 DataType::all_bits(),
1545 [("test".into(), schema_definition)].into()
1546 )]
1547 );
1548
1549 let context = TransformContext {
1550 key: Some(ComponentKey::from("remapper")),
1551 ..Default::default()
1552 };
1553 let mut tform = Remap::new_ast(conf, &context).unwrap().0;
1554
1555 let output = transform_one_fallible(&mut tform, happy).unwrap();
1556 let log = output.as_log();
1557 assert_eq!(log["hello"], "world".into());
1558 assert_eq!(log["foo"], "bar".into());
1559 assert!(!log.contains(event_path!("metadata")));
1560
1561 let out = collect_outputs(&mut tform, abort);
1562 assert!(out.primary.is_empty());
1563 assert!(out.named[DROPPED].is_empty());
1564
1565 let out = collect_outputs(&mut tform, error);
1566 assert!(out.primary.is_empty());
1567 assert!(out.named[DROPPED].is_empty());
1568 }
1569
1570 #[tokio::test]
1571 async fn check_remap_branching_metrics_with_output() {
1572 init_test();
1573
1574 let config: ConfigBuilder = serde_yaml::from_str(indoc! {"
1575 transforms:
1576 foo:
1577 inputs: []
1578 type: remap
1579 drop_on_abort: true
1580 reroute_dropped: true
1581 source: abort
1582 tests:
1583 - name: metric output
1584 input:
1585 insert_at: foo
1586 value: none
1587 outputs:
1588 - extract_from: foo.dropped
1589 conditions:
1590 - type: vrl
1591 source: \"true\"
1592 "})
1593 .unwrap();
1594
1595 let mut tests = build_unit_tests(config).await.unwrap();
1596 assert!(tests.remove(0).run().await.errors.is_empty());
1597 COMPONENT_MULTIPLE_OUTPUTS_TESTS.assert(&["output"]);
1599 }
1600
1601 struct CollectedOutput {
1602 primary: OutputBuffer,
1603 named: HashMap<String, OutputBuffer>,
1604 }
1605
1606 fn collect_outputs(ft: &mut dyn SyncTransform, event: Event) -> CollectedOutput {
1607 let mut outputs = TransformOutputsBuf::new_with_capacity(
1608 vec![
1609 TransformOutput::new(DataType::all_bits(), HashMap::new()),
1610 TransformOutput::new(DataType::all_bits(), HashMap::new()).with_port(DROPPED),
1611 ],
1612 1,
1613 );
1614
1615 ft.transform(event, &mut outputs);
1616
1617 CollectedOutput {
1618 primary: outputs.take_primary(),
1619 named: outputs.take_all_named(),
1620 }
1621 }
1622
1623 fn transform_one(ft: &mut dyn SyncTransform, event: Event) -> Option<Event> {
1624 let out = collect_outputs(ft, event);
1625 assert_eq!(0, out.named.values().map(|v| v.len()).sum::<usize>());
1626 assert!(out.primary.len() <= 1);
1627 out.primary.into_events().next()
1628 }
1629
1630 fn transform_one_fallible(
1631 ft: &mut dyn SyncTransform,
1632 event: Event,
1633 ) -> std::result::Result<Event, Event> {
1634 let mut outputs = TransformOutputsBuf::new_with_capacity(
1635 vec![
1636 TransformOutput::new(DataType::all_bits(), HashMap::new()),
1637 TransformOutput::new(DataType::all_bits(), HashMap::new()).with_port(DROPPED),
1638 ],
1639 1,
1640 );
1641
1642 ft.transform(event, &mut outputs);
1643
1644 let mut buf = outputs.drain().collect::<Vec<_>>();
1645 let mut err_buf = outputs.drain_named(DROPPED).collect::<Vec<_>>();
1646
1647 assert!(buf.len() < 2);
1648 assert!(err_buf.len() < 2);
1649 match (buf.pop(), err_buf.pop()) {
1650 (Some(good), None) => Ok(good),
1651 (None, Some(bad)) => Err(bad),
1652 (a, b) => panic!("expected output xor error output, got {a:?} and {b:?}"),
1653 }
1654 }
1655
1656 #[tokio::test]
1657 async fn emits_internal_events() {
1658 assert_transform_compliance(async move {
1659 let config = RemapConfig {
1660 source: Some("abort".to_owned()),
1661 drop_on_abort: true,
1662 ..Default::default()
1663 };
1664
1665 let (tx, rx) = mpsc::channel(1);
1666 let (topology, mut out) = create_topology(ReceiverStream::new(rx), config).await;
1667
1668 let log = LogEvent::from("hello world");
1669 tx.send(log.into()).await.unwrap();
1670
1671 drop(tx);
1672 topology.stop().await;
1673 assert_eq!(out.recv().await, None);
1674 })
1675 .await
1676 }
1677
1678 #[test]
1679 fn test_combined_transforms_simple() {
1680 let transform1 = RemapConfig {
1685 source: Some(r#".thing = "potato""#.to_string()),
1686 ..Default::default()
1687 };
1688
1689 let transform2 = RemapConfig {
1690 source: Some(".thang = .thing".to_string()),
1691 ..Default::default()
1692 };
1693
1694 let outputs1 = transform1.outputs(
1695 &Default::default(),
1696 &[("in".into(), schema::Definition::default_legacy_namespace())],
1697 );
1698
1699 assert_eq!(
1700 vec![TransformOutput::new(
1701 DataType::all_bits(),
1702 [(
1704 "in".into(),
1705 Definition::default_legacy_namespace().with_event_field(
1706 &owned_value_path!("thing"),
1707 Kind::bytes(),
1708 None
1709 ),
1710 )]
1711 .into()
1712 )],
1713 outputs1
1714 );
1715
1716 let outputs2 = transform2.outputs(
1717 &Default::default(),
1718 &[(
1719 "in1".into(),
1720 outputs1[0].schema_definitions(true)[&"in".into()].clone(),
1721 )],
1722 );
1723
1724 assert_eq!(
1725 vec![TransformOutput::new(
1726 DataType::all_bits(),
1727 [(
1728 "in1".into(),
1729 Definition::default_legacy_namespace()
1730 .with_event_field(&owned_value_path!("thing"), Kind::bytes(), None)
1731 .with_event_field(&owned_value_path!("thang"), Kind::bytes(), None),
1732 )]
1733 .into(),
1734 )],
1735 outputs2
1736 );
1737 }
1738
1739 #[test]
1740 fn test_combined_transforms_unnest() {
1741 let transform1 = RemapConfig {
1746 source: Some(
1747 indoc! {
1748 r#"
1749 .thing = [{"cabbage": 32}, {"parsnips": 45}]
1750 . = unnest(.thing)
1751 "#
1752 }
1753 .to_string(),
1754 ),
1755 ..Default::default()
1756 };
1757
1758 let transform2 = RemapConfig {
1759 source: Some(r#".thang = .thing.cabbage || "beetroot""#.to_string()),
1760 ..Default::default()
1761 };
1762
1763 let outputs1 = transform1.outputs(
1764 &Default::default(),
1765 &[(
1766 "in".into(),
1767 schema::Definition::new_with_default_metadata(
1768 Kind::any_object(),
1769 [LogNamespace::Legacy],
1770 ),
1771 )],
1772 );
1773
1774 assert_eq!(
1775 vec![TransformOutput::new(
1776 DataType::all_bits(),
1777 [(
1778 "in".into(),
1779 Definition::new_with_default_metadata(
1780 Kind::any_object(),
1781 [LogNamespace::Legacy]
1782 )
1783 .with_event_field(
1784 &owned_value_path!("thing"),
1785 Kind::object(Collection::from(BTreeMap::from([
1786 ("cabbage".into(), Kind::integer().or_undefined(),),
1787 ("parsnips".into(), Kind::integer().or_undefined(),)
1788 ]))),
1789 None
1790 ),
1791 )]
1792 .into(),
1793 )],
1794 outputs1
1795 );
1796
1797 let outputs2 = transform2.outputs(
1798 &Default::default(),
1799 &[(
1800 "in1".into(),
1801 outputs1[0].schema_definitions(true)[&"in".into()].clone(),
1802 )],
1803 );
1804
1805 assert_eq!(
1806 vec![TransformOutput::new(
1807 DataType::all_bits(),
1808 [(
1809 "in1".into(),
1810 Definition::default_legacy_namespace()
1811 .with_event_field(
1812 &owned_value_path!("thing"),
1813 Kind::object(Collection::from(BTreeMap::from([
1814 ("cabbage".into(), Kind::integer().or_undefined(),),
1815 ("parsnips".into(), Kind::integer().or_undefined(),)
1816 ]))),
1817 None
1818 )
1819 .with_event_field(
1820 &owned_value_path!("thang"),
1821 Kind::integer().or_null(),
1822 None
1823 ),
1824 )]
1825 .into(),
1826 )],
1827 outputs2
1828 );
1829 }
1830
1831 #[test]
1832 fn test_transform_abort() {
1833 let transform1 = RemapConfig {
1836 source: Some(r"abort".to_string()),
1837 ..Default::default()
1838 };
1839
1840 let outputs1 = transform1.outputs(
1841 &Default::default(),
1842 &[(
1843 "in".into(),
1844 schema::Definition::new_with_default_metadata(
1845 Kind::any_object(),
1846 [LogNamespace::Legacy],
1847 ),
1848 )],
1849 );
1850
1851 assert_eq!(
1852 vec![TransformOutput::new(
1853 DataType::all_bits(),
1854 [(
1855 "in".into(),
1856 Definition::new_with_default_metadata(
1857 Kind::any_object(),
1858 [LogNamespace::Legacy]
1859 ),
1860 )]
1861 .into(),
1862 )],
1863 outputs1
1864 );
1865 }
1866
1867 #[test]
1868 fn test_error_outputs() {
1869 let transform1 = RemapConfig {
1874 source: Some(r#". |= get_enrichment_table_record("carrot", {"id": .id})"#.to_string()),
1876 reroute_dropped: true,
1877 ..Default::default()
1878 };
1879
1880 let outputs1 = transform1.outputs(
1881 &Default::default(),
1882 &[(
1883 "in".into(),
1884 schema::Definition::new_with_default_metadata(
1885 Kind::any_object(),
1886 [LogNamespace::Legacy],
1887 ),
1888 )],
1889 );
1890
1891 assert_eq!(
1892 HashSet::from([None, Some("dropped".to_string())]),
1893 outputs1
1894 .into_iter()
1895 .map(|output| output.port)
1896 .collect::<HashSet<_>>()
1897 );
1898 }
1899
1900 #[test]
1901 fn test_non_object_events() {
1902 let transform1 = RemapConfig {
1903 source: Some(r#". = "fish" "#.to_string()),
1905 ..Default::default()
1906 };
1907
1908 let outputs1 = transform1.outputs(
1909 &Default::default(),
1910 &[(
1911 "in".into(),
1912 schema::Definition::new_with_default_metadata(
1913 Kind::any_object(),
1914 [LogNamespace::Legacy],
1915 ),
1916 )],
1917 );
1918
1919 let wanted = schema::Definition::new_with_default_metadata(
1920 Kind::object(Collection::from_unknown(Kind::undefined())),
1921 [LogNamespace::Legacy],
1922 )
1923 .with_event_field(&owned_value_path!("message"), Kind::bytes(), None);
1924
1925 assert_eq!(
1926 HashMap::from([(OutputId::from("in"), wanted)]),
1927 outputs1[0].schema_definitions(true),
1928 );
1929 }
1930
1931 #[test]
1932 fn test_array_and_non_object_events() {
1933 let transform1 = RemapConfig {
1934 source: Some(
1935 indoc! {r#"
1936 if .lizard == true {
1937 .thing = [{"cabbage": 42}];
1938 . = unnest(.thing)
1939 } else {
1940 . = "fish"
1941 }
1942 "#}
1943 .to_string(),
1944 ),
1945 ..Default::default()
1946 };
1947
1948 let outputs1 = transform1.outputs(
1949 &Default::default(),
1950 &[(
1951 "in".into(),
1952 schema::Definition::new_with_default_metadata(
1953 Kind::any_object(),
1954 [LogNamespace::Legacy],
1955 ),
1956 )],
1957 );
1958
1959 let wanted = schema::Definition::new_with_default_metadata(
1960 Kind::any_object(),
1961 [LogNamespace::Legacy],
1962 )
1963 .with_event_field(&owned_value_path!("message"), Kind::any(), None)
1964 .with_event_field(
1965 &owned_value_path!("thing"),
1966 Kind::object(Collection::from(BTreeMap::from([(
1967 "cabbage".into(),
1968 Kind::integer(),
1969 )])))
1970 .or_undefined(),
1971 None,
1972 );
1973
1974 assert_eq!(
1975 HashMap::from([(OutputId::from("in"), wanted)]),
1976 outputs1[0].schema_definitions(true),
1977 );
1978 }
1979
1980 #[test]
1981 fn check_remap_array_vector_namespace() {
1982 let event = {
1983 let mut event = LogEvent::from("input");
1984 event
1986 .metadata_mut()
1987 .value_mut()
1988 .insert(vrl::path!("vector"), BTreeMap::new());
1989 Event::from(event)
1990 };
1991
1992 let conf = RemapConfig {
1993 source: Some(
1994 r". = [null]
1995"
1996 .to_string(),
1997 ),
1998 file: None,
1999 drop_on_error: true,
2000 drop_on_abort: false,
2001 ..Default::default()
2002 };
2003 let mut tform = remap(conf.clone()).unwrap();
2004 let result = transform_one(&mut tform, event).unwrap();
2005
2006 assert_eq!(result.as_log().get(event_path!()), Some(&Value::Null));
2008
2009 let outputs1 = conf.outputs(
2010 &Default::default(),
2011 &[(
2012 "in".into(),
2013 schema::Definition::new_with_default_metadata(
2014 Kind::any_object(),
2015 [LogNamespace::Vector],
2016 ),
2017 )],
2018 );
2019
2020 let wanted =
2021 schema::Definition::new_with_default_metadata(Kind::null(), [LogNamespace::Vector]);
2022
2023 assert_eq!(
2024 HashMap::from([(OutputId::from("in"), wanted)]),
2025 outputs1[0].schema_definitions(true),
2026 );
2027 }
2028
2029 fn assert_no_metrics(source: String) {
2030 vector_lib::metrics::init_test();
2031
2032 let config = RemapConfig {
2033 source: Some(source),
2034 drop_on_error: true,
2035 drop_on_abort: true,
2036 reroute_dropped: true,
2037 ..Default::default()
2038 };
2039 let mut ast_runner = remap(config).unwrap();
2040 let input_event =
2041 Event::from_json_value(serde_json::json!({"a": 42}), LogNamespace::Vector).unwrap();
2042 let dropped_event = transform_one_fallible(&mut ast_runner, input_event).unwrap_err();
2043 let dropped_log = dropped_event.as_log();
2044 assert_eq!(dropped_log.get(event_path!("a")), Some(&Value::from(42)));
2045
2046 let controller = Controller::get().expect("no controller");
2047 let metrics = controller
2048 .capture_metrics()
2049 .into_iter()
2050 .map(|metric| (metric.name().to_string(), metric))
2051 .collect::<BTreeMap<String, Metric>>();
2052 assert_eq!(metrics.get("component_discarded_events_total"), None);
2053 assert_eq!(metrics.get("component_errors_total"), None);
2054 }
2055 #[test]
2056 fn do_not_emit_metrics_when_dropped() {
2057 assert_no_metrics("abort".to_string());
2058 }
2059
2060 #[test]
2061 fn do_not_emit_metrics_when_errored() {
2062 assert_no_metrics("parse_key_value!(.message)".to_string());
2063 }
2064}