Skip to content

Commit 3c64a31

Browse files
odubajDTtiffany76
andauthored
[pkg/ottl]: add IsRootSpan converter (#33729)
**Description:** <Describe what has changed.> <!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> Introduced `IsRootSpan()` converter function which returns `true` if the span in the corresponding context is root, that means its `parent_span_id` equals to hexadecimal representation of zero. In all other scenarios function returns `false`. **Link to tracking Issue:** #32918 **Testing:** - unit tests - e2e tests **Documentation:** <Describe the documentation added.> - README entry --------- Signed-off-by: odubajDT <[email protected]> Co-authored-by: Tiffany Hrabusa <[email protected]>
1 parent 6a5b9e6 commit 3c64a31

File tree

9 files changed

+192
-2
lines changed

9 files changed

+192
-2
lines changed

.chloggen/add-isrootspan.yaml

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: 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 IsRootSpan() converter function."
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: [32918]
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: Converter `IsRootSpan()` returns `true` if the span in the corresponding context is root, that means its `parent_span_id` equals to hexadecimal representation of zero. In all other scenarios function returns `false`.
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: []

internal/filter/filterottl/functions.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ import (
2121
)
2222

2323
func StandardSpanFuncs() map[string]ottl.Factory[ottlspan.TransformContext] {
24-
return ottlfuncs.StandardConverters[ottlspan.TransformContext]()
24+
m := ottlfuncs.StandardConverters[ottlspan.TransformContext]()
25+
isRootSpanFactory := ottlfuncs.NewIsRootSpanFactory()
26+
m[isRootSpanFactory.Name()] = isRootSpanFactory
27+
return m
2528
}
2629

2730
func StandardSpanEventFuncs() map[string]ottl.Factory[ottlspanevent.TransformContext] {

pkg/ottl/e2e/e2e_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@ import (
1212
"go.opentelemetry.io/collector/component/componenttest"
1313
"go.opentelemetry.io/collector/pdata/pcommon"
1414
"go.opentelemetry.io/collector/pdata/plog"
15+
"go.opentelemetry.io/collector/pdata/ptrace"
1516

1617
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottllog"
18+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlspan"
1719
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottlfuncs"
1820
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest/plogtest"
21+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest/ptracetest"
1922
)
2023

2124
var (
@@ -840,6 +843,41 @@ func Test_e2e_ottl_features(t *testing.T) {
840843
}
841844
}
842845

846+
func Test_ProcessTraces_TraceContext(t *testing.T) {
847+
tests := []struct {
848+
statement string
849+
want func(_ ottlspan.TransformContext)
850+
}{
851+
{
852+
statement: `set(attributes["entrypoint-root"], name) where IsRootSpan()`,
853+
want: func(tCtx ottlspan.TransformContext) {
854+
tCtx.GetSpan().Attributes().PutStr("entrypoint-root", "operationB")
855+
},
856+
},
857+
}
858+
859+
for _, tt := range tests {
860+
t.Run(tt.statement, func(t *testing.T) {
861+
settings := componenttest.NewNopTelemetrySettings()
862+
funcs := ottlfuncs.StandardFuncs[ottlspan.TransformContext]()
863+
isRootSpanFactory := ottlfuncs.NewIsRootSpanFactory()
864+
funcs[isRootSpanFactory.Name()] = isRootSpanFactory
865+
spanParser, err := ottlspan.NewParser(funcs, settings)
866+
assert.NoError(t, err)
867+
spanStatements, err := spanParser.ParseStatement(tt.statement)
868+
assert.NoError(t, err)
869+
870+
tCtx := constructSpanTransformContext()
871+
_, _, _ = spanStatements.Execute(context.Background(), tCtx)
872+
873+
exTCtx := constructSpanTransformContext()
874+
tt.want(exTCtx)
875+
876+
assert.NoError(t, ptracetest.CompareResourceSpans(newResourceSpans(exTCtx), newResourceSpans(tCtx)))
877+
})
878+
}
879+
}
880+
843881
func constructLogTransformContext() ottllog.TransformContext {
844882
resource := pcommon.NewResource()
845883
resource.Attributes().PutStr("host.name", "localhost")
@@ -873,6 +911,18 @@ func constructLogTransformContext() ottllog.TransformContext {
873911
return ottllog.NewTransformContext(logRecord, scope, resource, plog.NewScopeLogs(), plog.NewResourceLogs())
874912
}
875913

914+
func constructSpanTransformContext() ottlspan.TransformContext {
915+
resource := pcommon.NewResource()
916+
917+
scope := pcommon.NewInstrumentationScope()
918+
scope.SetName("scope")
919+
920+
td := ptrace.NewSpan()
921+
fillSpanOne(td)
922+
923+
return ottlspan.NewTransformContext(td, scope, resource, ptrace.NewScopeSpans(), ptrace.NewResourceSpans())
924+
}
925+
876926
func newResourceLogs(tCtx ottllog.TransformContext) plog.ResourceLogs {
877927
rl := plog.NewResourceLogs()
878928
tCtx.GetResource().CopyTo(rl.Resource())
@@ -882,3 +932,19 @@ func newResourceLogs(tCtx ottllog.TransformContext) plog.ResourceLogs {
882932
tCtx.GetLogRecord().CopyTo(l)
883933
return rl
884934
}
935+
936+
func newResourceSpans(tCtx ottlspan.TransformContext) ptrace.ResourceSpans {
937+
rl := ptrace.NewResourceSpans()
938+
tCtx.GetResource().CopyTo(rl.Resource())
939+
sl := rl.ScopeSpans().AppendEmpty()
940+
tCtx.GetInstrumentationScope().CopyTo(sl.Scope())
941+
l := sl.Spans().AppendEmpty()
942+
tCtx.GetSpan().CopyTo(l)
943+
return rl
944+
}
945+
946+
func fillSpanOne(span ptrace.Span) {
947+
span.SetName("operationB")
948+
span.SetSpanID(spanID)
949+
span.SetTraceID(traceID)
950+
}

pkg/ottl/ottlfuncs/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,7 @@ Available Converters:
424424
- [IsBool](#isbool)
425425
- [IsDouble](#isdouble)
426426
- [IsInt](#isint)
427+
- [IsRootSpan](#isrootspan)
427428
- [IsMap](#ismap)
428429
- [IsMatch](#ismatch)
429430
- [IsList](#islist)
@@ -744,6 +745,23 @@ Examples:
744745

745746
- `IsInt(attributes["maybe a int"])`
746747

748+
### IsRootSpan
749+
750+
`IsRootSpan()`
751+
752+
The `IsRootSpan` Converter returns `true` if the span in the corresponding context is root, which means
753+
its `parent_span_id` is equal to hexadecimal representation of zero.
754+
755+
This function is supported with [OTTL span context](../contexts/ottlspan/README.md). In any other context it is not supported.
756+
757+
The function returns `false` in all other scenarios, including `parent_span_id == ""` or `parent_span_id == nil`.
758+
759+
Examples:
760+
761+
- `IsRootSpan()`
762+
763+
- `set(attributes["isRoot"], "true") where IsRootSpan()`
764+
747765
### IsMap
748766

749767
`IsMap(value)`
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package ottlfuncs // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottlfuncs"
5+
6+
import (
7+
"context"
8+
9+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
10+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlspan"
11+
)
12+
13+
func NewIsRootSpanFactory() ottl.Factory[ottlspan.TransformContext] {
14+
return ottl.NewFactory("IsRootSpan", nil, createIsRootSpanFunction)
15+
}
16+
17+
func createIsRootSpanFunction(_ ottl.FunctionContext, _ ottl.Arguments) (ottl.ExprFunc[ottlspan.TransformContext], error) {
18+
return isRootSpan()
19+
}
20+
21+
func isRootSpan() (ottl.ExprFunc[ottlspan.TransformContext], error) {
22+
return func(_ context.Context, tCtx ottlspan.TransformContext) (any, error) {
23+
return tCtx.GetSpan().ParentSpanID().IsEmpty(), nil
24+
}, nil
25+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package ottlfuncs // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottlfuncs"
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
"go.opentelemetry.io/collector/pdata/pcommon"
12+
"go.opentelemetry.io/collector/pdata/ptrace"
13+
14+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlspan"
15+
)
16+
17+
func Test_IsRootSpan(t *testing.T) {
18+
exprFunc, err := isRootSpan()
19+
assert.NoError(t, err)
20+
21+
// root span
22+
spanRoot := ptrace.NewSpan()
23+
spanRoot.SetParentSpanID(pcommon.SpanID{
24+
0, 0, 0, 0, 0, 0, 0, 0,
25+
})
26+
27+
value, err := exprFunc(nil, ottlspan.NewTransformContext(spanRoot, pcommon.NewInstrumentationScope(), pcommon.NewResource(), ptrace.NewScopeSpans(), ptrace.NewResourceSpans()))
28+
assert.NoError(t, err)
29+
require.Equal(t, true, value)
30+
31+
// non root span
32+
spanNonRoot := ptrace.NewSpan()
33+
spanNonRoot.SetParentSpanID(pcommon.SpanID{
34+
1, 0, 0, 0, 0, 0, 0, 0,
35+
})
36+
37+
value, err = exprFunc(nil, ottlspan.NewTransformContext(spanNonRoot, pcommon.NewInstrumentationScope(), pcommon.NewResource(), ptrace.NewScopeSpans(), ptrace.NewResourceSpans()))
38+
assert.NoError(t, err)
39+
require.Equal(t, false, value)
40+
}

processor/transformprocessor/internal/traces/functions.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ import (
1212

1313
func SpanFunctions() map[string]ottl.Factory[ottlspan.TransformContext] {
1414
// No trace-only functions yet.
15-
return ottlfuncs.StandardFuncs[ottlspan.TransformContext]()
15+
m := ottlfuncs.StandardFuncs[ottlspan.TransformContext]()
16+
isRootSpanFactory := ottlfuncs.NewIsRootSpanFactory()
17+
m[isRootSpanFactory.Name()] = isRootSpanFactory
18+
return m
1619
}
1720

1821
func SpanEventFunctions() map[string]ottl.Factory[ottlspanevent.TransformContext] {

processor/transformprocessor/internal/traces/functions_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import (
1616

1717
func Test_SpanFunctions(t *testing.T) {
1818
expected := ottlfuncs.StandardFuncs[ottlspan.TransformContext]()
19+
isRootSpanFactory := ottlfuncs.NewIsRootSpanFactory()
20+
expected[isRootSpanFactory.Name()] = isRootSpanFactory
1921
actual := SpanFunctions()
2022
require.Equal(t, len(expected), len(actual))
2123
for k := range actual {

processor/transformprocessor/internal/traces/processor_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,12 @@ func Test_ProcessTraces_TraceContext(t *testing.T) {
344344
td.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(1).Attributes().PutStr("entrypoint", "operationB")
345345
},
346346
},
347+
{
348+
statement: `set(attributes["entrypoint-root"], name) where IsRootSpan()`,
349+
want: func(td ptrace.Traces) {
350+
td.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(1).Attributes().PutStr("entrypoint-root", "operationB")
351+
},
352+
},
347353
{
348354
statement: `set(attributes["test"], ConvertCase(name, "lower")) where name == "operationA"`,
349355
want: func(td ptrace.Traces) {

0 commit comments

Comments
 (0)