Skip to content

Commit 2054289

Browse files
committed
Add OTTL context inferrer utility to resolve statements context based on its paths
1 parent 003780f commit 2054289

File tree

3 files changed

+220
-0
lines changed

3 files changed

+220
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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: enhancement
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: Add OTTL context inferrer utility to resolve statements context based on its paths
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: [29017]
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+
The `ottl` package has a new interface `ottl.ContextInferrer`, and two new functions to create a priority-based
20+
context inferrer `ottl.NewPriorityContextInferrer` and `ottl.DefaultPriorityContextInferrer`.
21+
22+
# If your change doesn't affect end users or the exported elements of any package,
23+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
24+
# Optional: The change log or logs in which this entry should be included.
25+
# e.g. '[user]' or '[user, api]'
26+
# Include 'user' if the change is relevant to end users.
27+
# Include 'api' if there is a change to a library API.
28+
# Default: '[user]'
29+
change_logs: [api]

pkg/ottl/context_inferrer.go

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package ottl // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
5+
6+
import "math"
7+
8+
var (
9+
defaultContextInferPriority = []string{
10+
"log",
11+
"metric",
12+
"datapoint",
13+
"spanevent",
14+
"span",
15+
"resource",
16+
"scope",
17+
"instrumentation_scope",
18+
}
19+
)
20+
21+
// ContextInferrer is an interface used to infer the OTTL context from statements paths.
22+
type ContextInferrer interface {
23+
// Infer returns the OTTL context inferred from the given statements paths.
24+
Infer(statements []string) (string, error)
25+
}
26+
27+
type priorityContextInferrer struct {
28+
contextPriority map[string]int
29+
ignoreUnknownContext bool
30+
}
31+
32+
func (s *priorityContextInferrer) Infer(statements []string) (string, error) {
33+
var inferredContext string
34+
var inferredContextPriority int
35+
36+
for _, statement := range statements {
37+
parsed, err := parseStatement(statement)
38+
if err != nil {
39+
return inferredContext, err
40+
}
41+
42+
for _, p := range getParsedStatementPaths(parsed) {
43+
pathContextPriority, ok := s.contextPriority[p.Context]
44+
if !ok {
45+
if s.ignoreUnknownContext {
46+
continue
47+
}
48+
49+
// Lowest priority
50+
pathContextPriority = math.MaxInt
51+
}
52+
53+
if inferredContext == "" || pathContextPriority < inferredContextPriority {
54+
inferredContext = p.Context
55+
inferredContextPriority = pathContextPriority
56+
}
57+
}
58+
}
59+
60+
return inferredContext, nil
61+
}
62+
63+
// DefaultPriorityContextInferrer is like NewPriorityContextInferrer, but using the default
64+
// context priorities and ignoring unknown/non-prioritized contexts.
65+
func DefaultPriorityContextInferrer() ContextInferrer {
66+
return NewPriorityContextInferrer(defaultContextInferPriority, false)
67+
}
68+
69+
// NewPriorityContextInferrer creates a new priority-based context inferrer.
70+
// To infer the context, it compares all [ottl.Path.Context] values, prioritizing them based
71+
// on the provide contextsPriority argument, the lower the context position is in the array,
72+
// the more priority it will have over other items.
73+
// If unknown/non-prioritized contexts are found on the statements, they can be either ignored
74+
// or considered when no other prioritized context is found. To skip unknown contexts, the
75+
// ignoreUnknownContext argument must be set to false.
76+
func NewPriorityContextInferrer(contextsPriority []string, ignoreUnknownContext bool) ContextInferrer {
77+
contextPriority := make(map[string]int, len(contextsPriority))
78+
for i, ctx := range contextsPriority {
79+
contextPriority[ctx] = i
80+
}
81+
return &priorityContextInferrer{
82+
contextPriority: contextPriority,
83+
ignoreUnknownContext: ignoreUnknownContext,
84+
}
85+
}

pkg/ottl/context_inferrer_test.go

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package ottl
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func Test_NewPriorityContextInferrer_Infer(t *testing.T) {
14+
tests := []struct {
15+
name string
16+
priority []string
17+
ignoreUnknown bool
18+
statements []string
19+
expected string
20+
}{
21+
{
22+
name: "with priority and contexts",
23+
priority: []string{"spanevent", "span", "resource"},
24+
statements: []string{"set(span.foo, resource.value) where spanevent.bar == true"},
25+
expected: "spanevent",
26+
},
27+
{
28+
name: "with multiple statements",
29+
priority: []string{"spanevent", "span", "resource"},
30+
statements: []string{
31+
"set(resource.foo, resource.value) where span.bar == true",
32+
"set(resource.foo, resource.value) where spanevent.bar == true",
33+
},
34+
expected: "spanevent",
35+
},
36+
{
37+
name: "with no context",
38+
priority: []string{"log", "resource"},
39+
statements: []string{"set(foo, true) where bar == true"},
40+
expected: "",
41+
},
42+
{
43+
name: "with empty priority",
44+
statements: []string{"set(foo.name, true) where bar.name == true"},
45+
expected: "foo",
46+
},
47+
{
48+
name: "with ignore unknown false",
49+
priority: []string{"foo", "bar"},
50+
ignoreUnknown: false,
51+
statements: []string{"set(span.foo, true) where span.bar == true"},
52+
expected: "span",
53+
},
54+
{
55+
name: "with ignore unknown true",
56+
priority: []string{"foo", "bar"},
57+
ignoreUnknown: true,
58+
statements: []string{"set(span.foo, true) where span.bar == true"},
59+
expected: "",
60+
},
61+
{
62+
name: "with ignore unknown true and mixed statement contexts",
63+
priority: []string{"foo", "span"},
64+
ignoreUnknown: true,
65+
statements: []string{"set(bar.foo, true) where span.bar == true"},
66+
expected: "span",
67+
},
68+
}
69+
70+
for _, tt := range tests {
71+
t.Run(tt.name, func(t *testing.T) {
72+
inferrer := NewPriorityContextInferrer(tt.priority, tt.ignoreUnknown)
73+
inferredContext, err := inferrer.Infer(tt.statements)
74+
require.NoError(t, err)
75+
assert.Equal(t, tt.expected, inferredContext)
76+
})
77+
}
78+
}
79+
80+
func Test_NewPriorityContextInferrer_InvalidStatement(t *testing.T) {
81+
inferrer := NewPriorityContextInferrer([]string{"foo"}, false)
82+
statements := []string{"set(foo.field,"}
83+
_, err := inferrer.Infer(statements)
84+
require.ErrorContains(t, err, "unexpected token")
85+
}
86+
87+
func Test_DefaultPriorityContextInferrer(t *testing.T) {
88+
expectedPriority := []string{
89+
"log",
90+
"metric",
91+
"datapoint",
92+
"spanevent",
93+
"span",
94+
"resource",
95+
"scope",
96+
"instrumentation_scope",
97+
}
98+
99+
inferrer := DefaultPriorityContextInferrer().(*priorityContextInferrer)
100+
require.NotNil(t, inferrer)
101+
require.False(t, inferrer.ignoreUnknownContext)
102+
103+
for pri, ctx := range expectedPriority {
104+
require.Equal(t, pri, inferrer.contextPriority[ctx])
105+
}
106+
}

0 commit comments

Comments
 (0)