Skip to content

[pkg/ottl] Add OTTL context inferrer utility #35721

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .chloggen/ottl-add-context-inferrer-utility.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: pkg/ottl

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Add OTTL context inferrer utility to resolve statements context based on its paths

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [29017]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext: |
The `ottl` package has a new interface `ottl.ContextInferrer`, and two new functions to create a priority-based
context inferrer `ottl.NewPriorityContextInferrer` and `ottl.DefaultPriorityContextInferrer`.

# If your change doesn't affect end users or the exported elements of any package,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: [api]
79 changes: 79 additions & 0 deletions pkg/ottl/context_inferrer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package ottl // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"

import "math"

var (
defaultContextInferPriority = []string{
"log",
"metric",
"datapoint",
"spanevent",
"span",
"resource",
"scope",
"instrumentation_scope",
}
)

// contextInferrer is an interface used to infer the OTTL context from statements paths.
type contextInferrer interface {
// infer returns the OTTL context inferred from the given statements paths.
infer(statements []string) (string, error)
}

type priorityContextInferrer struct {
contextPriority map[string]int
}

func (s *priorityContextInferrer) infer(statements []string) (string, error) {
var inferredContext string
var inferredContextPriority int

for _, statement := range statements {
parsed, err := parseStatement(statement)
if err != nil {
return inferredContext, err
}

for _, p := range getParsedStatementPaths(parsed) {
pathContextPriority, ok := s.contextPriority[p.Context]
if !ok {
// Lowest priority
pathContextPriority = math.MaxInt
}

if inferredContext == "" || pathContextPriority < inferredContextPriority {
inferredContext = p.Context
inferredContextPriority = pathContextPriority
}
}
}

return inferredContext, nil
}

// defaultPriorityContextInferrer is like newPriorityContextInferrer, but using the default
// context priorities and ignoring unknown/non-prioritized contexts.
func defaultPriorityContextInferrer() contextInferrer {
return newPriorityContextInferrer(defaultContextInferPriority)
}

// newPriorityContextInferrer creates a new priority-based context inferrer.
// To infer the context, it compares all [ottl.Path.Context] values, prioritizing them based
// on the provide contextsPriority argument, the lower the context position is in the array,
// the more priority it will have over other items.
// If unknown/non-prioritized contexts are found on the statements, they can be either ignored
// or considered when no other prioritized context is found. To skip unknown contexts, the
// ignoreUnknownContext argument must be set to false.
func newPriorityContextInferrer(contextsPriority []string) contextInferrer {
contextPriority := make(map[string]int, len(contextsPriority))
for i, ctx := range contextsPriority {
contextPriority[ctx] = i
}
return &priorityContextInferrer{
contextPriority: contextPriority,
}
}
89 changes: 89 additions & 0 deletions pkg/ottl/context_inferrer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package ottl

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func Test_NewPriorityContextInferrer_Infer(t *testing.T) {
tests := []struct {
name string
priority []string
statements []string
expected string
}{
{
name: "with priority and contexts",
priority: []string{"spanevent", "span", "resource"},
statements: []string{"set(span.foo, resource.value) where spanevent.bar == true"},
expected: "spanevent",
},
{
name: "with multiple statements",
priority: []string{"spanevent", "span", "resource"},
statements: []string{
"set(resource.foo, resource.value) where span.bar == true",
"set(resource.foo, resource.value) where spanevent.bar == true",
},
expected: "spanevent",
},
{
name: "with no context",
priority: []string{"log", "resource"},
statements: []string{"set(foo, true) where bar == true"},
expected: "",
},
{
name: "with empty priority",
statements: []string{"set(foo.name, true) where bar.name == true"},
expected: "foo",
},
{
name: "with unknown context",
priority: []string{"foo", "bar"},
statements: []string{"set(span.foo, true) where span.bar == true"},
expected: "span",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
inferrer := newPriorityContextInferrer(tt.priority)
inferredContext, err := inferrer.infer(tt.statements)
require.NoError(t, err)
assert.Equal(t, tt.expected, inferredContext)
})
}
}

func Test_NewPriorityContextInferrer_InvalidStatement(t *testing.T) {
inferrer := newPriorityContextInferrer([]string{"foo"})
statements := []string{"set(foo.field,"}
_, err := inferrer.infer(statements)
require.ErrorContains(t, err, "unexpected token")
}

func Test_DefaultPriorityContextInferrer(t *testing.T) {
expectedPriority := []string{
"log",
"metric",
"datapoint",
"spanevent",
"span",
"resource",
"scope",
"instrumentation_scope",
}

inferrer := defaultPriorityContextInferrer().(*priorityContextInferrer)
require.NotNil(t, inferrer)

for pri, ctx := range expectedPriority {
require.Equal(t, pri, inferrer.contextPriority[ctx])
}
}
Loading