Skip to content

Commit

Permalink
Change OTTL contexts to handle paths with context
Browse files Browse the repository at this point in the history
  • Loading branch information
edmocosta committed Dec 13, 2024
1 parent 28ede1d commit 074a57f
Show file tree
Hide file tree
Showing 27 changed files with 1,264 additions and 71 deletions.
25 changes: 23 additions & 2 deletions pkg/ottl/context_inferrer.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,15 @@ func (s *priorityContextInferrer) infer(statements []string) (string, error) {
}

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

if inferredContext == "" || pathContextPriority < inferredContextPriority {
inferredContext = p.Context
inferredContext = pathContext
inferredContextPriority = pathContextPriority
}
}
Expand All @@ -53,6 +54,26 @@ func (s *priorityContextInferrer) infer(statements []string) (string, error) {
return inferredContext, nil
}

// When a path has no dots separators (e.g.: resource), the grammar extracts it into the
// path.Fields slice, letting the path.Context empty. This function returns either the
// path.Context string or, if it's eligible and meets certain conditions, the first
// path.Fields name.
func (s *priorityContextInferrer) getContextCandidate(p path) string {
if p.Context != "" {
return p.Context
}
// If it has multiple fields or keys, it means the path has at least one dot on it,
// and isn't a context access.
if len(p.Fields) != 1 || len(p.Fields[0].Keys) > 0 {
return ""
}
_, ok := s.contextPriority[p.Fields[0].Name]
if ok {
return p.Fields[0].Name
}
return ""
}

// defaultPriorityContextInferrer is like newPriorityContextInferrer, but using the default
// context priorities and ignoring unknown/non-prioritized contexts.
func defaultPriorityContextInferrer() contextInferrer {
Expand Down
18 changes: 18 additions & 0 deletions pkg/ottl/context_inferrer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,24 @@ func Test_NewPriorityContextInferrer_Infer(t *testing.T) {
statements: []string{"set(span.foo, true) where span.bar == true"},
expected: "span",
},
{
name: "with context root access",
priority: []string{"resource", "foo"},
statements: []string{"set(foo.attributes[\"body\"], resource)"},
expected: "resource",
},
{
name: "with non-eligible context root access",
priority: []string{"resource", "foo"},
statements: []string{"set(foo.attributes[\"body\"], resource[\"foo\"])"},
expected: "foo",
},
{
name: "with non-prioritized context root access",
priority: []string{"foo"},
statements: []string{"set(resource, bar.attributes[\"body\"])"},
expected: "bar",
},
}

