diff --git a/internal/fields/model.go b/internal/fields/model.go index adf7b9d2f9..4613bff892 100644 --- a/internal/fields/model.go +++ b/internal/fields/model.go @@ -128,17 +128,22 @@ func (fds *FieldDefinitions) UnmarshalYAML(value *yaml.Node) error { return err } - // Some groups are used by convention in ECS to include - // fields that can appear in the root level of the document. - // Append their child fields directly instead. - // TODO: Make this generic looking for groups whose children don't match. - if name == "base" || name == "tracing" { - fields = append(fields, field.Fields...) - } else { - field.Name = name - cleanNestedNames(field.Name, field.Fields) - fields = append(fields, field) + field.Name = name + baseFields := cleanNested(&field) + if len(baseFields) > 0 { + // Some groups are used by convention in ECS to include + // fields that can appear in the root level of the document. + // Append their child fields directly instead. + // Examples of such groups are `base` or `tracing`. + fields = append(fields, baseFields...) + if len(field.Fields) == 0 { + // If it had base fields, and doesn't have any other + // field, don't add it. + continue + } } + + fields = append(fields, field) } *fds = fields return nil @@ -150,10 +155,30 @@ func (fds *FieldDefinitions) UnmarshalYAML(value *yaml.Node) error { } } -func cleanNestedNames(parent string, fields []FieldDefinition) { - for i := range fields { - if strings.HasPrefix(fields[i].Name, parent+".") { - fields[i].Name = fields[i].Name[len(parent)+1:] +// cleanNested processes fields nested inside another field, and returns +// defined base fields. +// If a field name is prefixed by the parent field, this part is removed, +// so the full path, taking into account the parent name, matches. +// If a field name is not prefixed by the parent field, this is considered +// a base field, that should appear at the top-level. It is removed from +// the list of nested fields and returned as base field. +func cleanNested(parent *FieldDefinition) (base []FieldDefinition) { + var nested []FieldDefinition + for _, field := range parent.Fields { + // If the field name is prefixed by the name of its parent, + // this is a normal nested field. If not, it is a base field. + if strings.HasPrefix(field.Name, parent.Name+".") { + field.Name = field.Name[len(parent.Name)+1:] + nested = append(nested, field) + } else { + base = append(base, field) } } + + // At the moment of writing this code, a group field has base fields + // (`base` and `tracing` groups), or nested fields, but not both. + // This code handles the case of having groups with both kinds of fields, + // just in case this happens. + parent.Fields = nested + return base }