Skip to content

Commit ccc3e6e

Browse files
authored
[pkg/ottl] adapt mapGetter to handle nested map items within slices (#37408)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> #### Description This PR fixes the limitations described in #37405. The recently introduced `ValueExpression` had to be adapted, and will now only return raw types, instead of their `pcommon.Map`/`pcommon.Slice` eqivalents, as suggested in #37280 (comment) <!-- Issue number (e.g. #1234) or full URL to issue, if applicable. --> #### Link to tracking issue Fixes #37405 <!--Describe what testing was performed and which tests were added.--> #### Testing Adapted and extended the unit and e2e tests --------- Signed-off-by: Florian Bacher <[email protected]>
1 parent 944c7b1 commit ccc3e6e

File tree

6 files changed

+346
-25
lines changed

6 files changed

+346
-25
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: bug_fix
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
7+
component: pkg/ottl
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Fix limitation of map literals within slice literals not being handled correctly
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [37405]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext:
19+
20+
# If your change doesn't affect end users or the exported elements of any package,
21+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
22+
# Optional: The change log or logs in which this entry should be included.
23+
# e.g. '[user]' or '[user, api]'
24+
# Include 'user' if the change is relevant to end users.
25+
# Include 'api' if there is a change to a library API.
26+
# Default: '[user]'
27+
change_logs: []

pkg/ottl/e2e/e2e_test.go

Lines changed: 247 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,15 @@ func Test_e2e_editors(t *testing.T) {
226226
m2.PutStr("test", "pass")
227227
},
228228
},
229+
{
230+
statement: `merge_maps(attributes, {"map_literal": {"list": [{"foo":"bar"}, "test"]}}, "upsert")`,
231+
want: func(tCtx ottllog.TransformContext) {
232+
mapAttr := tCtx.GetLogRecord().Attributes().PutEmptyMap("map_literal")
233+
l := mapAttr.PutEmptySlice("list")
234+
l.AppendEmpty().SetEmptyMap().PutStr("foo", "bar")
235+
l.AppendEmpty().SetStr("test")
236+
},
237+
},
229238
{
230239
statement: `replace_all_matches(attributes, "*/*", "test")`,
231240
want: func(tCtx ottllog.TransformContext) {
@@ -1125,6 +1134,48 @@ func Test_e2e_converters(t *testing.T) {
11251134
m.PutInt("bar", 5)
11261135
},
11271136
},
1137+
{
1138+
statement: `set(attributes["test"], {"list":[{"foo":"bar"}]})`,
1139+
want: func(tCtx ottllog.TransformContext) {
1140+
m := tCtx.GetLogRecord().Attributes().PutEmptyMap("test")
1141+
m2 := m.PutEmptySlice("list").AppendEmpty().SetEmptyMap()
1142+
m2.PutStr("foo", "bar")
1143+
},
1144+
},
1145+
{
1146+
statement: `set(attributes, {"list":[{"foo":"bar"}]})`,
1147+
want: func(tCtx ottllog.TransformContext) {
1148+
tCtx.GetLogRecord().Attributes().Clear()
1149+
m2 := tCtx.GetLogRecord().Attributes().PutEmptySlice("list").AppendEmpty().SetEmptyMap()
1150+
m2.PutStr("foo", "bar")
1151+
},
1152+
},
1153+
{
1154+
statement: `set(attributes["arr"], [{"list":[{"foo":"bar"}]}, {"bar":"baz"}])`,
1155+
want: func(tCtx ottllog.TransformContext) {
1156+
arr := tCtx.GetLogRecord().Attributes().PutEmptySlice("arr")
1157+
arr.AppendEmpty().SetEmptyMap().PutEmptySlice("list").AppendEmpty().SetEmptyMap().PutStr("foo", "bar")
1158+
arr.AppendEmpty().SetEmptyMap().PutStr("bar", "baz")
1159+
},
1160+
},
1161+
{
1162+
statement: `set(attributes["test"], IsList([{"list":[{"foo":"bar"}]}, {"bar":"baz"}]))`,
1163+
want: func(tCtx ottllog.TransformContext) {
1164+
tCtx.GetLogRecord().Attributes().PutBool("test", true)
1165+
},
1166+
},
1167+
{
1168+
statement: `set(attributes["test"], IsMap({"list":[{"foo":"bar"}]}))`,
1169+
want: func(tCtx ottllog.TransformContext) {
1170+
tCtx.GetLogRecord().Attributes().PutBool("test", true)
1171+
},
1172+
},
1173+
{
1174+
statement: `set(attributes["test"], Len([{"list":[{"foo":"bar"}]}, {"bar":"baz"}]))`,
1175+
want: func(tCtx ottllog.TransformContext) {
1176+
tCtx.GetLogRecord().Attributes().PutInt("test", 2)
1177+
},
1178+
},
11281179
}
11291180

11301181
for _, tt := range tests {
@@ -1268,41 +1319,176 @@ func Test_e2e_ottl_features(t *testing.T) {
12681319
}
12691320
}
12701321

