diff --git a/receiver/libhoneyreceiver/internal/parser/parser.go b/receiver/libhoneyreceiver/internal/parser/parser.go index dc8330aef017..254884d9a75e 100644 --- a/receiver/libhoneyreceiver/internal/parser/parser.go +++ b/receiver/libhoneyreceiver/internal/parser/parser.go @@ -96,98 +96,14 @@ func ToPdata(dataset string, lhes []libhoneyevent.LibhoneyEvent, cfg libhoneyeve for _, ss := range foundScopes.Scope { for i := 0; i < ss.ScopeSpans.Len(); i++ { sp := ss.ScopeSpans.At(i) - spId := trc.SpanID(sp.SpanID()) + if speArr, ok := spanEvents[spId]; ok { - // call to new function in libhoneyevent. - for _, spe := range speArr { - newEvent := sp.Events().AppendEmpty() - newEvent.SetTimestamp(pcommon.Timestamp(spe.MsgPackTimestamp.UnixNano())) - newEvent.SetName(spe.Data["name"].(string)) - for lkey, lval := range spe.Data { - if slices.Contains(alreadyUsedFields, lkey) { - continue - } - if lkey == "meta.annotation_type" || lkey == "meta.signal_type" { - continue - } - switch lval := lval.(type) { - case string: - newEvent.Attributes().PutStr(lkey, lval) - case int: - newEvent.Attributes().PutInt(lkey, int64(lval)) - case int64, int16, int32: - intv := lval.(int64) - newEvent.Attributes().PutInt(lkey, intv) - case float64: - newEvent.Attributes().PutDouble(lkey, lval) - case bool: - newEvent.Attributes().PutBool(lkey, lval) - default: - logger.Warn("SpanEvent data type issue", zap.String("trace.trace_id", sp.TraceID().String()), zap.String("trace.span_id", sp.SpanID().String()), zap.String("key", lkey)) - } - } - } + addSpanEventsToSpan(sp, speArr, alreadyUsedFields, &logger) } + if splArr, ok := spanLinks[spId]; ok { - for _, spl := range splArr { - newLink := sp.Links().AppendEmpty() - - if linkTraceStr, ok := spl.Data["trace.link.trace_id"]; ok { - tidByteArray, err := hex.DecodeString(linkTraceStr.(string)) - if err != nil { - logger.Warn("span link invalid", zap.String("missing.attribute", "trace.link.trace_id"), zap.String("span link contents", spl.DebugString())) - continue - } - if len(tidByteArray) >= 32 { - tidByteArray = tidByteArray[0:32] - } - newLink.SetTraceID(pcommon.TraceID(tidByteArray)) - } else { - logger.Warn("span link missing attributes", zap.String("missing.attribute", "trace.link.trace_id"), zap.String("span link contents", spl.DebugString())) - continue - } - if linkSpanStr, ok := spl.Data["trace.link.span_id"]; ok { - sidByteArray, err := hex.DecodeString(linkSpanStr.(string)) - if err != nil { - logger.Warn("span link invalid", zap.String("missing.attribute", "trace.link.span_id"), zap.String("span link contents", spl.DebugString())) - continue - } - if len(sidByteArray) >= 16 { - sidByteArray = sidByteArray[0:16] - } - newLink.SetSpanID(pcommon.SpanID(sidByteArray)) - } else { - logger.Warn("span link missing attributes", zap.String("missing.attribute", "trace.link.span_id"), zap.String("span link contents", spl.DebugString())) - continue - } - for lkey, lval := range spl.Data { - if len(lkey) > 10 && lkey[:11] == "trace.link." { - continue - } - if slices.Contains(alreadyUsedFields, lkey) { - continue - } - if lkey == "meta.annotation_type" || lkey == "meta.signal_type" { - continue - } - switch lval := lval.(type) { - case string: - newLink.Attributes().PutStr(lkey, lval) - case int: - newLink.Attributes().PutInt(lkey, int64(lval)) - case int64, int16, int32: - intv := lval.(int64) - newLink.Attributes().PutInt(lkey, intv) - case float64: - newLink.Attributes().PutDouble(lkey, lval) - case bool: - newLink.Attributes().PutBool(lkey, lval) - default: - logger.Warn("SpanLink data type issue", zap.String("trace.trace_id", sp.TraceID().String()), zap.String("trace.span_id", sp.SpanID().String()), zap.String("key", lkey)) - } - } - } + addSpanLinksToSpan(sp, splArr, alreadyUsedFields, &logger) } } } @@ -220,3 +136,111 @@ func ToPdata(dataset string, lhes []libhoneyevent.LibhoneyEvent, cfg libhoneyeve return resultLogs, resultTraces } + +func addSpanEventsToSpan(sp ptrace.Span, events []libhoneyevent.LibhoneyEvent, alreadyUsedFields []string, logger *zap.Logger) { + for _, spe := range events { + newEvent := sp.Events().AppendEmpty() + newEvent.SetTimestamp(pcommon.Timestamp(spe.MsgPackTimestamp.UnixNano())) + newEvent.SetName(spe.Data["name"].(string)) + for lkey, lval := range spe.Data { + if slices.Contains(alreadyUsedFields, lkey) { + continue + } + if lkey == "meta.annotation_type" || lkey == "meta.signal_type" { + continue + } + switch lval := lval.(type) { + case string: + newEvent.Attributes().PutStr(lkey, lval) + case int: + newEvent.Attributes().PutInt(lkey, int64(lval)) + case int64, int16, int32: + intv := lval.(int64) + newEvent.Attributes().PutInt(lkey, intv) + case float64: + newEvent.Attributes().PutDouble(lkey, lval) + case bool: + newEvent.Attributes().PutBool(lkey, lval) + default: + logger.Warn("SpanEvent data type issue", + zap.String("trace.trace_id", sp.TraceID().String()), + zap.String("trace.span_id", sp.SpanID().String()), + zap.String("key", lkey)) + } + } + } +} + +func addSpanLinksToSpan(sp ptrace.Span, links []libhoneyevent.LibhoneyEvent, alreadyUsedFields []string, logger *zap.Logger) { + for _, spl := range links { + newLink := sp.Links().AppendEmpty() + + if linkTraceStr, ok := spl.Data["trace.link.trace_id"]; ok { + tidByteArray, err := hex.DecodeString(linkTraceStr.(string)) + if err != nil { + logger.Warn("span link invalid", + zap.String("missing.attribute", "trace.link.trace_id"), + zap.String("span link contents", spl.DebugString())) + continue + } + if len(tidByteArray) >= 32 { + tidByteArray = tidByteArray[0:32] + } + newLink.SetTraceID(pcommon.TraceID(tidByteArray)) + } else { + logger.Warn("span link missing attributes", + zap.String("missing.attribute", "trace.link.trace_id"), + zap.String("span link contents", spl.DebugString())) + continue + } + + if linkSpanStr, ok := spl.Data["trace.link.span_id"]; ok { + sidByteArray, err := hex.DecodeString(linkSpanStr.(string)) + if err != nil { + logger.Warn("span link invalid", + zap.String("missing.attribute", "trace.link.span_id"), + zap.String("span link contents", spl.DebugString())) + continue + } + if len(sidByteArray) >= 16 { + sidByteArray = sidByteArray[0:16] + } + newLink.SetSpanID(pcommon.SpanID(sidByteArray)) + } else { + logger.Warn("span link missing attributes", + zap.String("missing.attribute", "trace.link.span_id"), + zap.String("span link contents", spl.DebugString())) + continue + } + + for lkey, lval := range spl.Data { + if len(lkey) > 10 && lkey[:11] == "trace.link." { + continue + } + if slices.Contains(alreadyUsedFields, lkey) { + continue + } + if lkey == "meta.annotation_type" || lkey == "meta.signal_type" { + continue + } + switch lval := lval.(type) { + case string: + newLink.Attributes().PutStr(lkey, lval) + case int: + newLink.Attributes().PutInt(lkey, int64(lval)) + case int64, int16, int32: + intv := lval.(int64) + newLink.Attributes().PutInt(lkey, intv) + case float64: + newLink.Attributes().PutDouble(lkey, lval) + case bool: + newLink.Attributes().PutBool(lkey, lval) + default: + logger.Warn("SpanLink data type issue", + zap.String("trace.trace_id", sp.TraceID().String()), + zap.String("trace.span_id", sp.SpanID().String()), + zap.String("key", lkey)) + } + } + } +} diff --git a/receiver/libhoneyreceiver/internal/parser/parser_test.go b/receiver/libhoneyreceiver/internal/parser/parser_test.go new file mode 100644 index 000000000000..7603f43c6c32 --- /dev/null +++ b/receiver/libhoneyreceiver/internal/parser/parser_test.go @@ -0,0 +1,257 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package parser + +import ( + "encoding/hex" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/ptrace" + "go.uber.org/zap" + + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/libhoneyreceiver/internal/libhoneyevent" +) + +func TestGetDatasetFromRequest(t *testing.T) { + tests := []struct { + name string + path string + want string + wantErr bool + errContains string + }{ + { + name: "empty path", + path: "", + want: "", + wantErr: true, + errContains: "missing dataset name", + }, + { + name: "simple path", + path: "mydataset", + want: "mydataset", + }, + { + name: "encoded path", + path: "my%20dataset", + want: "my dataset", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetDatasetFromRequest(tt.path) + if tt.wantErr { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.errContains) + return + } + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestToPdata(t *testing.T) { + logger := zap.NewNop() + now := time.Now() + + // Create test trace and span IDs + traceID := make([]byte, 16) + spanID := make([]byte, 8) + hex.Decode(traceID, []byte("1234567890abcdef1234567890abcdef")) + hex.Decode(spanID, []byte("1234567890abcdef")) + + testCfg := libhoneyevent.FieldMapConfig{ + Attributes: libhoneyevent.AttributesConfig{ + Name: "name", + TraceID: "trace.trace_id", + SpanID: "trace.span_id", + ParentID: "trace.parent_id", + Error: "error", + SpanKind: "kind", + DurationFields: []string{"duration_ms"}, + }, + Resources: libhoneyevent.ResourcesConfig{ + ServiceName: "service.name", + }, + Scopes: libhoneyevent.ScopesConfig{ + LibraryName: "library.name", + LibraryVersion: "library.version", + }, + } + + tests := []struct { + name string + dataset string + events []libhoneyevent.LibhoneyEvent + cfg libhoneyevent.FieldMapConfig + wantLogs int + wantSpans int + }{ + { + name: "empty events", + dataset: "test-dataset", + events: []libhoneyevent.LibhoneyEvent{}, + cfg: testCfg, + }, + { + name: "single span", + dataset: "test-dataset", + events: []libhoneyevent.LibhoneyEvent{ + { + Data: map[string]interface{}{ + "meta.signal_type": "trace", + "trace.trace_id": hex.EncodeToString(traceID), + "trace.span_id": hex.EncodeToString(spanID), + "name": "test-span", + "duration_ms": 100.1, + "error": true, + "status_message": "error message", + "kind": "server", + }, + MsgPackTimestamp: &now, + Samplerate: 1, + }, + }, + cfg: testCfg, + wantSpans: 1, + }, + { + name: "single log", + dataset: "test-dataset", + events: []libhoneyevent.LibhoneyEvent{ + { + Data: map[string]interface{}{ + "meta.signal_type": "log", + "message": "test log message", + }, + MsgPackTimestamp: &now, + }, + }, + cfg: testCfg, + wantLogs: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + logs, traces := ToPdata(tt.dataset, tt.events, tt.cfg, *logger) + if tt.name == "single span" { + assert.Equal(t, tt.wantSpans, traces.SpanCount()) + } else if tt.name == "single log" { + assert.Equal(t, tt.wantLogs, logs.LogRecordCount()) + } + }) + } +} + +func TestAddSpanEventsToSpan(t *testing.T) { + logger := zap.NewNop() + now := time.Now() + s := ptrace.NewSpan() + s.SetName("test-span") + s.SetSpanID([8]byte{0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef}) + s.SetTraceID([16]byte{0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef}) + s.SetStartTimestamp(pcommon.NewTimestampFromTime(now)) + s.SetEndTimestamp(pcommon.NewTimestampFromTime(now.Add(100 * time.Millisecond))) + s.Status().SetCode(ptrace.StatusCodeError) + s.Status().SetMessage("error message") + s.SetKind(ptrace.SpanKindServer) + s.Attributes().PutStr("string_attr", "value") + s.Attributes().PutInt("int_attr", 42) + s.Attributes().PutBool("bool_attr", true) + + events := []libhoneyevent.LibhoneyEvent{ + { + Data: map[string]interface{}{ + "name": "event1", + "string": "value", + "int": 42, + "float": 3.14, + "bool": true, + }, + MsgPackTimestamp: &now, + }, + } + + addSpanEventsToSpan(s, events, []string{}, logger) + + assert.Equal(t, 1, s.Events().Len()) + event := s.Events().At(0) + assert.Equal(t, "event1", event.Name()) + assert.Equal(t, pcommon.Timestamp(now.UnixNano()), event.Timestamp()) + + attrs := event.Attributes() + attrs.Range(func(k string, v pcommon.Value) bool { + got, ok := event.Attributes().Get(k) + assert.True(t, ok, "missing attribute %s", k) + assert.Equal(t, v.Type(), got.Type(), "wrong type for attribute %s", k) + assert.Equal(t, v, got, "wrong value for attribute %s", k) + return true + }) +} + +func TestAddSpanLinksToSpan(t *testing.T) { + logger := zap.NewNop() + + now := time.Now() + s := ptrace.NewSpan() + s.SetName("test-span") + s.SetSpanID([8]byte{0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef}) + s.SetTraceID([16]byte{0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef}) + s.SetStartTimestamp(pcommon.NewTimestampFromTime(now)) + s.SetEndTimestamp(pcommon.NewTimestampFromTime(now.Add(100 * time.Millisecond))) + s.Status().SetCode(ptrace.StatusCodeError) + s.Status().SetMessage("error message") + s.SetKind(ptrace.SpanKindServer) + s.Attributes().PutStr("string_attr", "value") + s.Attributes().PutInt("int_attr", 42) + s.Attributes().PutBool("bool_attr", true) + + // Create test trace and span IDs for the link + linkTraceIDHex := make([]byte, 16) + linkSpanIDHex := make([]byte, 8) + hex.Decode(linkTraceIDHex, []byte("abcdef1234567890abcdef1234567890")) + hex.Decode(linkSpanIDHex, []byte("abcdef1234567890")) + linkTraceID := pcommon.TraceID(linkTraceIDHex) + linkSpanID := pcommon.SpanID(linkSpanIDHex) + + links := []libhoneyevent.LibhoneyEvent{ + { + Data: map[string]interface{}{ + "trace.link.trace_id": hex.EncodeToString(linkTraceIDHex), + "trace.link.span_id": hex.EncodeToString(linkSpanIDHex), + "trace.trace_id": "1234567890abcdef1234567890abcdef", + "trace.span_id": "1234567890abcdef", + "attribute1": "stringval", + "attribute2": 4, + }, + }, + } + + addSpanLinksToSpan(s, links, []string{}, logger) + + assert.Equal(t, 1, s.Links().Len()) + link := s.Links().At(0) + + // Verify trace and span IDs + assert.Equal(t, linkTraceID, link.TraceID()) + assert.Equal(t, linkSpanID, link.SpanID()) + + // Verify attributes + attrs := link.Attributes() + attrs.Range(func(k string, v pcommon.Value) bool { + got, ok := link.Attributes().Get(k) + assert.True(t, ok, "missing attribute %s", k) + assert.Equal(t, v.Type(), got.Type(), "wrong type for attribute %s", k) + assert.Equal(t, v, got, "wrong value for attribute %s", k) + return true + }) +}