diff --git a/.chloggen/ottl-span-event-index.yaml b/.chloggen/ottl-span-event-index.yaml new file mode 100644 index 000000000000..b36765ea0fcd --- /dev/null +++ b/.chloggen/ottl-span-event-index.yaml @@ -0,0 +1,27 @@ +# 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 `event_index` to the available paths of the span event context + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [35778] + +# (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: + +# 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: [] diff --git a/pkg/ottl/contexts/ottlspanevent/README.md b/pkg/ottl/contexts/ottlspanevent/README.md index 0de4ddb18774..95194ed779dc 100644 --- a/pkg/ottl/contexts/ottlspanevent/README.md +++ b/pkg/ottl/contexts/ottlspanevent/README.md @@ -7,26 +7,27 @@ In general, the Span Event Context supports accessing pdata using the field name The following paths are supported. -| path | field accessed | type | -|----------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------| -| cache | the value of the current transform context's temporary cache. cache can be used as a temporary placeholder for data during complex transformations | pcommon.Map | -| cache\[""\] | the value of an item in cache. Supports multiple indexes to access nested fields. | string, bool, int64, float64, pcommon.Map, pcommon.Slice, []byte or nil | -| resource | resource of the span event being processed | pcommon.Resource | -| resource.attributes | resource attributes of the span event being processed | pcommon.Map | -| resource.attributes\[""\] | the value of the resource attribute of the span event being processed. Supports multiple indexes to access nested fields. | string, bool, int64, float64, pcommon.Map, pcommon.Slice, []byte or nil | -| instrumentation_scope | instrumentation scope of the span event being processed | pcommon.InstrumentationScope | -| instrumentation_scope.name | name of the instrumentation scope of the span event being processed | string | -| instrumentation_scope.version | version of the instrumentation scope of the span event being processed | string | -| instrumentation_scope.attributes | instrumentation scope attributes of the span event being processed | pcommon.Map | -| instrumentation_scope.attributes\[""\] | the value of the instrumentation scope attribute of the span event being processed. Supports multiple indexes to access nested fields. | string, bool, int64, float64, pcommon.Map, pcommon.Slice, []byte or nil | -| span | span of the span event being processed | ptrace.Span | +| path | field accessed | type | +|----------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------| +| cache | the value of the current transform context's temporary cache. cache can be used as a temporary placeholder for data during complex transformations | pcommon.Map | +| cache\[""\] | the value of an item in cache. Supports multiple indexes to access nested fields. | string, bool, int64, float64, pcommon.Map, pcommon.Slice, []byte or nil | +| resource | resource of the span event being processed | pcommon.Resource | +| resource.attributes | resource attributes of the span event being processed | pcommon.Map | +| resource.attributes\[""\] | the value of the resource attribute of the span event being processed. Supports multiple indexes to access nested fields. | string, bool, int64, float64, pcommon.Map, pcommon.Slice, []byte or nil | +| instrumentation_scope | instrumentation scope of the span event being processed | pcommon.InstrumentationScope | +| instrumentation_scope.name | name of the instrumentation scope of the span event being processed | string | +| instrumentation_scope.version | version of the instrumentation scope of the span event being processed | string | +| instrumentation_scope.attributes | instrumentation scope attributes of the span event being processed | pcommon.Map | +| instrumentation_scope.attributes\[""\] | the value of the instrumentation scope attribute of the span event being processed. Supports multiple indexes to access nested fields. | string, bool, int64, float64, pcommon.Map, pcommon.Slice, []byte or nil | +| span | span of the span event being processed | ptrace.Span | | span.* | All fields exposed by the [ottlspan context](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/pkg/ottl/contexts/ottlspan) can accessed via `span.` | varies | -| attributes | attributes of the span event being processed | pcommon.Map | -| attributes\[""\] | the value of the attribute of the span event being processed. Supports multiple indexes to access nested fields. | string, bool, int64, float64, pcommon.Map, pcommon.Slice, []byte or nil | -| time_unix_nano | time_unix_nano of the span event being processed | int64 | -| time | time of the span event being processed | `time.Time` | -| name | name of the span event being processed | string | -| dropped_attributes_count | dropped_attributes_count of the span event being processed | int64 | +| attributes | attributes of the span event being processed | pcommon.Map | +| attributes\[""\] | the value of the attribute of the span event being processed. Supports multiple indexes to access nested fields. | string, bool, int64, float64, pcommon.Map, pcommon.Slice, []byte or nil | +| time_unix_nano | time_unix_nano of the span event being processed | int64 | +| time | time of the span event being processed | `time.Time` | +| name | name of the span event being processed | string | +| dropped_attributes_count | dropped_attributes_count of the span event being processed | int64 | +| event_index | index of the span event within the span | int64 | ## Enums diff --git a/pkg/ottl/contexts/ottlspanevent/span_events.go b/pkg/ottl/contexts/ottlspanevent/span_events.go index 839147639d6c..1944451e39f3 100644 --- a/pkg/ottl/contexts/ottlspanevent/span_events.go +++ b/pkg/ottl/contexts/ottlspanevent/span_events.go @@ -39,6 +39,7 @@ type TransformContext struct { cache pcommon.Map scopeSpans ptrace.ScopeSpans resouceSpans ptrace.ResourceSpans + eventIndex int64 } func (tCtx TransformContext) MarshalLogObject(encoder zapcore.ObjectEncoder) error { @@ -47,6 +48,7 @@ func (tCtx TransformContext) MarshalLogObject(encoder zapcore.ObjectEncoder) err err = errors.Join(err, encoder.AddObject("span", logging.Span(tCtx.span))) err = errors.Join(err, encoder.AddObject("spanevent", logging.SpanEvent(tCtx.spanEvent))) err = errors.Join(err, encoder.AddObject("cache", logging.Map(tCtx.cache))) + encoder.AddInt64("eventindex", tCtx.eventIndex) return err } @@ -55,6 +57,14 @@ type Option func(*ottl.Parser[TransformContext]) type TransformContextOption func(*TransformContext) func NewTransformContext(spanEvent ptrace.SpanEvent, span ptrace.Span, instrumentationScope pcommon.InstrumentationScope, resource pcommon.Resource, scopeSpans ptrace.ScopeSpans, resourceSpans ptrace.ResourceSpans, options ...TransformContextOption) TransformContext { + // calculate the event index + index := -1 + for i := 0; i < span.Events().Len(); i++ { + if span.Events().At(i) == spanEvent { + index = i + break + } + } tc := TransformContext{ spanEvent: spanEvent, span: span, @@ -63,6 +73,7 @@ func NewTransformContext(spanEvent ptrace.SpanEvent, span ptrace.Span, instrumen cache: pcommon.NewMap(), scopeSpans: scopeSpans, resouceSpans: resourceSpans, + eventIndex: int64(index), } for _, opt := range options { opt(&tc) @@ -107,6 +118,10 @@ func (tCtx TransformContext) GetResourceSchemaURLItem() internal.SchemaURLItem { return tCtx.resouceSpans } +func (tCtx TransformContext) GetEventIndex() int64 { + return tCtx.eventIndex +} + func NewParser(functions map[string]ottl.Factory[TransformContext], telemetrySettings component.TelemetrySettings, options ...Option) (ottl.Parser[TransformContext], error) { pep := pathExpressionParser{telemetrySettings} p, err := ottl.NewParser[TransformContext]( @@ -221,6 +236,8 @@ func (pep *pathExpressionParser) parsePath(path ottl.Path[TransformContext]) (ot return accessSpanEventAttributesKey(path.Keys()), nil case "dropped_attributes_count": return accessSpanEventDroppedAttributeCount(), nil + case "event_index": + return accessSpanEventIndex(), nil default: return nil, internal.FormatDefaultErrorMessage(path.Name(), path.String(), contextNameDescription, internal.SpanEventRef) } @@ -348,3 +365,14 @@ func accessSpanEventDroppedAttributeCount() ottl.StandardGetSetter[TransformCont }, } } + +func accessSpanEventIndex() ottl.StandardGetSetter[TransformContext] { + return ottl.StandardGetSetter[TransformContext]{ + Getter: func(_ context.Context, tCtx TransformContext) (any, error) { + return tCtx.eventIndex, nil + }, + Setter: func(_ context.Context, _ TransformContext, _ any) error { + return errors.New("the 'event_index' path cannot be modified") + }, + } +} diff --git a/pkg/ottl/contexts/ottlspanevent/span_events_test.go b/pkg/ottl/contexts/ottlspanevent/span_events_test.go index 29abc6abce1d..6a67c049ebba 100644 --- a/pkg/ottl/contexts/ottlspanevent/span_events_test.go +++ b/pkg/ottl/contexts/ottlspanevent/span_events_test.go @@ -49,11 +49,12 @@ func Test_newPathGetSetter(t *testing.T) { newMap["k2"] = newMap2 tests := []struct { - name string - path ottl.Path[TransformContext] - orig any - newVal any - modified func(spanEvent ptrace.SpanEvent, span ptrace.Span, il pcommon.InstrumentationScope, resource pcommon.Resource, cache pcommon.Map) + name string + path ottl.Path[TransformContext] + orig any + newVal any + expectSetterError bool + modified func(spanEvent ptrace.SpanEvent, span ptrace.Span, il pcommon.InstrumentationScope, resource pcommon.Resource, cache pcommon.Map) }{ { name: "span event time", @@ -407,6 +408,15 @@ func Test_newPathGetSetter(t *testing.T) { spanEvent.SetDroppedAttributesCount(20) }, }, + { + name: "event_index", + path: &internal.TestPath[TransformContext]{ + N: "event_index", + }, + orig: int64(0), + newVal: int64(1), + expectSetterError: true, + }, } // Copy all tests cases and sets the path.Context value to the generated ones. // It ensures all exiting field access also work when the path context is set. @@ -433,6 +443,10 @@ func Test_newPathGetSetter(t *testing.T) { assert.Equal(t, tt.orig, got) err = accessor.Set(context.Background(), tCtx, tt.newVal) + if tt.expectSetterError { + assert.Error(t, err) + return + } assert.NoError(t, err) exSpanEvent, exSpan, exIl, exRes := createTelemetry() @@ -521,7 +535,10 @@ func Test_newPathGetSetter_higherContextPath(t *testing.T) { } func createTelemetry() (ptrace.SpanEvent, ptrace.Span, pcommon.InstrumentationScope, pcommon.Resource) { - spanEvent := ptrace.NewSpanEvent() + span := ptrace.NewSpan() + span.SetName("test") + + spanEvent := span.Events().AppendEmpty() spanEvent.SetName("bear") spanEvent.SetTimestamp(pcommon.NewTimestampFromTime(time.UnixMilli(100))) @@ -562,9 +579,6 @@ func createTelemetry() (ptrace.SpanEvent, ptrace.Span, pcommon.InstrumentationSc s := spanEvent.Attributes().PutEmptySlice("slice") s.AppendEmpty().SetEmptyMap().PutStr("map", "pass") - span := ptrace.NewSpan() - span.SetName("test") - il := pcommon.NewInstrumentationScope() il.SetName("library") il.SetVersion("version") diff --git a/pkg/ottl/e2e/e2e_test.go b/pkg/ottl/e2e/e2e_test.go index e803a7bb3092..de8a4a4d8845 100644 --- a/pkg/ottl/e2e/e2e_test.go +++ b/pkg/ottl/e2e/e2e_test.go @@ -17,6 +17,7 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottllog" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlspan" + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlspanevent" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottlfuncs" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest/plogtest" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest/ptracetest" @@ -1276,6 +1277,40 @@ func Test_ProcessTraces_TraceContext(t *testing.T) { } } +func Test_ProcessSpanEvents(t *testing.T) { + tests := []struct { + statement string + want func(_ ottlspanevent.TransformContext) + }{ + { + statement: `set(attributes["index"], event_index)`, + want: func(tCtx ottlspanevent.TransformContext) { + tCtx.GetSpanEvent().Attributes().PutInt("index", 0) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.statement, func(t *testing.T) { + settings := componenttest.NewNopTelemetrySettings() + funcs := ottlfuncs.StandardFuncs[ottlspanevent.TransformContext]() + + spanEventParser, err := ottlspanevent.NewParser(funcs, settings) + assert.NoError(t, err) + spanStatements, err := spanEventParser.ParseStatement(tt.statement) + assert.NoError(t, err) + + tCtx := constructSpanEventTransformContext() + _, _, _ = spanStatements.Execute(context.Background(), tCtx) + + exTCtx := constructSpanEventTransformContext() + tt.want(exTCtx) + + assert.NoError(t, ptracetest.CompareSpanEvent(newSpanEvent(exTCtx), newSpanEvent(tCtx))) + }) + } +} + func constructLogTransformContext() ottllog.TransformContext { resource := pcommon.NewResource() resource.Attributes().PutStr("host.name", "localhost") @@ -1381,6 +1416,21 @@ func constructSpanTransformContext() ottlspan.TransformContext { return ottlspan.NewTransformContext(td, scope, resource, ptrace.NewScopeSpans(), ptrace.NewResourceSpans()) } +func constructSpanEventTransformContext() ottlspanevent.TransformContext { + resource := pcommon.NewResource() + + scope := pcommon.NewInstrumentationScope() + scope.SetName("scope") + + span := ptrace.NewSpan() + fillSpanOne(span) + + ev1 := span.Events().AppendEmpty() + ev1.SetName("event-1") + + return ottlspanevent.NewTransformContext(ev1, span, scope, resource, ptrace.NewScopeSpans(), ptrace.NewResourceSpans()) +} + func newResourceLogs(tCtx ottllog.TransformContext) plog.ResourceLogs { rl := plog.NewResourceLogs() tCtx.GetResource().CopyTo(rl.Resource()) @@ -1401,6 +1451,12 @@ func newResourceSpans(tCtx ottlspan.TransformContext) ptrace.ResourceSpans { return rl } +func newSpanEvent(tCtx ottlspanevent.TransformContext) ptrace.SpanEvent { + dst := ptrace.NewSpanEvent() + tCtx.GetSpanEvent().CopyTo(dst) + return dst +} + func fillSpanOne(span ptrace.Span) { span.SetName("operationB") span.SetSpanID(spanID)