1322+
func Test_e2e_ottl_statement_sequence(t *testing.T) {
1323+
tests := []struct {
1324+
name string
1325+
statements []string
1326+
want func(tCtx ottllog.TransformContext)
1327+
}{
1328+
{
1329+
name: "delete key of map literal",
1330+
statements: []string{
1331+
`set(attributes["test"], {"foo":"bar", "list":[{"test":"hello"}]})`,
1332+
`delete_key(attributes["test"], "foo")`,
1333+
},
1334+
want: func(tCtx ottllog.TransformContext) {
1335+
m := tCtx.GetLogRecord().Attributes().PutEmptyMap("test")
1336+
m.PutEmptySlice("list").AppendEmpty().SetEmptyMap().PutStr("test", "hello")
1337+
},
1338+
},
1339+
{
1340+
name: "delete matching keys of map literal",
1341+
statements: []string{
1342+
`set(attributes["test"], {"foo":"bar", "list":[{"test":"hello"}]})`,
1343+
`delete_matching_keys(attributes["test"], ".*oo")`,
1344+
},
1345+
want: func(tCtx ottllog.TransformContext) {
1346+
m := tCtx.GetLogRecord().Attributes().PutEmptyMap("test")
1347+
m.PutEmptySlice("list").AppendEmpty().SetEmptyMap().PutStr("test", "hello")
1348+
},
1349+
},
1350+
{
1351+
name: "keep matching keys of map literal",
1352+
statements: []string{
1353+
`set(attributes["test"], {"foo":"bar", "list":[{"test":"hello"}]})`,
1354+
`keep_matching_keys(attributes["test"], ".*ist")`,
1355+
},
1356+
want: func(tCtx ottllog.TransformContext) {
1357+
m := tCtx.GetLogRecord().Attributes().PutEmptyMap("test")
1358+
m.PutEmptySlice("list").AppendEmpty().SetEmptyMap().PutStr("test", "hello")
1359+
},
1360+
},
1361+
{
1362+
name: "flatten map literal",
1363+
statements: []string{
1364+
`set(attributes["test"], {"foo":"bar", "list":[{"test":"hello"}]})`,
1365+
`flatten(attributes["test"])`,
1366+
},
1367+
want: func(tCtx ottllog.TransformContext) {
1368+
m := tCtx.GetLogRecord().Attributes().PutEmptyMap("test")
1369+
m.PutStr("foo", "bar")
1370+
m.PutStr("list.0.test", "hello")
1371+
},
1372+
},
1373+
}
1374+
1375+
for _, tt := range tests {
1376+
t.Run(tt.name, func(t *testing.T) {
1377+
tCtx := constructLogTransformContext()
1378+
1379+
for _, statement := range tt.statements {
1380+
logStatements, err := parseStatementWithAndWithoutPathContext(statement)
1381+
assert.NoError(t, err)
1382+
1383+
for _, s := range logStatements {
1384+
_, _, _ = s.Execute(context.Background(), tCtx)
1385+
}
1386+
}
1387+
1388+
exTCtx := constructLogTransformContext()
1389+
tt.want(exTCtx)
1390+
1391+
assert.NoError(t, plogtest.CompareResourceLogs(newResourceLogs(exTCtx), newResourceLogs(tCtx)))
1392+
})
1393+
}
1394+
}
1395+
12711396
func Test_e2e_ottl_value_expressions(t *testing.T) {
12721397
tests := []struct {
12731398
name string
12741399
statement string
1275-
want any
1400+
want func() any
12761401
}{
12771402
{
12781403
name: "string literal",
12791404
statement: `"foo"`,
1280-
want: "foo",
1405+
want: func() any {
1406+
return "foo"
1407+
},
12811408
},
12821409
{
12831410
name: "attribute value",
12841411
statement: `resource.attributes["host.name"]`,
1285-
want: "localhost",
1412+
want: func() any {
1413+
return "localhost"
1414+
},
12861415
},
12871416
{
12881417
name: "accessing enum",
12891418
statement: `SEVERITY_NUMBER_TRACE`,
1290-
want: int64(1),
1419+
want: func() any {
1420+
return int64(1)
1421+
},
12911422
},
12921423
{
12931424
name: "Using converter",
12941425
statement: `TraceID(0x0102030405060708090a0b0c0d0e0f10)`,
1295-
want: pcommon.TraceID{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10},
1426+
want: func() any {
1427+
return pcommon.TraceID{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10}
1428+
},
12961429
},
12971430
{
12981431
name: "Adding results of two converter operations",
12991432
statement: `Len(attributes) + Len(attributes)`,
1300-
want: int64(24),
1433+
want: func() any {
1434+
return int64(28)
1435+
},
13011436
},
13021437
{
13031438
name: "Nested converter operations",
13041439
statement: `Hex(Len(attributes) + Len(attributes))`,
1305-
want: "0000000000000018",
1440+
want: func() any {
1441+
return "000000000000001c"
1442+
},
1443+
},
1444+
{
1445+
name: "return map type 1",
1446+
statement: `attributes["foo"]`,
1447+
want: func() any {
1448+
m := pcommon.NewMap()
1449+
_ = m.FromRaw(map[string]any{
1450+
"bar": "pass",
1451+
})
1452+
return m
1453+
},
1454+
},
1455+
{
1456+
name: "return map type 2",
1457+
statement: `attributes["foo2"]`,
1458+
want: func() any {
1459+
m := pcommon.NewMap()
1460+
_ = m.FromRaw(map[string]any{
1461+
"slice": []any{
1462+
"val",
1463+
},
1464+
})
1465+
return m
1466+
},
1467+
},
1468+
{
1469+
name: "return map type 3",
1470+
statement: `attributes["foo3"]`,
1471+
want: func() any {
1472+
m := pcommon.NewMap()
1473+
_ = m.FromRaw(map[string]any{
1474+
"nested": map[string]any{
1475+
"test": "pass",
1476+
},
1477+
})
1478+
return m
1479+
},
1480+
},
1481+
{
1482+
name: "return list",
1483+
statement: `attributes["things"]`,
1484+
want: func() any {
1485+
s := pcommon.NewSlice()
1486+
_ = s.FromRaw([]any{
1487+
map[string]any{"name": "foo"},
1488+
map[string]any{"name": "bar"},
1489+
})
1490+
return s
1491+
},
13061492
},
13071493
}
13081494

@@ -1314,11 +1500,11 @@ func Test_e2e_ottl_value_expressions(t *testing.T) {
13141500
valueExpr, err := logParser.ParseValueExpression(tt.statement)
13151501
assert.NoError(t, err)
13161502

1317-
tCtx := constructLogTransformContext()
1503+
tCtx := constructLogTransformContextValueExpressions()
13181504
val, err := valueExpr.Eval(context.Background(), tCtx)
13191505
assert.NoError(t, err)
13201506

1321-
assert.Equal(t, tt.want, val)
1507+
assert.Equal(t, tt.want(), val)
13221508
})
13231509
}
13241510
}
@@ -1526,6 +1712,58 @@ func constructLogTransformContextEditors() ottllog.TransformContext {
15261712
return ottllog.NewTransformContext(logRecord, scope, resource, plog.NewScopeLogs(), plog.NewResourceLogs())
15271713
}
15281714

