Skip to content

Commit c19938c

Browse files
authored
fix: remap behavior for root types when using the Vector namespace (vectordotdev#17807)
This fixes an issue with `remap` when using the `Vector` namespace, where assigning a non-collection value (such as a string) directly to the root ended up with that value nested under the `message` field. Now it stays on the root.
1 parent 3f6df61 commit c19938c

File tree

4 files changed

+271
-214
lines changed

4 files changed

+271
-214
lines changed

lib/vector-core/src/event/vrl_target.rs

+173-10
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ use lookup::{OwnedTargetPath, OwnedValuePath, PathPrefix};
66
use snafu::Snafu;
77
use vrl::compiler::value::VrlValueConvert;
88
use vrl::compiler::{ProgramInfo, SecretTarget, Target};
9-
use vrl::value::Value;
9+
use vrl::prelude::Collection;
10+
use vrl::value::{Kind, Value};
1011

1112
use super::{Event, EventMetadata, LogEvent, Metric, MetricKind, TraceEvent};
12-
use crate::config::log_schema;
13+
use crate::config::{log_schema, LogNamespace};
1314
use crate::event::metric::TagValue;
15+
use crate::schema::Definition;
1416

1517
const VALID_METRIC_PATHS_SET: &str = ".name, .namespace, .timestamp, .kind, .tags";
1618

@@ -114,11 +116,24 @@ impl VrlTarget {
114116
}
115117
}
116118

