Skip to content

Commit 7bfa78e

Browse files
authored
[OTel] Support Serializing nested data (#45008)
This adds support for `map[string]any` and also arrays in general (as opposed to just slices) to the converted map. Like `mapstr.M` and `[]mapstr.M`, this will dive into the `map[string]any` and `[]map[string]any` objects to ensure that they are properly converted for the pdata code.
1 parent 38f6842 commit 7bfa78e

File tree

3 files changed

+99
-9
lines changed

3 files changed

+99
-9
lines changed

CHANGELOG-developer.next.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ The list below covers the major changes between 7.0.0-rc2 and main only.
7575

7676
==== Bugfixes
7777

78+
- Handle conversion of `map[string]any` and `[]map[string]any` for `otelconsumer`.{pull}45008[45008]
7879
- Handle the starting of namespace and node watchers for metadata enrichment according to `add_resource_metadata` configuration.{pull}38762[38762]
7980
- Fix multiple metricbeat instances reporting same metrics when using autodiscover with provider kubernetes, and ensure leader elector is always running in autodiscover mode.{pull}38471[38471]
8081
- Fix how Prometheus histograms are calculated when percentiles are provide.{pull}36537[36537]

libbeat/otelbeat/otelmap/otelmap.go

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ import (
2929
"go.opentelemetry.io/collector/pdata/pcommon"
3030
)
3131

32+
// Allow ConvertNonPrimitive to be called recursively to handle nested maps of either type.
33+
type mapstrOrMap interface {
34+
mapstr.M | map[string]any
35+
}
36+
3237
// ToMapstr converts a [pcommon.Map] to a [mapstr.M].
3338
func ToMapstr(m pcommon.Map) mapstr.M {
3439
return m.AsRaw()
@@ -42,7 +47,7 @@ func ToMapstr(m pcommon.Map) mapstr.M {
4247
// If you attempt to use other slice types (e.g., []string or []int),
4348
// pcommon.Map.FromRaw(...) will return an "invalid type" error.
4449
// To overcome this, we use "reflect" to transform []T into []any.
45-
func ConvertNonPrimitive(m mapstr.M) {
50+
func ConvertNonPrimitive[T mapstrOrMap](m T) {
4651
for key, val := range m {
4752
switch x := val.(type) {
4853
case mapstr.M:
@@ -55,6 +60,16 @@ func ConvertNonPrimitive(m mapstr.M) {
5560
s[i] = map[string]any(val)
5661
}
5762
m[key] = s
63+
case map[string]any:
64+
ConvertNonPrimitive(x)
65+
m[key] = x
66+
case []map[string]any:
67+
s := make([]any, len(x))
68+
for i := range x {
69+
ConvertNonPrimitive(x[i])
70+
s[i] = x[i]
71+
}
72+
m[key] = s
5873
case time.Time:
5974
m[key] = x.UTC().Format("2006-01-02T15:04:05.000Z")
6075
case common.Time:
@@ -73,16 +88,30 @@ func ConvertNonPrimitive(m mapstr.M) {
7388
m[key] = s
7489
case []bool, []string, []float32, []float64, []int, []int8, []int16, []int32, []int64,
7590
[]uint, []uint8, []uint16, []uint32, []uint64:
91+
ref := reflect.ValueOf(x)
92+
s := make([]any, ref.Len())
93+
for i := 0; i < ref.Len(); i++ {
94+
s[i] = ref.Index(i).Interface()
95+
}
96+
m[key] = s
97+
case nil, string, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, bool:
98+
default:
7699
ref := reflect.ValueOf(x)
77100
if ref.Kind() == reflect.Slice || ref.Kind() == reflect.Array {
78-
slice := make([]any, ref.Len())
101+
s := make([]any, ref.Len())
79102
for i := 0; i < ref.Len(); i++ {
80-
slice[i] = ref.Index(i).Interface()
103+
elem := ref.Index(i)
104+
if elem.Kind() == reflect.Map && elem.Type().Key().Kind() == reflect.String && elem.Type().Elem().Kind() == reflect.Interface {
105+
if m, ok := elem.Interface().(map[string]any); ok {
106+
ConvertNonPrimitive(m)
107+
}
108+
}
109+
s[i] = elem.Interface()
81110
}
82-
m[key] = slice
111+
m[key] = s
112+
break // we figured out the type, so we don't need the unknown type case
83113
}
84-
case nil, string, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, bool, []any, map[string]any:
85-
default:
114+
86115
m[key] = fmt.Sprintf("unknown type: %T", x)
87116
}
88117
}

libbeat/otelbeat/otelmap/otelmap_test.go

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,56 @@ func TestFromMapstrSliceCommonTime(t *testing.T) {
273273
assert.Equal(t, want, inputMap)
274274
}
275275

276+
func TestFromMapstrWithNestedData(t *testing.T) {
277+
input := mapstr.M{
278+
"any_array": [3]any{1, "string", 3},
279+
"any_slice": []any{5.1, 6.2},
280+
"bool_array": [2]bool{true, false},
281+
"bool_slice": []bool{false, true},
282+
"inner": []mapstr.M{
283+
{
284+
"inner_int": 42,
285+
"inner_slice": []map[string]any{ // slice -> slice
286+
{"string": "string"},
287+
{"number": 12.3},
288+
},
289+
},
290+
{
291+
"inner_int": 43,
292+
"inner_slice": [2]map[string]any{ // array -> slice
293+
{"string": "string2"},
294+
{"number": 12.4},
295+
},
296+
},
297+
},
298+
}
299+
want := mapstr.M{
300+
"any_array": []any{1, "string", 3},
301+
"any_slice": []any{5.1, 6.2},
302+
"bool_array": []any{true, false},
303+
"bool_slice": []any{false, true},
304+
"inner": []any{
305+
map[string]any{
306+
"inner_int": 42,
307+
"inner_slice": []any{
308+
map[string]any{"string": "string"},
309+
map[string]any{"number": 12.3},
310+
},
311+
},
312+
map[string]any{
313+
"inner_int": 43,
314+
"inner_slice": []any{
315+
map[string]any{"string": "string2"},
316+
map[string]any{"number": 12.4},
317+
},
318+
},
319+
},
320+
}
321+
322+
ConvertNonPrimitive(input)
323+
assert.Equal(t, want, input)
324+
}
325+
276326
func TestToMapstr(t *testing.T) {
277327
pm := pcommon.NewMap()
278328
pm.PutInt("int", 42)
@@ -303,15 +353,25 @@ func TestToMapstr(t *testing.T) {
303353
assert.Equal(t, want, got)
304354
}
305355

306-
type unknown int
356+
type unknown struct {
357+
Value int `json:"value"`
358+
}
307359

308360
func TestUnknownType(t *testing.T) {
309361
inputMap := mapstr.M{
310-
"slice": []unknown{42, 43, 44},
362+
"unknown": unknown{42},
363+
"nested": mapstr.M{
364+
"unknown": unknown{43},
365+
},
366+
"unknown_map": map[string]int{"key": 42},
311367
}
312368

313369
expected := mapstr.M{
314-
"slice": "unknown type: []otelmap.unknown",
370+
"unknown": "unknown type: otelmap.unknown",
371+
"nested": map[string]any{
372+
"unknown": "unknown type: otelmap.unknown",
373+
},
374+
"unknown_map": "unknown type: map[string]int",
315375
}
316376

317377
ConvertNonPrimitive(inputMap)

0 commit comments

Comments
 (0)