for _, tt := range tests {
Expand Down
6 changes: 5 additions & 1 deletion pkg/ottl/contexts/internal/metric.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import (
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
)

const (
MetricContextName = "metric"
)

type MetricContext interface {
GetMetric() pmetric.Metric
}
Expand All @@ -28,7 +32,7 @@ var MetricSymbolTable = map[ottl.EnumSymbol]ottl.Enum{
}

func MetricPathGetSetter[K MetricContext](path ottl.Path[K]) (ottl.GetSetter[K], error) {
if path == nil {
if isPathToContextRoot(path, MetricContextName) {
return accessMetric[K](), nil
}
switch path.Name() {
Expand Down
13 changes: 13 additions & 0 deletions pkg/ottl/contexts/internal/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,16 @@ func (k *TestKey[K]) String(_ context.Context, _ K) (*string, error) {
func (k *TestKey[K]) Int(_ context.Context, _ K) (*int64, error) {
return k.I, nil
}

// isPathToContextRoot return true if the given path is accessing the context's root object
// instead of specific fields.
func isPathToContextRoot[T any](path ottl.Path[T], ctx string) bool {
if path == nil {
return true
}
// path with matching context and empty name/keys/next
return path.Context() == ctx &&
path.Name() == "" &&
len(path.Keys()) == 0 &&
path.Next() == nil
}
76 changes: 76 additions & 0 deletions pkg/ottl/contexts/internal/path_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package internal

import (
"testing"

"github.com/stretchr/testify/assert"

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

func Test_isPathToContextRoot(t *testing.T) {
tests := []struct {
name string
context string
path ottl.Path[any]
expected bool
}{
{
name: "with context and empty path",
context: "resource",
expected: true,
path: &TestPath[any]{
C: "resource",
N: "",
},
},
{
name: "with context and next path",
context: "resource",
expected: false,
path: &TestPath[any]{
C: "resource",
N: "",
NextPath: &TestPath[any]{
N: "foo",
},
},
},
{
name: "with context and path keys",
context: "resource",
expected: false,
path: &TestPath[any]{
C: "resource",
N: "",
KeySlice: []ottl.Key[any]{
&TestKey[any]{},
},
},
},
{
name: "with both context and path name",
context: "resource",
expected: false,
path: &TestPath[any]{
C: "resource",
N: "resource",
},
},
{
name: "with nil path",
context: "resource",
expected: true,
path: nil,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, isPathToContextRoot(tt.path, tt.context))
})
}
}
6 changes: 5 additions & 1 deletion pkg/ottl/contexts/internal/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@ import (
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
)

const (
ResourceContextName = "resource"
)

type ResourceContext interface {
GetResource() pcommon.Resource
GetResourceSchemaURLItem() SchemaURLItem
}

func ResourcePathGetSetter[K ResourceContext](path ottl.Path[K]) (ottl.GetSetter[K], error) {
if path == nil {
if isPathToContextRoot(path, ResourceContextName) {
return accessResource[K](), nil
}
switch path.Name() {
Expand Down
7 changes: 6 additions & 1 deletion pkg/ottl/contexts/internal/scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,18 @@ import (
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
)

const (
InstrumentationScopeContextName = "instrumentation_scope"
ScopeContextName = "scope"
)

type InstrumentationScopeContext interface {
GetInstrumentationScope() pcommon.InstrumentationScope
GetScopeSchemaURLItem() SchemaURLItem
}

func ScopePathGetSetter[K InstrumentationScopeContext](path ottl.Path[K]) (ottl.GetSetter[K], error) {
if path == nil {
if isPathToContextRoot(path, InstrumentationScopeContextName) || isPathToContextRoot(path, ScopeContextName) {
return accessInstrumentationScope[K](), nil
}
switch path.Name() {
Expand Down
23 changes: 23 additions & 0 deletions pkg/ottl/contexts/internal/scope_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,29 @@ func TestScopePathGetSetter(t *testing.T) {
s.AppendEmpty().SetEmptySlice().AppendEmpty().SetStr("new")
},
},
{
name: "scope",
path: &TestPath[*instrumentationScopeContext]{
C: "scope",
},
orig: refIS,
newVal: pcommon.NewInstrumentationScope(),
modified: func(is pcommon.InstrumentationScope) {
pcommon.NewInstrumentationScope().CopyTo(is)
},
},
{
name: "scope field",
path: &TestPath[*instrumentationScopeContext]{
C: "scope",
N: "name",
},
orig: refIS.Name(),
newVal: "newname",
modified: func(is pcommon.InstrumentationScope) {
is.SetName("newname")
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
17 changes: 9 additions & 8 deletions pkg/ottl/contexts/internal/span.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import (
)

const (
SpanContextName = "Span"
SpanContextName = "span"
SpanContextNameDescription = "Span"
)

type SpanContext interface {
Expand All @@ -38,7 +39,7 @@ var SpanSymbolTable = map[ottl.EnumSymbol]ottl.Enum{
}

func SpanPathGetSetter[K SpanContext](path ottl.Path[K]) (ottl.GetSetter[K], error) {
if path == nil {
if isPathToContextRoot(path, SpanContextName) {
return accessSpan[K](), nil
}
switch path.Name() {
Expand All @@ -48,7 +49,7 @@ func SpanPathGetSetter[K SpanContext](path ottl.Path[K]) (ottl.GetSetter[K], err
if nextPath.Name() == "string" {
return accessStringTraceID[K](), nil
}
return nil, FormatDefaultErrorMessage(nextPath.Name(), nextPath.String(), SpanContextName, SpanRef)
return nil, FormatDefaultErrorMessage(nextPath.Name(), nextPath.String(), SpanContextNameDescription, SpanRef)
}
return accessTraceID[K](), nil
case "span_id":
Expand All @@ -57,7 +58,7 @@ func SpanPathGetSetter[K SpanContext](path ottl.Path[K]) (ottl.GetSetter[K], err
if nextPath.Name() == "string" {
return accessStringSpanID[K](), nil
}
return nil, FormatDefaultErrorMessage(nextPath.Name(), nextPath.String(), SpanContextName, SpanRef)
return nil, FormatDefaultErrorMessage(nextPath.Name(), nextPath.String(), SpanContextNameDescription, SpanRef)
}
return accessSpanID[K](), nil
case "trace_state":
Expand All @@ -72,7 +73,7 @@ func SpanPathGetSetter[K SpanContext](path ottl.Path[K]) (ottl.GetSetter[K], err
if nextPath.Name() == "string" {
return accessStringParentSpanID[K](), nil
}
return nil, FormatDefaultErrorMessage(nextPath.Name(), nextPath.String(), SpanContextName, SpanRef)
return nil, FormatDefaultErrorMessage(nextPath.Name(), nextPath.String(), SpanContextNameDescription, SpanRef)
}
return accessParentSpanID[K](), nil
case "name":
Expand All @@ -86,7 +87,7 @@ func SpanPathGetSetter[K SpanContext](path ottl.Path[K]) (ottl.GetSetter[K], err
case "deprecated_string":
return accessDeprecatedStringKind[K](), nil
default:
return nil, FormatDefaultErrorMessage(nextPath.Name(), nextPath.String(), SpanContextName, SpanRef)
return nil, FormatDefaultErrorMessage(nextPath.Name(), nextPath.String(), SpanContextNameDescription, SpanRef)
}
}
return accessKind[K](), nil
Expand Down Expand Up @@ -123,12 +124,12 @@ func SpanPathGetSetter[K SpanContext](path ottl.Path[K]) (ottl.GetSetter[K], err
case "message":
return accessStatusMessage[K](), nil
default:
return nil, FormatDefaultErrorMessage(nextPath.Name(), nextPath.String(), SpanContextName, SpanRef)
return nil, FormatDefaultErrorMessage(nextPath.Name(), nextPath.String(), SpanContextNameDescription, SpanRef)
}
}
return accessStatus[K](), nil
default:
return nil, FormatDefaultErrorMessage(path.Name(), path.String(), SpanContextName, SpanRef)
return nil, FormatDefaultErrorMessage(path.Name(), path.String(), SpanContextNameDescription, SpanRef)
}
}

Expand Down
Loading

0 comments on commit 074a57f

Please sign in to comment.