119+
/// Modifies a schema in the same way that the `into_events` function modifies the event
120+
pub fn modify_schema_definition_for_into_events(input: Definition) -> Definition {
121+
let log_namespaces = input.log_namespaces().clone();
122+
123+
// both namespaces merge arrays, but only `Legacy` moves field definitions into a "message" field.
124+
let merged_arrays = merge_array_definitions(input);
125+
Definition::combine_log_namespaces(
126+
&log_namespaces,
127+
move_field_definitions_into_message(merged_arrays.clone()),
128+
merged_arrays,
129+
)
130+
}
131+
117132
/// Turn the target back into events.
118133
///
119134
/// This returns an iterator of events as one event can be turned into multiple by assigning an
120135
/// array to `.` in VRL.
121-
pub fn into_events(self) -> TargetEvents {
136+
pub fn into_events(self, log_namespace: LogNamespace) -> TargetEvents {
122137
match self {
123138
VrlTarget::LogEvent(value, metadata) => match value {
124139
value @ Value::Object(_) => {
@@ -131,11 +146,16 @@ impl VrlTarget {
131146
_marker: PhantomData,
132147
}),
133148

134-
v => {
135-
let mut log = LogEvent::new_with_metadata(metadata);
136-
log.insert(log_schema().message_key(), v);
137-
TargetEvents::One(log.into())
138-
}
149+
v => match log_namespace {
150+
LogNamespace::Vector => {
151+
TargetEvents::One(LogEvent::from_parts(v, metadata).into())
152+
}
153+
LogNamespace::Legacy => {
154+
let mut log = LogEvent::new_with_metadata(metadata);
155+
log.insert(log_schema().message_key(), v);
156+
TargetEvents::One(log.into())
157+
}
158+
},
139159
},
140160
VrlTarget::Trace(value, metadata) => match value {
141161
value @ Value::Object(_) => {
@@ -174,6 +194,53 @@ impl VrlTarget {
174194
}
175195
}
176196

197+
/// If the VRL returns a value that is not an array (see [`merge_array_definitions`]),
198+
/// or an object, that data is moved into the `message` field.
199+
fn move_field_definitions_into_message(mut definition: Definition) -> Definition {
200+
let mut message = definition.event_kind().clone();
201+
message.remove_object();
202+
message.remove_array();
203+
204+
if !message.is_never() {
205+
// We need to add the given message type to a field called `message`
206+
// in the event.
207+
let message = Kind::object(Collection::from(BTreeMap::from([(
208+
log_schema().message_key().into(),
209+
message,
210+
)])));
211+
212+
definition.event_kind_mut().remove_bytes();
213+
definition.event_kind_mut().remove_integer();
214+
definition.event_kind_mut().remove_float();
215+
definition.event_kind_mut().remove_boolean();
216+
definition.event_kind_mut().remove_timestamp();
217+
definition.event_kind_mut().remove_regex();
218+
definition.event_kind_mut().remove_null();
219+
220+
*definition.event_kind_mut() = definition.event_kind().union(message);
221+
}
222+
223+
definition
224+
}
225+
226+
/// If the transform returns an array, the elements of this array will be separated
227+
/// out into it's individual elements and passed downstream.
228+
///
229+
/// The potential types that the transform can output are any of the arrays
230+
/// elements or any non-array elements that are within the definition. All these
231+
/// definitions need to be merged together.
232+
fn merge_array_definitions(mut definition: Definition) -> Definition {
233+
if let Some(array) = definition.event_kind().as_array() {
234+
let array_kinds = array.reduced_kind();
235+
236+
let kind = definition.event_kind_mut();
237+
kind.remove_array();
238+
*kind = kind.union(array_kinds);
239+
}
240+
241+
definition
242+
}
243+
177244
fn set_metric_tag_values(name: String, value: &Value, metric: &mut Metric, multi_value_tags: bool) {
178245
if multi_value_tags {
179246
let tag_values = value
@@ -589,11 +656,107 @@ mod test {
589656
use lookup::owned_value_path;
590657
use similar_asserts::assert_eq;
591658
use vrl::btreemap;
659+
use vrl::value::kind::Index;
592660

593661
use super::super::MetricValue;
594662
use super::*;
595663
use crate::metric_tags;
596664

665+
#[test]
666+
fn test_field_definitions_in_message() {
667+
let definition =
668+
Definition::new_with_default_metadata(Kind::bytes(), [LogNamespace::Legacy]);
669+
assert_eq!(
670+
Definition::new_with_default_metadata(
671+
Kind::object(BTreeMap::from([("message".into(), Kind::bytes())])),
672+
[LogNamespace::Legacy]
673+
),
674+
move_field_definitions_into_message(definition)
675+
);
676+
677+
// Test when a message field already exists.
678+
let definition = Definition::new_with_default_metadata(
679+
Kind::object(BTreeMap::from([("message".into(), Kind::integer())])).or_bytes(),
680+
[LogNamespace::Legacy],
681+
);
682+
assert_eq!(
683+
Definition::new_with_default_metadata(
684+
Kind::object(BTreeMap::from([(
685+
"message".into(),
686+
Kind::bytes().or_integer()
687+
)])),
688+
[LogNamespace::Legacy]
689+
),
690+
move_field_definitions_into_message(definition)
691+
);
692+
}
693+
694+
#[test]
695+
fn test_merged_array_definitions_simple() {
696+
// Test merging the array definitions where the schema definition
697+
// is simple, containing only one possible type in the array.
698+
let object: BTreeMap<vrl::value::kind::Field, Kind> = [
699+
("carrot".into(), Kind::bytes()),
700+
("potato".into(), Kind::integer()),
701+
]
702+
.into();
703+
704+
let kind = Kind::array(Collection::from_unknown(Kind::object(object)));
705+
706+
let definition = Definition::new_with_default_metadata(kind, [LogNamespace::Legacy]);
707+
708+
let kind = Kind::object(BTreeMap::from([
709+
("carrot".into(), Kind::bytes()),
710+
("potato".into(), Kind::integer()),
711+
]));
712+
713+
let wanted = Definition::new_with_default_metadata(kind, [LogNamespace::Legacy]);
714+
let merged = merge_array_definitions(definition);
715+
716+
assert_eq!(wanted, merged);
717+
}
718+
719+
#[test]
720+
fn test_merged_array_definitions_complex() {
721+
// Test merging the array definitions where the schema definition
722+
// is fairly complex containing multiple different possible types.
723+
let object: BTreeMap<vrl::value::kind::Field, Kind> = [
724+
("carrot".into(), Kind::bytes()),
725+
("potato".into(), Kind::integer()),
726+
]
727+
.into();
728+
729+
let array: BTreeMap<Index, Kind> = [
730+
(Index::from(0), Kind::integer()),
731+
(Index::from(1), Kind::boolean()),
732+
(
733+
Index::from(2),
734+
Kind::object(BTreeMap::from([("peas".into(), Kind::bytes())])),
735+
),
736+
]
737+
.into();
738+
739+
let mut kind = Kind::bytes();
740+
kind.add_object(object);
741+
kind.add_array(array);
742+
743+
let definition = Definition::new_with_default_metadata(kind, [LogNamespace::Legacy]);
744+
745+
let mut kind = Kind::bytes();
746+
kind.add_integer();
747+
kind.add_boolean();
748+
kind.add_object(BTreeMap::from([
749+
("carrot".into(), Kind::bytes().or_undefined()),
750+
("potato".into(), Kind::integer().or_undefined()),
751+
("peas".into(), Kind::bytes().or_undefined()),
752+
]));
753+
754+
let wanted = Definition::new_with_default_metadata(kind, [LogNamespace::Legacy]);
755+
let merged = merge_array_definitions(definition);
756+
757+
assert_eq!(wanted, merged);
758+
}
759+
597760
#[test]
598761
fn log_get() {
599762
let cases = vec![
@@ -755,7 +918,7 @@ mod test {
755918
Ok(Some(value))
756919
);
757920
assert_eq!(
758-
match target.into_events() {
921+
match target.into_events(LogNamespace::Legacy) {
759922
TargetEvents::One(event) => vec![event],
760923
TargetEvents::Logs(events) => events.collect::<Vec<_>>(),
761924
TargetEvents::Traces(events) => events.collect::<Vec<_>>(),
@@ -901,7 +1064,7 @@ mod test {
9011064
Target::target_insert(&mut target, &OwnedTargetPath::event_root(), value).unwrap();
9021065

9031066
assert_eq!(
904-
match target.into_events() {
1067+
match target.into_events(LogNamespace::Legacy) {
9051068
TargetEvents::One(event) => vec![event],
9061069
TargetEvents::Logs(events) => events.collect::<Vec<_>>(),
9071070
TargetEvents::Traces(events) => events.collect::<Vec<_>>(),

lib/vector-core/src/schema/definition.rs

+19
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,25 @@ impl Definition {
457457
self
458458
}
459459

460+
/// If the schema definition depends on the `LogNamespace`, this combines the individual
461+
/// definitions for each `LogNamespace`.
462+
pub fn combine_log_namespaces(
463+
log_namespaces: &BTreeSet<LogNamespace>,
464+
legacy: Self,
465+
vector: Self,
466+
) -> Self {
467+
let mut combined =
468+
Definition::new_with_default_metadata(Kind::never(), log_namespaces.clone());
469+
470+
if log_namespaces.contains(&LogNamespace::Legacy) {
471+
combined = combined.merge(legacy);
472+
}
473+
if log_namespaces.contains(&LogNamespace::Vector) {
474+
combined = combined.merge(vector);
475+
}
476+
combined
477+
}
478+
460479
/// Returns an `OwnedTargetPath` into an event, based on the provided `meaning`, if the meaning exists.
461480
pub fn meaning_path(&self, meaning: &str) -> Option<&OwnedTargetPath> {
462481
match self.meaning.get(meaning) {

src/conditions/vrl.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use vrl::compiler::{CompilationResult, CompileConfig, Program, TypeState, VrlRun
66
use vrl::diagnostic::Formatter;
77
use vrl::value::Value;
88

9+
use crate::config::LogNamespace;
910
use crate::event::TargetEvents;
1011
use crate::{
1112
conditions::{Condition, Conditional, ConditionalConfig},
@@ -84,12 +85,16 @@ pub struct Vrl {
8485

8586
impl Vrl {
8687
fn run(&self, event: Event) -> (Event, RuntimeResult) {
88+
let log_namespace = event
89+
.maybe_as_log()
90+
.map(|log| log.namespace())
91+
.unwrap_or(LogNamespace::Legacy);
8792
let mut target = VrlTarget::new(event, self.program.info(), false);
8893
// TODO: use timezone from remap config
8994
let timezone = TimeZone::default();
9095

9196
let result = Runtime::default().resolve(&mut target, &self.program, &timezone);
92-
let original_event = match target.into_events() {
97+
let original_event = match target.into_events(log_namespace) {
9398
TargetEvents::One(event) => event,
9499
_ => panic!("Event was modified in a condition. This is an internal compiler error."),
95100
};

0 commit comments

Comments
 (0)