1715+
func constructLogTransformContextValueExpressions() ottllog.TransformContext {
1716+
resource := pcommon.NewResource()
1717+
resource.Attributes().PutStr("host.name", "localhost")
1718+
resource.Attributes().PutStr("A|B|C", "newValue")
1719+
1720+
scope := pcommon.NewInstrumentationScope()
1721+
scope.SetName("scope")
1722+
1723+
logRecord := plog.NewLogRecord()
1724+
logRecord.Body().SetStr("operationA")
1725+
logRecord.SetTimestamp(TestLogTimestamp)
1726+
logRecord.SetObservedTimestamp(TestObservedTimestamp)
1727+
logRecord.SetDroppedAttributesCount(1)
1728+
logRecord.SetFlags(plog.DefaultLogRecordFlags.WithIsSampled(true))
1729+
logRecord.SetSeverityNumber(1)
1730+
logRecord.SetTraceID(traceID)
1731+
logRecord.SetSpanID(spanID)
1732+
logRecord.Attributes().PutStr("http.method", "get")
1733+
logRecord.Attributes().PutStr("http.path", "/health")
1734+
logRecord.Attributes().PutStr("http.url", "http://localhost/health")
1735+
logRecord.Attributes().PutStr("flags", "A|B|C")
1736+
logRecord.Attributes().PutStr("total.string", "123456789")
1737+
logRecord.Attributes().PutStr("A|B|C", "something")
1738+
logRecord.Attributes().PutStr("foo", "foo")
1739+
logRecord.Attributes().PutStr("slice", "slice")
1740+
logRecord.Attributes().PutStr("val", "val2")
1741+
logRecord.Attributes().PutInt("int_value", 0)
1742+
arr := logRecord.Attributes().PutEmptySlice("array")
1743+
arr0 := arr.AppendEmpty()
1744+
arr0.SetStr("looong")
1745+
m := logRecord.Attributes().PutEmptyMap("foo")
1746+
m.PutStr("bar", "pass")
1747+
1748+
m2 := logRecord.Attributes().PutEmptyMap("foo2")
1749+
s := m2.PutEmptySlice("slice")
1750+
v := s.AppendEmpty()
1751+
v.SetStr("val")
1752+
1753+
m3 := logRecord.Attributes().PutEmptyMap("foo3")
1754+
m31 := m3.PutEmptyMap("nested")
1755+
m31.PutStr("test", "pass")
1756+
1757+
s2 := logRecord.Attributes().PutEmptySlice("things")
1758+
thing1 := s2.AppendEmpty().SetEmptyMap()
1759+
thing1.PutStr("name", "foo")
1760+
1761+
thing2 := s2.AppendEmpty().SetEmptyMap()
1762+
thing2.PutStr("name", "bar")
1763+
1764+
return ottllog.NewTransformContext(logRecord, scope, resource, plog.NewScopeLogs(), plog.NewResourceLogs())
1765+
}
1766+
15291767
func constructSpanTransformContext() ottlspan.TransformContext {
15301768
resource := pcommon.NewResource()
15311769

0 commit comments

Comments
 (0)