diff --git a/.chloggen/ottl-spanevents-context.yaml b/.chloggen/ottl-spanevents-context.yaml new file mode 100755 index 000000000000..14a9fef1ffac --- /dev/null +++ b/.chloggen/ottl-spanevents-context.yaml @@ -0,0 +1,16 @@ +# 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 new Span Event context to allow for efficient transformation of Span Event telemetry. + +# One or more tracking issues related to the change +issues: [14907] + +# (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: diff --git a/pkg/ottl/contexts/internal/ottlcommon/span.go b/pkg/ottl/contexts/internal/ottlcommon/span.go new file mode 100644 index 000000000000..e8b40ef5cf92 --- /dev/null +++ b/pkg/ottl/contexts/internal/ottlcommon/span.go @@ -0,0 +1,460 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ottlcommon // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/internal/ottlcommon" + +import ( + "encoding/hex" + "errors" + "fmt" + "time" + + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/ptrace" + "go.opentelemetry.io/otel/trace" + + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" +) + +type SpanContext interface { + GetSpan() ptrace.Span +} + +var SpanSymbolTable = map[ottl.EnumSymbol]ottl.Enum{ + "SPAN_KIND_UNSPECIFIED": ottl.Enum(ptrace.SpanKindUnspecified), + "SPAN_KIND_INTERNAL": ottl.Enum(ptrace.SpanKindInternal), + "SPAN_KIND_SERVER": ottl.Enum(ptrace.SpanKindServer), + "SPAN_KIND_CLIENT": ottl.Enum(ptrace.SpanKindClient), + "SPAN_KIND_PRODUCER": ottl.Enum(ptrace.SpanKindProducer), + "SPAN_KIND_CONSUMER": ottl.Enum(ptrace.SpanKindConsumer), + "STATUS_CODE_UNSET": ottl.Enum(ptrace.StatusCodeUnset), + "STATUS_CODE_OK": ottl.Enum(ptrace.StatusCodeOk), + "STATUS_CODE_ERROR": ottl.Enum(ptrace.StatusCodeError), +} + +func SpanPathGetSetter[K SpanContext](path []ottl.Field) (ottl.GetSetter[K], error) { + if len(path) == 0 { + return accessSpan[K](), nil + } + + switch path[0].Name { + case "trace_id": + if len(path) == 1 { + return accessTraceID[K](), nil + } + if path[1].Name == "string" { + return accessStringTraceID[K](), nil + } + case "span_id": + if len(path) == 1 { + return accessSpanID[K](), nil + } + if path[1].Name == "string" { + return accessStringSpanID[K](), nil + } + case "trace_state": + mapKey := path[0].MapKey + if mapKey == nil { + return accessTraceState[K](), nil + } + return accessTraceStateKey[K](mapKey), nil + case "parent_span_id": + return accessParentSpanID[K](), nil + case "name": + return accessSpanName[K](), nil + case "kind": + return accessKind[K](), nil + case "start_time_unix_nano": + return accessStartTimeUnixNano[K](), nil + case "end_time_unix_nano": + return accessEndTimeUnixNano[K](), nil + case "attributes": + mapKey := path[0].MapKey + if mapKey == nil { + return accessAttributes[K](), nil + } + return accessAttributesKey[K](mapKey), nil + case "dropped_attributes_count": + return accessSpanDroppedAttributesCount[K](), nil + case "events": + return accessEvents[K](), nil + case "dropped_events_count": + return accessDroppedEventsCount[K](), nil + case "links": + return accessLinks[K](), nil + case "dropped_links_count": + return accessDroppedLinksCount[K](), nil + case "status": + if len(path) == 1 { + return accessStatus[K](), nil + } + switch path[1].Name { + case "code": + return accessStatusCode[K](), nil + case "message": + return accessStatusMessage[K](), nil + } + } + + return nil, fmt.Errorf("invalid span path expression %v", path) +} + +func accessSpan[K SpanContext]() ottl.StandardGetSetter[K] { + return ottl.StandardGetSetter[K]{ + Getter: func(ctx K) (interface{}, error) { + return ctx.GetSpan(), nil + }, + Setter: func(ctx K, val interface{}) error { + if newSpan, ok := val.(ptrace.Span); ok { + newSpan.CopyTo(ctx.GetSpan()) + } + return nil + }, + } +} + +func accessTraceID[K SpanContext]() ottl.StandardGetSetter[K] { + return ottl.StandardGetSetter[K]{ + Getter: func(ctx K) (interface{}, error) { + return ctx.GetSpan().TraceID(), nil + }, + Setter: func(ctx K, val interface{}) error { + if newTraceID, ok := val.(pcommon.TraceID); ok { + ctx.GetSpan().SetTraceID(newTraceID) + } + return nil + }, + } +} + +func accessStringTraceID[K SpanContext]() ottl.StandardGetSetter[K] { + return ottl.StandardGetSetter[K]{ + Getter: func(ctx K) (interface{}, error) { + return ctx.GetSpan().TraceID().HexString(), nil + }, + Setter: func(ctx K, val interface{}) error { + if str, ok := val.(string); ok { + if traceID, err := parseTraceID(str); err == nil { + ctx.GetSpan().SetTraceID(traceID) + } + } + return nil + }, + } +} + +func accessSpanID[K SpanContext]() ottl.StandardGetSetter[K] { + return ottl.StandardGetSetter[K]{ + Getter: func(ctx K) (interface{}, error) { + return ctx.GetSpan().SpanID(), nil + }, + Setter: func(ctx K, val interface{}) error { + if newSpanID, ok := val.(pcommon.SpanID); ok { + ctx.GetSpan().SetSpanID(newSpanID) + } + return nil + }, + } +} + +func accessStringSpanID[K SpanContext]() ottl.StandardGetSetter[K] { + return ottl.StandardGetSetter[K]{ + Getter: func(ctx K) (interface{}, error) { + return ctx.GetSpan().SpanID().HexString(), nil + }, + Setter: func(ctx K, val interface{}) error { + if str, ok := val.(string); ok { + if spanID, err := parseSpanID(str); err == nil { + ctx.GetSpan().SetSpanID(spanID) + } + } + return nil + }, + } +} + +func accessTraceState[K SpanContext]() ottl.StandardGetSetter[K] { + return ottl.StandardGetSetter[K]{ + Getter: func(ctx K) (interface{}, error) { + return ctx.GetSpan().TraceState().AsRaw(), nil + }, + Setter: func(ctx K, val interface{}) error { + if str, ok := val.(string); ok { + ctx.GetSpan().TraceState().FromRaw(str) + } + return nil + }, + } +} + +func accessTraceStateKey[K SpanContext](mapKey *string) ottl.StandardGetSetter[K] { + return ottl.StandardGetSetter[K]{ + Getter: func(ctx K) (interface{}, error) { + if ts, err := trace.ParseTraceState(ctx.GetSpan().TraceState().AsRaw()); err == nil { + return ts.Get(*mapKey), nil + } + return nil, nil + }, + Setter: func(ctx K, val interface{}) error { + if str, ok := val.(string); ok { + if ts, err := trace.ParseTraceState(ctx.GetSpan().TraceState().AsRaw()); err == nil { + if updated, err := ts.Insert(*mapKey, str); err == nil { + ctx.GetSpan().TraceState().FromRaw(updated.String()) + } + } + } + return nil + }, + } +} + +func accessParentSpanID[K SpanContext]() ottl.StandardGetSetter[K] { + return ottl.StandardGetSetter[K]{ + Getter: func(ctx K) (interface{}, error) { + return ctx.GetSpan().ParentSpanID(), nil + }, + Setter: func(ctx K, val interface{}) error { + if newParentSpanID, ok := val.(pcommon.SpanID); ok { + ctx.GetSpan().SetParentSpanID(newParentSpanID) + } + return nil + }, + } +} + +func accessSpanName[K SpanContext]() ottl.StandardGetSetter[K] { + return ottl.StandardGetSetter[K]{ + Getter: func(ctx K) (interface{}, error) { + return ctx.GetSpan().Name(), nil + }, + Setter: func(ctx K, val interface{}) error { + if str, ok := val.(string); ok { + ctx.GetSpan().SetName(str) + } + return nil + }, + } +} + +func accessKind[K SpanContext]() ottl.StandardGetSetter[K] { + return ottl.StandardGetSetter[K]{ + Getter: func(ctx K) (interface{}, error) { + return int64(ctx.GetSpan().Kind()), nil + }, + Setter: func(ctx K, val interface{}) error { + if i, ok := val.(int64); ok { + ctx.GetSpan().SetKind(ptrace.SpanKind(i)) + } + return nil + }, + } +} + +func accessStartTimeUnixNano[K SpanContext]() ottl.StandardGetSetter[K] { + return ottl.StandardGetSetter[K]{ + Getter: func(ctx K) (interface{}, error) { + return ctx.GetSpan().StartTimestamp().AsTime().UnixNano(), nil + }, + Setter: func(ctx K, val interface{}) error { + if i, ok := val.(int64); ok { + ctx.GetSpan().SetStartTimestamp(pcommon.NewTimestampFromTime(time.Unix(0, i))) + } + return nil + }, + } +} + +func accessEndTimeUnixNano[K SpanContext]() ottl.StandardGetSetter[K] { + return ottl.StandardGetSetter[K]{ + Getter: func(ctx K) (interface{}, error) { + return ctx.GetSpan().EndTimestamp().AsTime().UnixNano(), nil + }, + Setter: func(ctx K, val interface{}) error { + if i, ok := val.(int64); ok { + ctx.GetSpan().SetEndTimestamp(pcommon.NewTimestampFromTime(time.Unix(0, i))) + } + return nil + }, + } +} + +func accessAttributes[K SpanContext]() ottl.StandardGetSetter[K] { + return ottl.StandardGetSetter[K]{ + Getter: func(ctx K) (interface{}, error) { + return ctx.GetSpan().Attributes(), nil + }, + Setter: func(ctx K, val interface{}) error { + if attrs, ok := val.(pcommon.Map); ok { + attrs.CopyTo(ctx.GetSpan().Attributes()) + } + return nil + }, + } +} + +func accessAttributesKey[K SpanContext](mapKey *string) ottl.StandardGetSetter[K] { + return ottl.StandardGetSetter[K]{ + Getter: func(ctx K) (interface{}, error) { + return GetMapValue(ctx.GetSpan().Attributes(), *mapKey), nil + }, + Setter: func(ctx K, val interface{}) error { + SetMapValue(ctx.GetSpan().Attributes(), *mapKey, val) + return nil + }, + } +} + +func accessSpanDroppedAttributesCount[K SpanContext]() ottl.StandardGetSetter[K] { + return ottl.StandardGetSetter[K]{ + Getter: func(ctx K) (interface{}, error) { + return int64(ctx.GetSpan().DroppedAttributesCount()), nil + }, + Setter: func(ctx K, val interface{}) error { + if i, ok := val.(int64); ok { + ctx.GetSpan().SetDroppedAttributesCount(uint32(i)) + } + return nil + }, + } +} + +func accessEvents[K SpanContext]() ottl.StandardGetSetter[K] { + return ottl.StandardGetSetter[K]{ + Getter: func(ctx K) (interface{}, error) { + return ctx.GetSpan().Events(), nil + }, + Setter: func(ctx K, val interface{}) error { + if slc, ok := val.(ptrace.SpanEventSlice); ok { + ctx.GetSpan().Events().RemoveIf(func(event ptrace.SpanEvent) bool { + return true + }) + slc.CopyTo(ctx.GetSpan().Events()) + } + return nil + }, + } +} + +func accessDroppedEventsCount[K SpanContext]() ottl.StandardGetSetter[K] { + return ottl.StandardGetSetter[K]{ + Getter: func(ctx K) (interface{}, error) { + return int64(ctx.GetSpan().DroppedEventsCount()), nil + }, + Setter: func(ctx K, val interface{}) error { + if i, ok := val.(int64); ok { + ctx.GetSpan().SetDroppedEventsCount(uint32(i)) + } + return nil + }, + } +} + +func accessLinks[K SpanContext]() ottl.StandardGetSetter[K] { + return ottl.StandardGetSetter[K]{ + Getter: func(ctx K) (interface{}, error) { + return ctx.GetSpan().Links(), nil + }, + Setter: func(ctx K, val interface{}) error { + if slc, ok := val.(ptrace.SpanLinkSlice); ok { + ctx.GetSpan().Links().RemoveIf(func(event ptrace.SpanLink) bool { + return true + }) + slc.CopyTo(ctx.GetSpan().Links()) + } + return nil + }, + } +} + +func accessDroppedLinksCount[K SpanContext]() ottl.StandardGetSetter[K] { + return ottl.StandardGetSetter[K]{ + Getter: func(ctx K) (interface{}, error) { + return int64(ctx.GetSpan().DroppedLinksCount()), nil + }, + Setter: func(ctx K, val interface{}) error { + if i, ok := val.(int64); ok { + ctx.GetSpan().SetDroppedLinksCount(uint32(i)) + } + return nil + }, + } +} + +func accessStatus[K SpanContext]() ottl.StandardGetSetter[K] { + return ottl.StandardGetSetter[K]{ + Getter: func(ctx K) (interface{}, error) { + return ctx.GetSpan().Status(), nil + }, + Setter: func(ctx K, val interface{}) error { + if status, ok := val.(ptrace.Status); ok { + status.CopyTo(ctx.GetSpan().Status()) + } + return nil + }, + } +} + +func accessStatusCode[K SpanContext]() ottl.StandardGetSetter[K] { + return ottl.StandardGetSetter[K]{ + Getter: func(ctx K) (interface{}, error) { + return int64(ctx.GetSpan().Status().Code()), nil + }, + Setter: func(ctx K, val interface{}) error { + if i, ok := val.(int64); ok { + ctx.GetSpan().Status().SetCode(ptrace.StatusCode(i)) + } + return nil + }, + } +} + +func accessStatusMessage[K SpanContext]() ottl.StandardGetSetter[K] { + return ottl.StandardGetSetter[K]{ + Getter: func(ctx K) (interface{}, error) { + return ctx.GetSpan().Status().Message(), nil + }, + Setter: func(ctx K, val interface{}) error { + if str, ok := val.(string); ok { + ctx.GetSpan().Status().SetMessage(str) + } + return nil + }, + } +} + +func parseSpanID(spanIDStr string) (pcommon.SpanID, error) { + id, err := hex.DecodeString(spanIDStr) + if err != nil { + return pcommon.SpanID{}, err + } + if len(id) != 8 { + return pcommon.SpanID{}, errors.New("span ids must be 8 bytes") + } + var idArr [8]byte + copy(idArr[:8], id) + return pcommon.SpanID(idArr), nil +} + +func parseTraceID(traceIDStr string) (pcommon.TraceID, error) { + id, err := hex.DecodeString(traceIDStr) + if err != nil { + return pcommon.TraceID{}, err + } + if len(id) != 16 { + return pcommon.TraceID{}, errors.New("traces ids must be 16 bytes") + } + var idArr [16]byte + copy(idArr[:16], id) + return pcommon.TraceID(idArr), nil +} diff --git a/pkg/ottl/contexts/internal/ottlcommon/span_test.go b/pkg/ottl/contexts/internal/ottlcommon/span_test.go new file mode 100644 index 000000000000..4c0ca077ec0a --- /dev/null +++ b/pkg/ottl/contexts/internal/ottlcommon/span_test.go @@ -0,0 +1,576 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ottlcommon + +import ( + "encoding/hex" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/ptrace" + + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottltest" +) + +var ( + traceID = [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} + traceID2 = [16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1} + spanID = [8]byte{1, 2, 3, 4, 5, 6, 7, 8} + spanID2 = [8]byte{8, 7, 6, 5, 4, 3, 2, 1} +) + +func TestSpanPathGetSetter(t *testing.T) { + refSpan := createSpan() + + newAttrs := pcommon.NewMap() + newAttrs.PutStr("hello", "world") + + newEvents := ptrace.NewSpanEventSlice() + newEvents.AppendEmpty().SetName("new event") + + newLinks := ptrace.NewSpanLinkSlice() + newLinks.AppendEmpty().SetSpanID(spanID2) + + newStatus := ptrace.NewStatus() + newStatus.SetMessage("new status") + + tests := []struct { + name string + path []ottl.Field + orig interface{} + newVal interface{} + modified func(span ptrace.Span) + }{ + { + name: "trace_id", + path: []ottl.Field{ + { + Name: "trace_id", + }, + }, + orig: pcommon.TraceID(traceID), + newVal: pcommon.TraceID(traceID2), + modified: func(span ptrace.Span) { + span.SetTraceID(traceID2) + }, + }, + { + name: "span_id", + path: []ottl.Field{ + { + Name: "span_id", + }, + }, + orig: pcommon.SpanID(spanID), + newVal: pcommon.SpanID(spanID2), + modified: func(span ptrace.Span) { + span.SetSpanID(spanID2) + }, + }, + { + name: "trace_id string", + path: []ottl.Field{ + { + Name: "trace_id", + }, + { + Name: "string", + }, + }, + orig: hex.EncodeToString(traceID[:]), + newVal: hex.EncodeToString(traceID2[:]), + modified: func(span ptrace.Span) { + span.SetTraceID(traceID2) + }, + }, + { + name: "span_id string", + path: []ottl.Field{ + { + Name: "span_id", + }, + { + Name: "string", + }, + }, + orig: hex.EncodeToString(spanID[:]), + newVal: hex.EncodeToString(spanID2[:]), + modified: func(span ptrace.Span) { + span.SetSpanID(spanID2) + }, + }, + { + name: "trace_state", + path: []ottl.Field{ + { + Name: "trace_state", + }, + }, + orig: "key1=val1,key2=val2", + newVal: "key=newVal", + modified: func(span ptrace.Span) { + span.TraceState().FromRaw("key=newVal") + }, + }, + { + name: "trace_state key", + path: []ottl.Field{ + { + Name: "trace_state", + MapKey: ottltest.Strp("key1"), + }, + }, + orig: "val1", + newVal: "newVal", + modified: func(span ptrace.Span) { + span.TraceState().FromRaw("key1=newVal,key2=val2") + }, + }, + { + name: "parent_span_id", + path: []ottl.Field{ + { + Name: "parent_span_id", + }, + }, + orig: pcommon.SpanID(spanID2), + newVal: pcommon.SpanID(spanID), + modified: func(span ptrace.Span) { + span.SetParentSpanID(spanID) + }, + }, + { + name: "name", + path: []ottl.Field{ + { + Name: "name", + }, + }, + orig: "bear", + newVal: "cat", + modified: func(span ptrace.Span) { + span.SetName("cat") + }, + }, + { + name: "kind", + path: []ottl.Field{ + { + Name: "kind", + }, + }, + orig: int64(2), + newVal: int64(3), + modified: func(span ptrace.Span) { + span.SetKind(ptrace.SpanKindClient) + }, + }, + { + name: "start_time_unix_nano", + path: []ottl.Field{ + { + Name: "start_time_unix_nano", + }, + }, + orig: int64(100_000_000), + newVal: int64(200_000_000), + modified: func(span ptrace.Span) { + span.SetStartTimestamp(pcommon.NewTimestampFromTime(time.UnixMilli(200))) + }, + }, + { + name: "end_time_unix_nano", + path: []ottl.Field{ + { + Name: "end_time_unix_nano", + }, + }, + orig: int64(500_000_000), + newVal: int64(200_000_000), + modified: func(span ptrace.Span) { + span.SetEndTimestamp(pcommon.NewTimestampFromTime(time.UnixMilli(200))) + }, + }, + { + name: "attributes", + path: []ottl.Field{ + { + Name: "attributes", + }, + }, + orig: refSpan.Attributes(), + newVal: newAttrs, + modified: func(span ptrace.Span) { + newAttrs.CopyTo(span.Attributes()) + }, + }, + { + name: "attributes string", + path: []ottl.Field{ + { + Name: "attributes", + MapKey: ottltest.Strp("str"), + }, + }, + orig: "val", + newVal: "newVal", + modified: func(span ptrace.Span) { + span.Attributes().PutStr("str", "newVal") + }, + }, + { + name: "attributes bool", + path: []ottl.Field{ + { + Name: "attributes", + MapKey: ottltest.Strp("bool"), + }, + }, + orig: true, + newVal: false, + modified: func(span ptrace.Span) { + span.Attributes().PutBool("bool", false) + }, + }, + { + name: "attributes int", + path: []ottl.Field{ + { + Name: "attributes", + MapKey: ottltest.Strp("int"), + }, + }, + orig: int64(10), + newVal: int64(20), + modified: func(span ptrace.Span) { + span.Attributes().PutInt("int", 20) + }, + }, + { + name: "attributes float", + path: []ottl.Field{ + { + Name: "attributes", + MapKey: ottltest.Strp("double"), + }, + }, + orig: float64(1.2), + newVal: float64(2.4), + modified: func(span ptrace.Span) { + span.Attributes().PutDouble("double", 2.4) + }, + }, + { + name: "attributes bytes", + path: []ottl.Field{ + { + Name: "attributes", + MapKey: ottltest.Strp("bytes"), + }, + }, + orig: []byte{1, 3, 2}, + newVal: []byte{2, 3, 4}, + modified: func(span ptrace.Span) { + span.Attributes().PutEmptyBytes("bytes").FromRaw([]byte{2, 3, 4}) + }, + }, + { + name: "attributes array string", + path: []ottl.Field{ + { + Name: "attributes", + MapKey: ottltest.Strp("arr_str"), + }, + }, + orig: func() pcommon.Slice { + val, _ := refSpan.Attributes().Get("arr_str") + return val.Slice() + }(), + newVal: []string{"new"}, + modified: func(span ptrace.Span) { + span.Attributes().PutEmptySlice("arr_str").AppendEmpty().SetStr("new") + }, + }, + { + name: "attributes array bool", + path: []ottl.Field{ + { + Name: "attributes", + MapKey: ottltest.Strp("arr_bool"), + }, + }, + orig: func() pcommon.Slice { + val, _ := refSpan.Attributes().Get("arr_bool") + return val.Slice() + }(), + newVal: []bool{false}, + modified: func(span ptrace.Span) { + span.Attributes().PutEmptySlice("arr_bool").AppendEmpty().SetBool(false) + }, + }, + { + name: "attributes array int", + path: []ottl.Field{ + { + Name: "attributes", + MapKey: ottltest.Strp("arr_int"), + }, + }, + orig: func() pcommon.Slice { + val, _ := refSpan.Attributes().Get("arr_int") + return val.Slice() + }(), + newVal: []int64{20}, + modified: func(span ptrace.Span) { + span.Attributes().PutEmptySlice("arr_int").AppendEmpty().SetInt(20) + }, + }, + { + name: "attributes array float", + path: []ottl.Field{ + { + Name: "attributes", + MapKey: ottltest.Strp("arr_float"), + }, + }, + orig: func() pcommon.Slice { + val, _ := refSpan.Attributes().Get("arr_float") + return val.Slice() + }(), + newVal: []float64{2.0}, + modified: func(span ptrace.Span) { + span.Attributes().PutEmptySlice("arr_float").AppendEmpty().SetDouble(2.0) + }, + }, + { + name: "attributes array bytes", + path: []ottl.Field{ + { + Name: "attributes", + MapKey: ottltest.Strp("arr_bytes"), + }, + }, + orig: func() pcommon.Slice { + val, _ := refSpan.Attributes().Get("arr_bytes") + return val.Slice() + }(), + newVal: [][]byte{{9, 6, 4}}, + modified: func(span ptrace.Span) { + span.Attributes().PutEmptySlice("arr_bytes").AppendEmpty().SetEmptyBytes().FromRaw([]byte{9, 6, 4}) + }, + }, + { + name: "dropped_attributes_count", + path: []ottl.Field{ + { + Name: "dropped_attributes_count", + }, + }, + orig: int64(10), + newVal: int64(20), + modified: func(span ptrace.Span) { + span.SetDroppedAttributesCount(20) + }, + }, + { + name: "events", + path: []ottl.Field{ + { + Name: "events", + }, + }, + orig: refSpan.Events(), + newVal: newEvents, + modified: func(span ptrace.Span) { + span.Events().RemoveIf(func(_ ptrace.SpanEvent) bool { + return true + }) + newEvents.CopyTo(span.Events()) + }, + }, + { + name: "dropped_events_count", + path: []ottl.Field{ + { + Name: "dropped_events_count", + }, + }, + orig: int64(20), + newVal: int64(30), + modified: func(span ptrace.Span) { + span.SetDroppedEventsCount(30) + }, + }, + { + name: "links", + path: []ottl.Field{ + { + Name: "links", + }, + }, + orig: refSpan.Links(), + newVal: newLinks, + modified: func(span ptrace.Span) { + span.Links().RemoveIf(func(_ ptrace.SpanLink) bool { + return true + }) + newLinks.CopyTo(span.Links()) + }, + }, + { + name: "dropped_links_count", + path: []ottl.Field{ + { + Name: "dropped_links_count", + }, + }, + orig: int64(30), + newVal: int64(40), + modified: func(span ptrace.Span) { + span.SetDroppedLinksCount(40) + }, + }, + { + name: "status", + path: []ottl.Field{ + { + Name: "status", + }, + }, + orig: refSpan.Status(), + newVal: newStatus, + modified: func(span ptrace.Span) { + newStatus.CopyTo(span.Status()) + }, + }, + { + name: "status code", + path: []ottl.Field{ + { + Name: "status", + }, + { + Name: "code", + }, + }, + orig: int64(ptrace.StatusCodeOk), + newVal: int64(ptrace.StatusCodeError), + modified: func(span ptrace.Span) { + span.Status().SetCode(ptrace.StatusCodeError) + }, + }, + { + name: "status message", + path: []ottl.Field{ + { + Name: "status", + }, + { + Name: "message", + }, + }, + orig: "good span", + newVal: "bad span", + modified: func(span ptrace.Span) { + span.Status().SetMessage("bad span") + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + accessor, err := SpanPathGetSetter[*spanContext](tt.path) + assert.NoError(t, err) + + span := createSpan() + + got, err := accessor.Get(newSpanContext(span)) + assert.NoError(t, err) + assert.Equal(t, tt.orig, got) + + err = accessor.Set(newSpanContext(span), tt.newVal) + assert.NoError(t, err) + + expectedSpan := createSpan() + tt.modified(expectedSpan) + + assert.Equal(t, expectedSpan, span) + }) + } +} + +func createSpan() ptrace.Span { + span := ptrace.NewSpan() + span.SetTraceID(traceID) + span.SetSpanID(spanID) + span.TraceState().FromRaw("key1=val1,key2=val2") + span.SetParentSpanID(spanID2) + span.SetName("bear") + span.SetKind(ptrace.SpanKindServer) + span.SetStartTimestamp(pcommon.NewTimestampFromTime(time.UnixMilli(100))) + span.SetEndTimestamp(pcommon.NewTimestampFromTime(time.UnixMilli(500))) + span.Attributes().PutStr("str", "val") + span.Attributes().PutBool("bool", true) + span.Attributes().PutInt("int", 10) + span.Attributes().PutDouble("double", 1.2) + span.Attributes().PutEmptyBytes("bytes").FromRaw([]byte{1, 3, 2}) + + arrStr := span.Attributes().PutEmptySlice("arr_str") + arrStr.AppendEmpty().SetStr("one") + arrStr.AppendEmpty().SetStr("two") + + arrBool := span.Attributes().PutEmptySlice("arr_bool") + arrBool.AppendEmpty().SetBool(true) + arrBool.AppendEmpty().SetBool(false) + + arrInt := span.Attributes().PutEmptySlice("arr_int") + arrInt.AppendEmpty().SetInt(2) + arrInt.AppendEmpty().SetInt(3) + + arrFloat := span.Attributes().PutEmptySlice("arr_float") + arrFloat.AppendEmpty().SetDouble(1.0) + arrFloat.AppendEmpty().SetDouble(2.0) + + arrBytes := span.Attributes().PutEmptySlice("arr_bytes") + arrBytes.AppendEmpty().SetEmptyBytes().FromRaw([]byte{1, 2, 3}) + arrBytes.AppendEmpty().SetEmptyBytes().FromRaw([]byte{2, 3, 4}) + + span.SetDroppedAttributesCount(10) + + span.Events().AppendEmpty().SetName("event") + span.SetDroppedEventsCount(20) + + span.Links().AppendEmpty().SetTraceID(traceID) + span.SetDroppedLinksCount(30) + + span.Status().SetCode(ptrace.StatusCodeOk) + span.Status().SetMessage("good span") + + return span +} + +type spanContext struct { + span ptrace.Span +} + +func (r *spanContext) GetSpan() ptrace.Span { + return r.span +} + +func newSpanContext(span ptrace.Span) *spanContext { + return &spanContext{span: span} +} diff --git a/pkg/ottl/contexts/ottlspanevent/README.md b/pkg/ottl/contexts/ottlspanevent/README.md new file mode 100644 index 000000000000..813dabdbfe99 --- /dev/null +++ b/pkg/ottl/contexts/ottlspanevent/README.md @@ -0,0 +1,27 @@ +# Span Event Context + +The Span Event Context is a Context implementation for [pdata SpanEvents](https://github.com/open-telemetry/opentelemetry-collector/blob/main/pdata/ptrace/generated_traces.go), the Collector's internal representation for OTLP Span Event data. This Context should be used when interacting with individual OTLP Span Events. + +## Paths +In general, the Span Event Context supports accessing pdata using the field names from the [traces proto](https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/trace/v1/trace.proto). All integers are returned and set via `int64`. All doubles are returned and set via `float64`. + +The following fields are the exception. + +| path | field accessed | type | +|----------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------| +| 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 | 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 | 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 [ottltraces context](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/pkg/ottl/contexts/ottltraces) 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 | string, bool, int64, float64, pcommon.Map, pcommon.Slice, []byte or nil | + +## Enums + +The Span Event Context supports the enum names from the traces proto. diff --git a/pkg/ottl/contexts/ottlspanevent/span_events.go b/pkg/ottl/contexts/ottlspanevent/span_events.go new file mode 100644 index 000000000000..cc88e2404364 --- /dev/null +++ b/pkg/ottl/contexts/ottlspanevent/span_events.go @@ -0,0 +1,177 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ottlspanevent // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlspanevent" + +import ( + "fmt" + "time" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/ptrace" + + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/internal/ottlcommon" +) + +var _ ottlcommon.ResourceContext = TransformContext{} +var _ ottlcommon.InstrumentationScopeContext = TransformContext{} +var _ ottlcommon.SpanContext = TransformContext{} + +type TransformContext struct { + spanEvent ptrace.SpanEvent + span ptrace.Span + instrumentationScope pcommon.InstrumentationScope + resource pcommon.Resource +} + +func NewTransformContext(spanEvent ptrace.SpanEvent, span ptrace.Span, instrumentationScope pcommon.InstrumentationScope, resource pcommon.Resource) TransformContext { + return TransformContext{ + spanEvent: spanEvent, + span: span, + instrumentationScope: instrumentationScope, + resource: resource, + } +} + +func (ctx TransformContext) GetSpanEvent() ptrace.SpanEvent { + return ctx.spanEvent +} + +func (ctx TransformContext) GetSpan() ptrace.Span { + return ctx.span +} + +func (ctx TransformContext) GetInstrumentationScope() pcommon.InstrumentationScope { + return ctx.instrumentationScope +} + +func (ctx TransformContext) GetResource() pcommon.Resource { + return ctx.resource +} + +func NewParser(functions map[string]interface{}, telemetrySettings component.TelemetrySettings) ottl.Parser[TransformContext] { + return ottl.NewParser[TransformContext](functions, parsePath, parseEnum, telemetrySettings) +} + +func parseEnum(val *ottl.EnumSymbol) (*ottl.Enum, error) { + if val != nil { + if enum, ok := ottlcommon.SpanSymbolTable[*val]; ok { + return &enum, nil + } + return nil, fmt.Errorf("enum symbol, %s, not found", *val) + } + return nil, fmt.Errorf("enum symbol not provided") +} + +func parsePath(val *ottl.Path) (ottl.GetSetter[TransformContext], error) { + if val != nil && len(val.Fields) > 0 { + return newPathGetSetter(val.Fields) + } + return nil, fmt.Errorf("bad path %v", val) +} + +func newPathGetSetter(path []ottl.Field) (ottl.GetSetter[TransformContext], error) { + switch path[0].Name { + case "resource": + return ottlcommon.ResourcePathGetSetter[TransformContext](path[1:]) + case "instrumentation_scope": + return ottlcommon.ScopePathGetSetter[TransformContext](path[1:]) + case "span": + return ottlcommon.SpanPathGetSetter[TransformContext](path[1:]) + case "time_unix_nano": + return accessSpanEventTimeUnixNano(), nil + case "name": + return accessSpanEventName(), nil + case "attributes": + mapKey := path[0].MapKey + if mapKey == nil { + return accessSpanEventAttributes(), nil + } + return accessSpanEventAttributesKey(mapKey), nil + case "dropped_attributes_count": + return accessSpanEventDroppedAttributeCount(), nil + } + + return nil, fmt.Errorf("invalid scope path expression %v", path) +} + +func accessSpanEventTimeUnixNano() ottl.StandardGetSetter[TransformContext] { + return ottl.StandardGetSetter[TransformContext]{ + Getter: func(ctx TransformContext) (interface{}, error) { + return ctx.GetSpanEvent().Timestamp().AsTime().UnixNano(), nil + }, + Setter: func(ctx TransformContext, val interface{}) error { + if newTimestamp, ok := val.(int64); ok { + ctx.GetSpanEvent().SetTimestamp(pcommon.NewTimestampFromTime(time.Unix(0, newTimestamp))) + } + return nil + }, + } +} + +func accessSpanEventName() ottl.StandardGetSetter[TransformContext] { + return ottl.StandardGetSetter[TransformContext]{ + Getter: func(ctx TransformContext) (interface{}, error) { + return ctx.GetSpanEvent().Name(), nil + }, + Setter: func(ctx TransformContext, val interface{}) error { + if newName, ok := val.(string); ok { + ctx.GetSpanEvent().SetName(newName) + } + return nil + }, + } +} + +func accessSpanEventAttributes() ottl.StandardGetSetter[TransformContext] { + return ottl.StandardGetSetter[TransformContext]{ + Getter: func(ctx TransformContext) (interface{}, error) { + return ctx.GetSpanEvent().Attributes(), nil + }, + Setter: func(ctx TransformContext, val interface{}) error { + if attrs, ok := val.(pcommon.Map); ok { + attrs.CopyTo(ctx.GetSpanEvent().Attributes()) + } + return nil + }, + } +} + +func accessSpanEventAttributesKey(mapKey *string) ottl.StandardGetSetter[TransformContext] { + return ottl.StandardGetSetter[TransformContext]{ + Getter: func(ctx TransformContext) (interface{}, error) { + return ottlcommon.GetMapValue(ctx.GetSpanEvent().Attributes(), *mapKey), nil + }, + Setter: func(ctx TransformContext, val interface{}) error { + ottlcommon.SetMapValue(ctx.GetSpanEvent().Attributes(), *mapKey, val) + return nil + }, + } +} + +func accessSpanEventDroppedAttributeCount() ottl.StandardGetSetter[TransformContext] { + return ottl.StandardGetSetter[TransformContext]{ + Getter: func(ctx TransformContext) (interface{}, error) { + return int64(ctx.GetSpanEvent().DroppedAttributesCount()), nil + }, + Setter: func(ctx TransformContext, val interface{}) error { + if newCount, ok := val.(int64); ok { + ctx.GetSpanEvent().SetDroppedAttributesCount(uint32(newCount)) + } + return nil + }, + } +} diff --git a/pkg/ottl/contexts/ottlspanevent/span_events_test.go b/pkg/ottl/contexts/ottlspanevent/span_events_test.go new file mode 100644 index 000000000000..4739d8a316e7 --- /dev/null +++ b/pkg/ottl/contexts/ottlspanevent/span_events_test.go @@ -0,0 +1,447 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ottlspanevent + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/ptrace" + + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottltest" +) + +var ( + traceID = [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} + traceID2 = [16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1} + spanID = [8]byte{1, 2, 3, 4, 5, 6, 7, 8} + spanID2 = [8]byte{8, 7, 6, 5, 4, 3, 2, 1} +) + +func Test_newPathGetSetter(t *testing.T) { + refSpanEvent, refSpan, refIS, refResource := createTelemetry() + + newAttrs := pcommon.NewMap() + newAttrs.PutStr("hello", "world") + + newEvents := ptrace.NewSpanEventSlice() + newEvents.AppendEmpty().SetName("new event") + + newLinks := ptrace.NewSpanLinkSlice() + newLinks.AppendEmpty().SetSpanID(spanID2) + + newStatus := ptrace.NewStatus() + newStatus.SetMessage("new status") + + tests := []struct { + name string + path []ottl.Field + orig interface{} + newVal interface{} + modified func(spanEvent ptrace.SpanEvent, span ptrace.Span, il pcommon.InstrumentationScope, resource pcommon.Resource) + }{ + { + name: "name", + path: []ottl.Field{ + { + Name: "name", + }, + }, + orig: "bear", + newVal: "cat", + modified: func(spanEvent ptrace.SpanEvent, span ptrace.Span, il pcommon.InstrumentationScope, resource pcommon.Resource) { + spanEvent.SetName("cat") + }, + }, + { + name: "time_unix_nano", + path: []ottl.Field{ + { + Name: "time_unix_nano", + }, + }, + orig: int64(100_000_000), + newVal: int64(200_000_000), + modified: func(spanEvent ptrace.SpanEvent, span ptrace.Span, il pcommon.InstrumentationScope, resource pcommon.Resource) { + spanEvent.SetTimestamp(pcommon.NewTimestampFromTime(time.UnixMilli(200))) + }, + }, + { + name: "attributes", + path: []ottl.Field{ + { + Name: "attributes", + }, + }, + orig: refSpanEvent.Attributes(), + newVal: newAttrs, + modified: func(spanEvent ptrace.SpanEvent, span ptrace.Span, il pcommon.InstrumentationScope, resource pcommon.Resource) { + newAttrs.CopyTo(spanEvent.Attributes()) + }, + }, + { + name: "attributes string", + path: []ottl.Field{ + { + Name: "attributes", + MapKey: ottltest.Strp("str"), + }, + }, + orig: "val", + newVal: "newVal", + modified: func(spanEvent ptrace.SpanEvent, span ptrace.Span, il pcommon.InstrumentationScope, resource pcommon.Resource) { + spanEvent.Attributes().PutStr("str", "newVal") + }, + }, + { + name: "attributes bool", + path: []ottl.Field{ + { + Name: "attributes", + MapKey: ottltest.Strp("bool"), + }, + }, + orig: true, + newVal: false, + modified: func(spanEvent ptrace.SpanEvent, span ptrace.Span, il pcommon.InstrumentationScope, resource pcommon.Resource) { + spanEvent.Attributes().PutBool("bool", false) + }, + }, + { + name: "attributes int", + path: []ottl.Field{ + { + Name: "attributes", + MapKey: ottltest.Strp("int"), + }, + }, + orig: int64(10), + newVal: int64(20), + modified: func(spanEvent ptrace.SpanEvent, span ptrace.Span, il pcommon.InstrumentationScope, resource pcommon.Resource) { + spanEvent.Attributes().PutInt("int", 20) + }, + }, + { + name: "attributes float", + path: []ottl.Field{ + { + Name: "attributes", + MapKey: ottltest.Strp("double"), + }, + }, + orig: float64(1.2), + newVal: float64(2.4), + modified: func(spanEvent ptrace.SpanEvent, span ptrace.Span, il pcommon.InstrumentationScope, resource pcommon.Resource) { + spanEvent.Attributes().PutDouble("double", 2.4) + }, + }, + { + name: "attributes bytes", + path: []ottl.Field{ + { + Name: "attributes", + MapKey: ottltest.Strp("bytes"), + }, + }, + orig: []byte{1, 3, 2}, + newVal: []byte{2, 3, 4}, + modified: func(spanEvent ptrace.SpanEvent, span ptrace.Span, il pcommon.InstrumentationScope, resource pcommon.Resource) { + spanEvent.Attributes().PutEmptyBytes("bytes").FromRaw([]byte{2, 3, 4}) + }, + }, + { + name: "attributes array string", + path: []ottl.Field{ + { + Name: "attributes", + MapKey: ottltest.Strp("arr_str"), + }, + }, + orig: func() pcommon.Slice { + val, _ := refSpanEvent.Attributes().Get("arr_str") + return val.Slice() + }(), + newVal: []string{"new"}, + modified: func(spanEvent ptrace.SpanEvent, span ptrace.Span, il pcommon.InstrumentationScope, resource pcommon.Resource) { + spanEvent.Attributes().PutEmptySlice("arr_str").AppendEmpty().SetStr("new") + }, + }, + { + name: "attributes array bool", + path: []ottl.Field{ + { + Name: "attributes", + MapKey: ottltest.Strp("arr_bool"), + }, + }, + orig: func() pcommon.Slice { + val, _ := refSpanEvent.Attributes().Get("arr_bool") + return val.Slice() + }(), + newVal: []bool{false}, + modified: func(spanEvent ptrace.SpanEvent, span ptrace.Span, il pcommon.InstrumentationScope, resource pcommon.Resource) { + spanEvent.Attributes().PutEmptySlice("arr_bool").AppendEmpty().SetBool(false) + }, + }, + { + name: "attributes array int", + path: []ottl.Field{ + { + Name: "attributes", + MapKey: ottltest.Strp("arr_int"), + }, + }, + orig: func() pcommon.Slice { + val, _ := refSpanEvent.Attributes().Get("arr_int") + return val.Slice() + }(), + newVal: []int64{20}, + modified: func(spanEvent ptrace.SpanEvent, span ptrace.Span, il pcommon.InstrumentationScope, resource pcommon.Resource) { + spanEvent.Attributes().PutEmptySlice("arr_int").AppendEmpty().SetInt(20) + }, + }, + { + name: "attributes array float", + path: []ottl.Field{ + { + Name: "attributes", + MapKey: ottltest.Strp("arr_float"), + }, + }, + orig: func() pcommon.Slice { + val, _ := refSpanEvent.Attributes().Get("arr_float") + return val.Slice() + }(), + newVal: []float64{2.0}, + modified: func(spanEvent ptrace.SpanEvent, span ptrace.Span, il pcommon.InstrumentationScope, resource pcommon.Resource) { + spanEvent.Attributes().PutEmptySlice("arr_float").AppendEmpty().SetDouble(2.0) + }, + }, + { + name: "attributes array bytes", + path: []ottl.Field{ + { + Name: "attributes", + MapKey: ottltest.Strp("arr_bytes"), + }, + }, + orig: func() pcommon.Slice { + val, _ := refSpanEvent.Attributes().Get("arr_bytes") + return val.Slice() + }(), + newVal: [][]byte{{9, 6, 4}}, + modified: func(spanEvent ptrace.SpanEvent, span ptrace.Span, il pcommon.InstrumentationScope, resource pcommon.Resource) { + spanEvent.Attributes().PutEmptySlice("arr_bytes").AppendEmpty().SetEmptyBytes().FromRaw([]byte{9, 6, 4}) + }, + }, + { + name: "dropped_attributes_count", + path: []ottl.Field{ + { + Name: "dropped_attributes_count", + }, + }, + orig: int64(10), + newVal: int64(20), + modified: func(spanEvent ptrace.SpanEvent, span ptrace.Span, il pcommon.InstrumentationScope, resource pcommon.Resource) { + spanEvent.SetDroppedAttributesCount(20) + }, + }, + { + name: "instrumentation_scope", + path: []ottl.Field{ + { + Name: "instrumentation_scope", + }, + }, + orig: refIS, + newVal: pcommon.NewInstrumentationScope(), + modified: func(spanEvent ptrace.SpanEvent, span ptrace.Span, il pcommon.InstrumentationScope, resource pcommon.Resource) { + pcommon.NewInstrumentationScope().CopyTo(il) + }, + }, + { + name: "resource", + path: []ottl.Field{ + { + Name: "resource", + }, + }, + orig: refResource, + newVal: pcommon.NewResource(), + modified: func(spanEvent ptrace.SpanEvent, span ptrace.Span, il pcommon.InstrumentationScope, resource pcommon.Resource) { + pcommon.NewResource().CopyTo(resource) + }, + }, + { + name: "span", + path: []ottl.Field{ + { + Name: "span", + }, + }, + orig: refSpan, + newVal: ptrace.NewSpan(), + modified: func(spanEvent ptrace.SpanEvent, span ptrace.Span, il pcommon.InstrumentationScope, resource pcommon.Resource) { + ptrace.NewSpan().CopyTo(span) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + accessor, err := newPathGetSetter(tt.path) + assert.NoError(t, err) + + spanEvent, span, il, resource := createTelemetry() + + got, err := accessor.Get(NewTransformContext(spanEvent, span, il, resource)) + assert.NoError(t, err) + assert.Equal(t, tt.orig, got) + + err = accessor.Set(NewTransformContext(spanEvent, span, il, resource), tt.newVal) + assert.NoError(t, err) + + exSpanEvent, exSpan, exIl, exRes := createTelemetry() + tt.modified(exSpanEvent, exSpan, exIl, exRes) + + assert.Equal(t, exSpan, span) + assert.Equal(t, exIl, il) + assert.Equal(t, exRes, resource) + }) + } +} + +func createTelemetry() (ptrace.SpanEvent, ptrace.Span, pcommon.InstrumentationScope, pcommon.Resource) { + spanEvent := ptrace.NewSpanEvent() + + spanEvent.SetName("bear") + spanEvent.SetTimestamp(pcommon.NewTimestampFromTime(time.UnixMilli(100))) + spanEvent.SetDroppedAttributesCount(10) + + spanEvent.Attributes().PutStr("str", "val") + spanEvent.Attributes().PutBool("bool", true) + spanEvent.Attributes().PutInt("int", 10) + spanEvent.Attributes().PutDouble("double", 1.2) + spanEvent.Attributes().PutEmptyBytes("bytes").FromRaw([]byte{1, 3, 2}) + + arrStr := spanEvent.Attributes().PutEmptySlice("arr_str") + arrStr.AppendEmpty().SetStr("one") + arrStr.AppendEmpty().SetStr("two") + + arrBool := spanEvent.Attributes().PutEmptySlice("arr_bool") + arrBool.AppendEmpty().SetBool(true) + arrBool.AppendEmpty().SetBool(false) + + arrInt := spanEvent.Attributes().PutEmptySlice("arr_int") + arrInt.AppendEmpty().SetInt(2) + arrInt.AppendEmpty().SetInt(3) + + arrFloat := spanEvent.Attributes().PutEmptySlice("arr_float") + arrFloat.AppendEmpty().SetDouble(1.0) + arrFloat.AppendEmpty().SetDouble(2.0) + + arrBytes := spanEvent.Attributes().PutEmptySlice("arr_bytes") + arrBytes.AppendEmpty().SetEmptyBytes().FromRaw([]byte{1, 2, 3}) + arrBytes.AppendEmpty().SetEmptyBytes().FromRaw([]byte{2, 3, 4}) + + span := ptrace.NewSpan() + span.SetName("test") + + il := pcommon.NewInstrumentationScope() + il.SetName("library") + il.SetVersion("version") + + resource := pcommon.NewResource() + span.Attributes().CopyTo(resource.Attributes()) + + return spanEvent, span, il, resource +} + +func Test_ParseEnum(t *testing.T) { + tests := []struct { + name string + want ottl.Enum + }{ + { + name: "SPAN_KIND_UNSPECIFIED", + want: ottl.Enum(ptrace.SpanKindUnspecified), + }, + { + name: "SPAN_KIND_INTERNAL", + want: ottl.Enum(ptrace.SpanKindInternal), + }, + { + name: "SPAN_KIND_SERVER", + want: ottl.Enum(ptrace.SpanKindServer), + }, + { + name: "SPAN_KIND_CLIENT", + want: ottl.Enum(ptrace.SpanKindClient), + }, + { + name: "SPAN_KIND_PRODUCER", + want: ottl.Enum(ptrace.SpanKindProducer), + }, + { + name: "SPAN_KIND_CONSUMER", + want: ottl.Enum(ptrace.SpanKindConsumer), + }, + { + name: "STATUS_CODE_UNSET", + want: ottl.Enum(ptrace.StatusCodeUnset), + }, + { + name: "STATUS_CODE_OK", + want: ottl.Enum(ptrace.StatusCodeOk), + }, + { + name: "STATUS_CODE_ERROR", + want: ottl.Enum(ptrace.StatusCodeError), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual, err := parseEnum((*ottl.EnumSymbol)(ottltest.Strp(tt.name))) + assert.NoError(t, err) + assert.Equal(t, *actual, tt.want) + }) + } +} + +func Test_ParseEnum_False(t *testing.T) { + tests := []struct { + name string + enumSymbol *ottl.EnumSymbol + }{ + { + name: "unknown enum symbol", + enumSymbol: (*ottl.EnumSymbol)(ottltest.Strp("not an enum")), + }, + { + name: "nil enum symbol", + enumSymbol: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual, err := parseEnum(tt.enumSymbol) + assert.Error(t, err) + assert.Nil(t, actual) + }) + } +} diff --git a/pkg/ottl/contexts/ottltraces/traces.go b/pkg/ottl/contexts/ottltraces/traces.go index ac374b338bc1..4e923d5f6639 100644 --- a/pkg/ottl/contexts/ottltraces/traces.go +++ b/pkg/ottl/contexts/ottltraces/traces.go @@ -15,15 +15,11 @@ package ottltraces // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottltraces" import ( - "encoding/hex" - "errors" "fmt" - "time" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/ptrace" - "go.opentelemetry.io/otel/trace" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/internal/ottlcommon" @@ -62,21 +58,9 @@ func NewParser(functions map[string]interface{}, telemetrySettings component.Tel return ottl.NewParser[TransformContext](functions, parsePath, parseEnum, telemetrySettings) } -var symbolTable = map[ottl.EnumSymbol]ottl.Enum{ - "SPAN_KIND_UNSPECIFIED": ottl.Enum(ptrace.SpanKindUnspecified), - "SPAN_KIND_INTERNAL": ottl.Enum(ptrace.SpanKindInternal), - "SPAN_KIND_SERVER": ottl.Enum(ptrace.SpanKindServer), - "SPAN_KIND_CLIENT": ottl.Enum(ptrace.SpanKindClient), - "SPAN_KIND_PRODUCER": ottl.Enum(ptrace.SpanKindProducer), - "SPAN_KIND_CONSUMER": ottl.Enum(ptrace.SpanKindConsumer), - "STATUS_CODE_UNSET": ottl.Enum(ptrace.StatusCodeUnset), - "STATUS_CODE_OK": ottl.Enum(ptrace.StatusCodeOk), - "STATUS_CODE_ERROR": ottl.Enum(ptrace.StatusCodeError), -} - func parseEnum(val *ottl.EnumSymbol) (*ottl.Enum, error) { if val != nil { - if enum, ok := symbolTable[*val]; ok { + if enum, ok := ottlcommon.SpanSymbolTable[*val]; ok { return &enum, nil } return nil, fmt.Errorf("enum symbol, %s, not found", *val) @@ -97,400 +81,7 @@ func newPathGetSetter(path []ottl.Field) (ottl.GetSetter[TransformContext], erro return ottlcommon.ResourcePathGetSetter[TransformContext](path[1:]) case "instrumentation_scope": return ottlcommon.ScopePathGetSetter[TransformContext](path[1:]) - case "trace_id": - if len(path) == 1 { - return accessTraceID(), nil - } - if path[1].Name == "string" { - return accessStringTraceID(), nil - } - case "span_id": - if len(path) == 1 { - return accessSpanID(), nil - } - if path[1].Name == "string" { - return accessStringSpanID(), nil - } - case "trace_state": - mapKey := path[0].MapKey - if mapKey == nil { - return accessTraceState(), nil - } - return accessTraceStateKey(mapKey), nil - case "parent_span_id": - return accessParentSpanID(), nil - case "name": - return accessName(), nil - case "kind": - return accessKind(), nil - case "start_time_unix_nano": - return accessStartTimeUnixNano(), nil - case "end_time_unix_nano": - return accessEndTimeUnixNano(), nil - case "attributes": - mapKey := path[0].MapKey - if mapKey == nil { - return accessAttributes(), nil - } - return accessAttributesKey(mapKey), nil - case "dropped_attributes_count": - return accessDroppedAttributesCount(), nil - case "events": - return accessEvents(), nil - case "dropped_events_count": - return accessDroppedEventsCount(), nil - case "links": - return accessLinks(), nil - case "dropped_links_count": - return accessDroppedLinksCount(), nil - case "status": - if len(path) == 1 { - return accessStatus(), nil - } - switch path[1].Name { - case "code": - return accessStatusCode(), nil - case "message": - return accessStatusMessage(), nil - } default: - return nil, fmt.Errorf("invalid path expression, unrecognized field %v", path[0].Name) - } - - return nil, fmt.Errorf("invalid path expression %v", path) -} - -func accessTraceID() ottl.StandardGetSetter[TransformContext] { - return ottl.StandardGetSetter[TransformContext]{ - Getter: func(ctx TransformContext) (interface{}, error) { - return ctx.GetSpan().TraceID(), nil - }, - Setter: func(ctx TransformContext, val interface{}) error { - if newTraceID, ok := val.(pcommon.TraceID); ok { - ctx.GetSpan().SetTraceID(newTraceID) - } - return nil - }, - } -} - -func accessStringTraceID() ottl.StandardGetSetter[TransformContext] { - return ottl.StandardGetSetter[TransformContext]{ - Getter: func(ctx TransformContext) (interface{}, error) { - return ctx.GetSpan().TraceID().HexString(), nil - }, - Setter: func(ctx TransformContext, val interface{}) error { - if str, ok := val.(string); ok { - if traceID, err := parseTraceID(str); err == nil { - ctx.GetSpan().SetTraceID(traceID) - } - } - return nil - }, - } -} - -func accessSpanID() ottl.StandardGetSetter[TransformContext] { - return ottl.StandardGetSetter[TransformContext]{ - Getter: func(ctx TransformContext) (interface{}, error) { - return ctx.GetSpan().SpanID(), nil - }, - Setter: func(ctx TransformContext, val interface{}) error { - if newSpanID, ok := val.(pcommon.SpanID); ok { - ctx.GetSpan().SetSpanID(newSpanID) - } - return nil - }, - } -} - -func accessStringSpanID() ottl.StandardGetSetter[TransformContext] { - return ottl.StandardGetSetter[TransformContext]{ - Getter: func(ctx TransformContext) (interface{}, error) { - return ctx.GetSpan().SpanID().HexString(), nil - }, - Setter: func(ctx TransformContext, val interface{}) error { - if str, ok := val.(string); ok { - if spanID, err := parseSpanID(str); err == nil { - ctx.GetSpan().SetSpanID(spanID) - } - } - return nil - }, - } -} - -func accessTraceState() ottl.StandardGetSetter[TransformContext] { - return ottl.StandardGetSetter[TransformContext]{ - Getter: func(ctx TransformContext) (interface{}, error) { - return ctx.GetSpan().TraceState().AsRaw(), nil - }, - Setter: func(ctx TransformContext, val interface{}) error { - if str, ok := val.(string); ok { - ctx.GetSpan().TraceState().FromRaw(str) - } - return nil - }, - } -} - -func accessTraceStateKey(mapKey *string) ottl.StandardGetSetter[TransformContext] { - return ottl.StandardGetSetter[TransformContext]{ - Getter: func(ctx TransformContext) (interface{}, error) { - if ts, err := trace.ParseTraceState(ctx.GetSpan().TraceState().AsRaw()); err == nil { - return ts.Get(*mapKey), nil - } - return nil, nil - }, - Setter: func(ctx TransformContext, val interface{}) error { - if str, ok := val.(string); ok { - if ts, err := trace.ParseTraceState(ctx.GetSpan().TraceState().AsRaw()); err == nil { - if updated, err := ts.Insert(*mapKey, str); err == nil { - ctx.GetSpan().TraceState().FromRaw(updated.String()) - } - } - } - return nil - }, - } -} - -func accessParentSpanID() ottl.StandardGetSetter[TransformContext] { - return ottl.StandardGetSetter[TransformContext]{ - Getter: func(ctx TransformContext) (interface{}, error) { - return ctx.GetSpan().ParentSpanID(), nil - }, - Setter: func(ctx TransformContext, val interface{}) error { - if newParentSpanID, ok := val.(pcommon.SpanID); ok { - ctx.GetSpan().SetParentSpanID(newParentSpanID) - } - return nil - }, - } -} - -func accessName() ottl.StandardGetSetter[TransformContext] { - return ottl.StandardGetSetter[TransformContext]{ - Getter: func(ctx TransformContext) (interface{}, error) { - return ctx.GetSpan().Name(), nil - }, - Setter: func(ctx TransformContext, val interface{}) error { - if str, ok := val.(string); ok { - ctx.GetSpan().SetName(str) - } - return nil - }, - } -} - -func accessKind() ottl.StandardGetSetter[TransformContext] { - return ottl.StandardGetSetter[TransformContext]{ - Getter: func(ctx TransformContext) (interface{}, error) { - return int64(ctx.GetSpan().Kind()), nil - }, - Setter: func(ctx TransformContext, val interface{}) error { - if i, ok := val.(int64); ok { - ctx.GetSpan().SetKind(ptrace.SpanKind(i)) - } - return nil - }, - } -} - -func accessStartTimeUnixNano() ottl.StandardGetSetter[TransformContext] { - return ottl.StandardGetSetter[TransformContext]{ - Getter: func(ctx TransformContext) (interface{}, error) { - return ctx.GetSpan().StartTimestamp().AsTime().UnixNano(), nil - }, - Setter: func(ctx TransformContext, val interface{}) error { - if i, ok := val.(int64); ok { - ctx.GetSpan().SetStartTimestamp(pcommon.NewTimestampFromTime(time.Unix(0, i))) - } - return nil - }, - } -} - -func accessEndTimeUnixNano() ottl.StandardGetSetter[TransformContext] { - return ottl.StandardGetSetter[TransformContext]{ - Getter: func(ctx TransformContext) (interface{}, error) { - return ctx.GetSpan().EndTimestamp().AsTime().UnixNano(), nil - }, - Setter: func(ctx TransformContext, val interface{}) error { - if i, ok := val.(int64); ok { - ctx.GetSpan().SetEndTimestamp(pcommon.NewTimestampFromTime(time.Unix(0, i))) - } - return nil - }, - } -} - -func accessAttributes() ottl.StandardGetSetter[TransformContext] { - return ottl.StandardGetSetter[TransformContext]{ - Getter: func(ctx TransformContext) (interface{}, error) { - return ctx.GetSpan().Attributes(), nil - }, - Setter: func(ctx TransformContext, val interface{}) error { - if attrs, ok := val.(pcommon.Map); ok { - attrs.CopyTo(ctx.GetSpan().Attributes()) - } - return nil - }, - } -} - -func accessAttributesKey(mapKey *string) ottl.StandardGetSetter[TransformContext] { - return ottl.StandardGetSetter[TransformContext]{ - Getter: func(ctx TransformContext) (interface{}, error) { - return ottlcommon.GetMapValue(ctx.GetSpan().Attributes(), *mapKey), nil - }, - Setter: func(ctx TransformContext, val interface{}) error { - ottlcommon.SetMapValue(ctx.GetSpan().Attributes(), *mapKey, val) - return nil - }, - } -} - -func accessDroppedAttributesCount() ottl.StandardGetSetter[TransformContext] { - return ottl.StandardGetSetter[TransformContext]{ - Getter: func(ctx TransformContext) (interface{}, error) { - return int64(ctx.GetSpan().DroppedAttributesCount()), nil - }, - Setter: func(ctx TransformContext, val interface{}) error { - if i, ok := val.(int64); ok { - ctx.GetSpan().SetDroppedAttributesCount(uint32(i)) - } - return nil - }, - } -} - -func accessEvents() ottl.StandardGetSetter[TransformContext] { - return ottl.StandardGetSetter[TransformContext]{ - Getter: func(ctx TransformContext) (interface{}, error) { - return ctx.GetSpan().Events(), nil - }, - Setter: func(ctx TransformContext, val interface{}) error { - if slc, ok := val.(ptrace.SpanEventSlice); ok { - ctx.GetSpan().Events().RemoveIf(func(event ptrace.SpanEvent) bool { - return true - }) - slc.CopyTo(ctx.GetSpan().Events()) - } - return nil - }, - } -} - -func accessDroppedEventsCount() ottl.StandardGetSetter[TransformContext] { - return ottl.StandardGetSetter[TransformContext]{ - Getter: func(ctx TransformContext) (interface{}, error) { - return int64(ctx.GetSpan().DroppedEventsCount()), nil - }, - Setter: func(ctx TransformContext, val interface{}) error { - if i, ok := val.(int64); ok { - ctx.GetSpan().SetDroppedEventsCount(uint32(i)) - } - return nil - }, - } -} - -func accessLinks() ottl.StandardGetSetter[TransformContext] { - return ottl.StandardGetSetter[TransformContext]{ - Getter: func(ctx TransformContext) (interface{}, error) { - return ctx.GetSpan().Links(), nil - }, - Setter: func(ctx TransformContext, val interface{}) error { - if slc, ok := val.(ptrace.SpanLinkSlice); ok { - ctx.GetSpan().Links().RemoveIf(func(event ptrace.SpanLink) bool { - return true - }) - slc.CopyTo(ctx.GetSpan().Links()) - } - return nil - }, - } -} - -func accessDroppedLinksCount() ottl.StandardGetSetter[TransformContext] { - return ottl.StandardGetSetter[TransformContext]{ - Getter: func(ctx TransformContext) (interface{}, error) { - return int64(ctx.GetSpan().DroppedLinksCount()), nil - }, - Setter: func(ctx TransformContext, val interface{}) error { - if i, ok := val.(int64); ok { - ctx.GetSpan().SetDroppedLinksCount(uint32(i)) - } - return nil - }, - } -} - -func accessStatus() ottl.StandardGetSetter[TransformContext] { - return ottl.StandardGetSetter[TransformContext]{ - Getter: func(ctx TransformContext) (interface{}, error) { - return ctx.GetSpan().Status(), nil - }, - Setter: func(ctx TransformContext, val interface{}) error { - if status, ok := val.(ptrace.Status); ok { - status.CopyTo(ctx.GetSpan().Status()) - } - return nil - }, - } -} - -func accessStatusCode() ottl.StandardGetSetter[TransformContext] { - return ottl.StandardGetSetter[TransformContext]{ - Getter: func(ctx TransformContext) (interface{}, error) { - return int64(ctx.GetSpan().Status().Code()), nil - }, - Setter: func(ctx TransformContext, val interface{}) error { - if i, ok := val.(int64); ok { - ctx.GetSpan().Status().SetCode(ptrace.StatusCode(i)) - } - return nil - }, - } -} - -func accessStatusMessage() ottl.StandardGetSetter[TransformContext] { - return ottl.StandardGetSetter[TransformContext]{ - Getter: func(ctx TransformContext) (interface{}, error) { - return ctx.GetSpan().Status().Message(), nil - }, - Setter: func(ctx TransformContext, val interface{}) error { - if str, ok := val.(string); ok { - ctx.GetSpan().Status().SetMessage(str) - } - return nil - }, - } -} - -func parseSpanID(spanIDStr string) (pcommon.SpanID, error) { - id, err := hex.DecodeString(spanIDStr) - if err != nil { - return pcommon.SpanID{}, err - } - if len(id) != 8 { - return pcommon.SpanID{}, errors.New("span ids must be 8 bytes") - } - var idArr [8]byte - copy(idArr[:8], id) - return pcommon.SpanID(idArr), nil -} - -func parseTraceID(traceIDStr string) (pcommon.TraceID, error) { - id, err := hex.DecodeString(traceIDStr) - if err != nil { - return pcommon.TraceID{}, err - } - if len(id) != 16 { - return pcommon.TraceID{}, errors.New("traces ids must be 16 bytes") + return ottlcommon.SpanPathGetSetter[TransformContext](path) } - var idArr [16]byte - copy(idArr[:16], id) - return pcommon.TraceID(idArr), nil }