diff --git a/exporter/logzioexporter/dbmodel.go b/exporter/logzioexporter/dbmodel.go deleted file mode 100644 index 194e7a6373a0..000000000000 --- a/exporter/logzioexporter/dbmodel.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) 2019 The Jaeger Authors. -// Copyright (c) 2018 Uber Technologies, Inc. -// SPDX-License-Identifier: Apache-2.0 - -package logzioexporter - -// ReferenceType is the reference type of one span to another -type ReferenceType string - -// TraceID is the shared trace ID of all spans in the trace. -type TraceID string - -// SpanID is the id of a span -type SpanID string - -// ValueType is the type of a value stored in KeyValue struct. -type ValueType string - -const ( - // ChildOf means a span is the child of another span - ChildOf ReferenceType = "CHILD_OF" - // FollowsFrom means a span follows from another span - FollowsFrom ReferenceType = "FOLLOWS_FROM" - - // StringType indicates a string value stored in KeyValue - StringType ValueType = "string" - // BoolType indicates a Boolean value stored in KeyValue - BoolType ValueType = "bool" - // Int64Type indicates a 64bit signed integer value stored in KeyValue - Int64Type ValueType = "int64" - // Float64Type indicates a 64bit float value stored in KeyValue - Float64Type ValueType = "float64" - // BinaryType indicates an arbitrary byte array stored in KeyValue - BinaryType ValueType = "binary" -) - -// Span is ES database representation of the domain span. -type Span struct { - TraceID TraceID `json:"traceID"` - SpanID SpanID `json:"spanID"` - ParentSpanID SpanID `json:"parentSpanID,omitempty"` // deprecated - Flags uint32 `json:"flags,omitempty"` - OperationName string `json:"operationName"` - References []Reference `json:"references"` - StartTime uint64 `json:"startTime"` // microseconds since Unix epoch - // ElasticSearch does not support a UNIX Epoch timestamp in microseconds, - // so Jaeger maps StartTime to a 'long' type. This extra StartTimeMillis field - // works around this issue, enabling timerange queries. - StartTimeMillis uint64 `json:"startTimeMillis"` - Duration uint64 `json:"duration"` // microseconds - Tags []KeyValue `json:"tags"` - // Alternative representation of tags for better kibana support - Tag map[string]any `json:"tag,omitempty"` - Logs []Log `json:"logs"` - Process Process `json:"process,omitempty"` -} - -// Reference is a reference from one span to another -type Reference struct { - RefType ReferenceType `json:"refType"` - TraceID TraceID `json:"traceID"` - SpanID SpanID `json:"spanID"` -} - -// Process is the process emitting a set of spans -type Process struct { - ServiceName string `json:"serviceName"` - Tags []KeyValue `json:"tags"` - // Alternative representation of tags for better kibana support - Tag map[string]any `json:"tag,omitempty"` -} - -// Log is a log emitted in a span -type Log struct { - Timestamp uint64 `json:"timestamp"` - Fields []KeyValue `json:"fields"` -} - -// KeyValue is a key-value pair with typed value. -type KeyValue struct { - Key string `json:"key"` - Type ValueType `json:"type,omitempty"` - Value any `json:"value"` -} - -// Service is the JSON struct for service:operation documents in ElasticSearch -type Service struct { - ServiceName string `json:"serviceName"` - OperationName string `json:"operationName"` -} diff --git a/exporter/logzioexporter/exporter_test.go b/exporter/logzioexporter/exporter_test.go index 2943c50a9413..a9628951fa62 100644 --- a/exporter/logzioexporter/exporter_test.go +++ b/exporter/logzioexporter/exporter_test.go @@ -255,7 +255,7 @@ func TestPushTraceData(tester *testing.T) { res.Attributes().PutStr(conventions.AttributeHostName, testHost) err := testTracesExporter(tester, td, &cfg) require.NoError(tester, err) - var newSpan logzioSpan + var newSpan LogzioSpan decoded, _ := gUnzipData(recordedRequests) requests := strings.Split(string(decoded), "\n") assert.NoError(tester, json.Unmarshal([]byte(requests[0]), &newSpan)) diff --git a/exporter/logzioexporter/from_domain.go b/exporter/logzioexporter/from_domain.go index 0b9c7329e0cb..a62752c2e7a6 100644 --- a/exporter/logzioexporter/from_domain.go +++ b/exporter/logzioexporter/from_domain.go @@ -28,13 +28,13 @@ type FromDomain struct { // FromDomainEmbedProcess converts model.Span into json.Span format. // This format includes a ParentSpanID and an embedded Process. -func (fd FromDomain) FromDomainEmbedProcess(span *model.Span) *Span { +func (fd FromDomain) FromDomainEmbedProcess(span *model.Span) *LogzioSpan { return fd.convertSpanEmbedProcess(span) } -func (fd FromDomain) convertSpanInternal(span *model.Span) Span { +func (fd FromDomain) convertSpanInternal(span *model.Span) LogzioSpan { tags, tagsMap := fd.convertKeyValuesString(span.Tags) - return Span{ + return LogzioSpan{ TraceID: TraceID(span.TraceID.String()), SpanID: SpanID(span.SpanID.String()), Flags: uint32(span.Flags), @@ -48,7 +48,7 @@ func (fd FromDomain) convertSpanInternal(span *model.Span) Span { } } -func (fd FromDomain) convertSpanEmbedProcess(span *model.Span) *Span { +func (fd FromDomain) convertSpanEmbedProcess(span *model.Span) *LogzioSpan { s := fd.convertSpanInternal(span) s.Process = fd.convertProcess(span.Process) s.References = fd.convertReferences(span) diff --git a/exporter/logzioexporter/from_domain_test.go b/exporter/logzioexporter/from_domain_test.go new file mode 100644 index 000000000000..24aae34c0700 --- /dev/null +++ b/exporter/logzioexporter/from_domain_test.go @@ -0,0 +1,139 @@ +// Copyright (c) 2019 The Jaeger Authors. +// Copyright (c) 2018 Uber Technologies, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package logzioexporter + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "fmt" + "os" + "testing" + + "github.com/gogo/protobuf/jsonpb" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/jaegertracing/jaeger/model" +) + +func TestFromDomainEmbedProcess(t *testing.T) { + domainStr, jsonStr := loadFixtures(t) + + var span model.Span + require.NoError(t, jsonpb.Unmarshal(bytes.NewReader(domainStr), &span)) + converter := NewFromDomain(false, nil, ":") + embeddedSpan := converter.FromDomainEmbedProcess(&span) + + var expectedSpan LogzioSpan + require.NoError(t, json.Unmarshal(jsonStr, &expectedSpan)) + + testJSONEncoding(t, jsonStr, embeddedSpan.transformToDbModelSpan()) +} + +// Loads and returns domain model and JSON model fixtures with given number i. +func loadFixtures(t *testing.T) ([]byte, []byte) { + in := fmt.Sprintf("./testdata/span.json") + inStr, err := os.ReadFile(in) + require.NoError(t, err) + out := fmt.Sprintf("./testdata/es.json") + outStr, err := os.ReadFile(out) + require.NoError(t, err) + return inStr, outStr +} + +func testJSONEncoding(t *testing.T, expectedStr []byte, object any) { + buf := &bytes.Buffer{} + enc := json.NewEncoder(buf) + enc.SetIndent("", " ") + + outFile := fmt.Sprintf("./testdata/es.json") + require.NoError(t, enc.Encode(object)) + + if !assert.Equal(t, string(expectedStr), buf.String()) { + err := os.WriteFile(outFile+"-actual.json", buf.Bytes(), 0o644) + require.NoError(t, err) + } +} + +func TestEmptyTags(t *testing.T) { + tags := make([]model.KeyValue, 0) + span := model.Span{Tags: tags, Process: &model.Process{Tags: tags}} + converter := NewFromDomain(false, nil, ":") + dbSpan := converter.FromDomainEmbedProcess(&span) + assert.Empty(t, dbSpan.Tags) + assert.Empty(t, dbSpan.Tag) +} + +func TestTagMap(t *testing.T) { + tags := []model.KeyValue{ + model.String("foo", "foo"), + model.Bool("a", true), + model.Int64("b.b", 1), + } + span := model.Span{Tags: tags, Process: &model.Process{Tags: tags}} + converter := NewFromDomain(false, []string{"a", "b.b", "b*"}, ":") + dbSpan := converter.FromDomainEmbedProcess(&span) + + assert.Len(t, dbSpan.Tags, 1) + assert.Equal(t, "foo", dbSpan.Tags[0].Key) + assert.Len(t, dbSpan.Process.Tags, 1) + assert.Equal(t, "foo", dbSpan.Process.Tags[0].Key) + + tagsMap := map[string]any{} + tagsMap["a"] = true + tagsMap["b:b"] = int64(1) + assert.Equal(t, tagsMap, dbSpan.Tag) + assert.Equal(t, tagsMap, dbSpan.Process.Tag) +} + +func TestConvertKeyValueValue(t *testing.T) { + longString := `Bender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues + Bender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues + Bender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues + Bender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues + Bender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues ` + key := "key" + tests := []struct { + kv model.KeyValue + expected KeyValue + }{ + { + kv: model.Bool(key, true), + expected: KeyValue{Key: key, Value: "true", Type: "bool"}, + }, + { + kv: model.Bool(key, false), + expected: KeyValue{Key: key, Value: "false", Type: "bool"}, + }, + { + kv: model.Int64(key, int64(1499)), + expected: KeyValue{Key: key, Value: "1499", Type: "int64"}, + }, + { + kv: model.Float64(key, float64(15.66)), + expected: KeyValue{Key: key, Value: "15.66", Type: "float64"}, + }, + { + kv: model.String(key, longString), + expected: KeyValue{Key: key, Value: longString, Type: "string"}, + }, + { + kv: model.Binary(key, []byte(longString)), + expected: KeyValue{Key: key, Value: hex.EncodeToString([]byte(longString)), Type: "binary"}, + }, + { + kv: model.KeyValue{VType: 1500, Key: key}, + expected: KeyValue{Key: key, Value: "unknown type 1500", Type: "1500"}, + }, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("%s:%s", test.expected.Type, test.expected.Key), func(t *testing.T) { + actual := convertKeyValue(test.kv) + assert.Equal(t, test.expected, actual) + }) + } +} diff --git a/exporter/logzioexporter/go.mod b/exporter/logzioexporter/go.mod index e7982a384766..317db1ac94cf 100644 --- a/exporter/logzioexporter/go.mod +++ b/exporter/logzioexporter/go.mod @@ -3,8 +3,10 @@ module github.com/open-telemetry/opentelemetry-collector-contrib/exporter/logzio go 1.22.0 require ( + github.com/gogo/protobuf v1.3.2 github.com/hashicorp/go-hclog v1.6.3 github.com/jaegertracing/jaeger v1.62.0 + github.com/kr/pretty v0.3.1 github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/jaeger v0.116.0 github.com/stretchr/testify v1.10.0 go.opentelemetry.io/collector/component v0.116.1-0.20241220212031-7c2639723f67 @@ -36,7 +38,6 @@ require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect - github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/go-version v1.7.0 // indirect @@ -45,6 +46,7 @@ require ( github.com/knadh/koanf/maps v0.1.1 // indirect github.com/knadh/koanf/providers/confmap v0.1.0 // indirect github.com/knadh/koanf/v2 v2.1.2 // indirect + github.com/kr/text v0.2.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect @@ -54,6 +56,7 @@ require ( github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.116.0 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/rs/cors v1.11.1 // indirect go.opentelemetry.io/collector/client v1.22.1-0.20241220212031-7c2639723f67 // indirect go.opentelemetry.io/collector/config/configauth v0.116.1-0.20241220212031-7c2639723f67 // indirect diff --git a/exporter/logzioexporter/go.sum b/exporter/logzioexporter/go.sum index 11467091acb2..f1a7cc8a032a 100644 --- a/exporter/logzioexporter/go.sum +++ b/exporter/logzioexporter/go.sum @@ -2,6 +2,7 @@ github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE= github.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07XIAgDw= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -73,9 +74,11 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= diff --git a/exporter/logzioexporter/logziospan.go b/exporter/logzioexporter/logziospan.go index 227f505e10b3..dc08e0cc797d 100644 --- a/exporter/logzioexporter/logziospan.go +++ b/exporter/logzioexporter/logziospan.go @@ -15,11 +15,75 @@ const ( tagDotReplacementCharacter = "@" ) -// logzioSpan is same as esSpan with a few different json field names and an addition on type field. -type logzioSpan struct { +// ReferenceType is the reference type of one span to another +type ReferenceType string + +// TraceID is the shared trace ID of all spans in the trace. +type TraceID string + +// SpanID is the id of a span +type SpanID string + +// ValueType is the type of a value stored in KeyValue struct. +type ValueType string + +const ( + // ChildOf means a span is the child of another span + ChildOf ReferenceType = "CHILD_OF" + // FollowsFrom means a span follows from another span + FollowsFrom ReferenceType = "FOLLOWS_FROM" + + // StringType indicates a string value stored in KeyValue + StringType ValueType = "string" + // BoolType indicates a Boolean value stored in KeyValue + BoolType ValueType = "bool" + // Int64Type indicates a 64bit signed integer value stored in KeyValue + Int64Type ValueType = "int64" + // Float64Type indicates a 64bit float value stored in KeyValue + Float64Type ValueType = "float64" + // BinaryType indicates an arbitrary byte array stored in KeyValue + BinaryType ValueType = "binary" +) + +// Reference is a reference from one span to another +type Reference struct { + RefType ReferenceType `json:"refType"` + TraceID TraceID `json:"traceID"` + SpanID SpanID `json:"spanID"` +} + +// Process is the process emitting a set of spans +type Process struct { + ServiceName string `json:"serviceName"` + Tags []KeyValue `json:"tags"` + // Alternative representation of tags for better kibana support + Tag map[string]any `json:"tag,omitempty"` +} + +// Log is a log emitted in a span +type Log struct { + Timestamp uint64 `json:"timestamp"` + Fields []KeyValue `json:"fields"` +} + +// KeyValue is a key-value pair with typed value. +type KeyValue struct { + Key string `json:"key"` + Type ValueType `json:"type,omitempty"` + Value any `json:"value"` +} + +// Service is the JSON struct for service:operation documents in ElasticSearch +type Service struct { + ServiceName string `json:"serviceName"` + OperationName string `json:"operationName"` +} + +// LogzioSpan is same as ESSpan with a few different json field names and an addition on type field. +type LogzioSpan struct { TraceID TraceID `json:"traceID"` - OperationName string `json:"operationName,omitempty"` SpanID SpanID `json:"spanID"` + OperationName string `json:"operationName,omitempty"` References []Reference `json:"references"` Flags uint32 `json:"flags,omitempty"` StartTime uint64 `json:"startTime"` @@ -33,6 +97,27 @@ type logzioSpan struct { Type string `json:"type"` } +// only for testing EsSpan is ES database representation of the domain span. +type EsSpan struct { + TraceID TraceID `json:"traceID"` + SpanID SpanID `json:"spanID"` + ParentSpanID SpanID `json:"parentSpanID,omitempty"` // deprecated + Flags uint32 `json:"flags,omitempty"` + OperationName string `json:"operationName"` + References []Reference `json:"references"` + StartTime uint64 `json:"startTime"` // microseconds since Unix epoch + // ElasticSearch does not support a UNIX Epoch timestamp in microseconds, + // so Jaeger maps StartTime to a 'long' type. This extra StartTimeMillis field + // works around this issue, enabling timerange queries. + StartTimeMillis uint64 `json:"startTimeMillis"` + Duration uint64 `json:"duration"` // microseconds + Tags []KeyValue `json:"tags"` + // Alternative representation of tags for better kibana support + Tag map[string]any `json:"tag,omitempty"` + Logs []Log `json:"logs"` + Process Process `json:"process,omitempty"` +} + func getTagsValues(tags []model.KeyValue) []string { values := make([]string, len(tags)) for i := range tags { @@ -46,7 +131,7 @@ func getTagsValues(tags []model.KeyValue) []string { func transformToLogzioSpanBytes(span *model.Span) ([]byte, error) { spanConverter := NewFromDomain(true, getTagsValues(span.Tags), tagDotReplacementCharacter) jsonSpan := spanConverter.FromDomainEmbedProcess(span) - newSpan := logzioSpan{ + newSpan := LogzioSpan{ TraceID: jsonSpan.TraceID, OperationName: jsonSpan.OperationName, SpanID: jsonSpan.SpanID, @@ -65,9 +150,9 @@ func transformToLogzioSpanBytes(span *model.Span) ([]byte, error) { return json.Marshal(newSpan) } -// transformToDbModelSpan coverts logz.io span to ElasticSearch span -func (span *logzioSpan) transformToDbModelSpan() *Span { - return &Span{ +// only for testing transformToDbModelSpan coverts logz.io span to ElasticSearch span +func (span *LogzioSpan) transformToDbModelSpan() *EsSpan { + return &EsSpan{ OperationName: span.OperationName, Process: span.Process, Tags: span.Tags, diff --git a/exporter/logzioexporter/logziospan_test.go b/exporter/logzioexporter/logziospan_test.go index faea671f4b40..cd5620742459 100644 --- a/exporter/logzioexporter/logziospan_test.go +++ b/exporter/logzioexporter/logziospan_test.go @@ -42,7 +42,7 @@ func TestTransformToDbModelSpan(tester *testing.T) { } newSpan, err := transformToLogzioSpanBytes(&span) require.NoError(tester, err) - var testLogzioSpan logzioSpan + var testLogzioSpan LogzioSpan err = json.Unmarshal(newSpan, &testLogzioSpan) require.NoError(tester, err) dbModelSpan := testLogzioSpan.transformToDbModelSpan() diff --git a/exporter/logzioexporter/testdata/es.json b/exporter/logzioexporter/testdata/es.json new file mode 100644 index 000000000000..8b30c120d9cd --- /dev/null +++ b/exporter/logzioexporter/testdata/es.json @@ -0,0 +1,90 @@ +{ + "traceID": "0000000000000001", + "spanID": "0000000000000002", + "flags": 1, + "operationName": "test-general-conversion", + "references": [ + { + "refType": "CHILD_OF", + "traceID": "0000000000000001", + "spanID": "0000000000000003" + }, + { + "refType": "FOLLOWS_FROM", + "traceID": "0000000000000001", + "spanID": "0000000000000004" + }, + { + "refType": "CHILD_OF", + "traceID": "00000000000000ff", + "spanID": "00000000000000ff" + } + ], + "startTime": 1485467191639875, + "startTimeMillis": 1485467191639, + "duration": 5, + "tags": [ + { + "key": "peer.service", + "type": "string", + "value": "service-y" + }, + { + "key": "peer.ipv4", + "type": "int64", + "value": "23456" + }, + { + "key": "error", + "type": "bool", + "value": "true" + }, + { + "key": "temperature", + "type": "float64", + "value": "72.5" + }, + { + "key": "blob", + "type": "binary", + "value": "00003039" + } + ], + "logs": [ + { + "timestamp": 1485467191639875, + "fields": [ + { + "key": "event", + "type": "int64", + "value": "123415" + } + ] + }, + { + "timestamp": 1485467191639875, + "fields": [ + { + "key": "x", + "type": "string", + "value": "y" + } + ] + } + ], + "process": { + "serviceName": "service-x", + "tags": [ + { + "key": "peer.ipv4", + "type": "int64", + "value": "23456" + }, + { + "key": "error", + "type": "bool", + "value": "true" + } + ] + } +} diff --git a/exporter/logzioexporter/to_domain.go b/exporter/logzioexporter/to_domain.go deleted file mode 100644 index 92aa72682a4c..000000000000 --- a/exporter/logzioexporter/to_domain.go +++ /dev/null @@ -1,255 +0,0 @@ -// Copyright (c) 2019 The Jaeger Authors. -// Copyright (c) 2018 Uber Technologies, Inc. -// SPDX-License-Identifier: Apache-2.0 - -package logzioexporter - -import ( - "encoding/hex" - "encoding/json" - "fmt" - "strconv" - "strings" - - "github.com/jaegertracing/jaeger/model" -) - -// NewToDomain creates ToDomain -func NewToDomain(tagDotReplacement string) ToDomain { - return ToDomain{tagDotReplacement: tagDotReplacement} -} - -// ToDomain is used to convert Span to model.Span -type ToDomain struct { - tagDotReplacement string -} - -// ReplaceDot replaces dot with dotReplacement -func (td ToDomain) ReplaceDot(k string) string { - return strings.ReplaceAll(k, ".", td.tagDotReplacement) -} - -// ReplaceDotReplacement replaces dotReplacement with dot -func (td ToDomain) ReplaceDotReplacement(k string) string { - return strings.ReplaceAll(k, td.tagDotReplacement, ".") -} - -// SpanToDomain converts db span into model Span -func (td ToDomain) SpanToDomain(dbSpan *Span) (*model.Span, error) { - tags, err := td.convertKeyValues(dbSpan.Tags) - if err != nil { - return nil, err - } - logs, err := td.convertLogs(dbSpan.Logs) - if err != nil { - return nil, err - } - refs, err := td.convertRefs(dbSpan.References) - if err != nil { - return nil, err - } - process, err := td.convertProcess(dbSpan.Process) - if err != nil { - return nil, err - } - traceID, err := model.TraceIDFromString(string(dbSpan.TraceID)) - if err != nil { - return nil, err - } - - spanIDInt, err := model.SpanIDFromString(string(dbSpan.SpanID)) - if err != nil { - return nil, err - } - - if dbSpan.ParentSpanID != "" { - parentSpanID, err := model.SpanIDFromString(string(dbSpan.ParentSpanID)) - if err != nil { - return nil, err - } - refs = model.MaybeAddParentSpanID(traceID, parentSpanID, refs) - } - - fieldTags, err := td.convertTagFields(dbSpan.Tag) - if err != nil { - return nil, err - } - tags = append(tags, fieldTags...) - - span := &model.Span{ - TraceID: traceID, - SpanID: model.NewSpanID(uint64(spanIDInt)), - OperationName: dbSpan.OperationName, - References: refs, - Flags: model.Flags(uint32(dbSpan.Flags)), - StartTime: model.EpochMicrosecondsAsTime(dbSpan.StartTime), - Duration: model.MicrosecondsAsDuration(dbSpan.Duration), - Tags: tags, - Logs: logs, - Process: process, - } - return span, nil -} - -func (ToDomain) convertRefs(refs []Reference) ([]model.SpanRef, error) { - retMe := make([]model.SpanRef, len(refs)) - for i, r := range refs { - // There are some inconsistencies with ReferenceTypes, hence the hacky fix. - var refType model.SpanRefType - switch r.RefType { - case ChildOf: - refType = model.ChildOf - case FollowsFrom: - refType = model.FollowsFrom - default: - return nil, fmt.Errorf("not a valid SpanRefType string %s", string(r.RefType)) - } - - traceID, err := model.TraceIDFromString(string(r.TraceID)) - if err != nil { - return nil, err - } - - spanID, err := strconv.ParseUint(string(r.SpanID), 16, 64) - if err != nil { - return nil, err - } - - retMe[i] = model.SpanRef{ - RefType: refType, - TraceID: traceID, - SpanID: model.NewSpanID(spanID), - } - } - return retMe, nil -} - -func (td ToDomain) convertKeyValues(tags []KeyValue) ([]model.KeyValue, error) { - retMe := make([]model.KeyValue, len(tags)) - for i := range tags { - kv, err := td.convertKeyValue(&tags[i]) - if err != nil { - return nil, err - } - retMe[i] = kv - } - return retMe, nil -} - -func (td ToDomain) convertTagFields(tagsMap map[string]any) ([]model.KeyValue, error) { - kvs := make([]model.KeyValue, len(tagsMap)) - i := 0 - for k, v := range tagsMap { - tag, err := td.convertTagField(k, v) - if err != nil { - return nil, err - } - kvs[i] = tag - i++ - } - return kvs, nil -} - -func (td ToDomain) convertTagField(k string, v any) (model.KeyValue, error) { - dKey := td.ReplaceDotReplacement(k) - switch val := v.(type) { - case int64: - return model.Int64(dKey, val), nil - case float64: - return model.Float64(dKey, val), nil - case bool: - return model.Bool(dKey, val), nil - case string: - return model.String(dKey, val), nil - // the binary is never returned, ES returns it as string with base64 encoding - case []byte: - return model.Binary(dKey, val), nil - // in spans are decoded using json.UseNumber() to preserve the type - // however note that float(1) will be parsed as int as ES does not store decimal point - case json.Number: - n, err := val.Int64() - if err == nil { - return model.Int64(dKey, n), nil - } - f, err := val.Float64() - if err == nil { - return model.Float64(dKey, f), nil - } - return model.String("", ""), fmt.Errorf("invalid tag type in %+v: %w", v, err) - default: - return model.String("", ""), fmt.Errorf("invalid tag type in %+v", v) - } -} - -// convertKeyValue expects the Value field to be string, because it only works -// as a reverse transformation after FromDomain() for ElasticSearch model. -func (ToDomain) convertKeyValue(tag *KeyValue) (model.KeyValue, error) { - if tag.Value == nil { - return model.KeyValue{}, fmt.Errorf("invalid nil Value in %v", tag) - } - tagValue, ok := tag.Value.(string) - if !ok { - return model.KeyValue{}, fmt.Errorf("non-string Value of type %t in %v", tag.Value, tag) - } - switch tag.Type { - case StringType: - return model.String(tag.Key, tagValue), nil - case BoolType: - value, err := strconv.ParseBool(tagValue) - if err != nil { - return model.KeyValue{}, err - } - return model.Bool(tag.Key, value), nil - case Int64Type: - value, err := strconv.ParseInt(tagValue, 10, 64) - if err != nil { - return model.KeyValue{}, err - } - return model.Int64(tag.Key, value), nil - case Float64Type: - value, err := strconv.ParseFloat(tagValue, 64) - if err != nil { - return model.KeyValue{}, err - } - return model.Float64(tag.Key, value), nil - case BinaryType: - value, err := hex.DecodeString(tagValue) - if err != nil { - return model.KeyValue{}, err - } - return model.Binary(tag.Key, value), nil - } - return model.KeyValue{}, fmt.Errorf("not a valid ValueType string %s", string(tag.Type)) -} - -func (td ToDomain) convertLogs(logs []Log) ([]model.Log, error) { - retMe := make([]model.Log, len(logs)) - for i, l := range logs { - fields, err := td.convertKeyValues(l.Fields) - if err != nil { - return nil, err - } - retMe[i] = model.Log{ - Timestamp: model.EpochMicrosecondsAsTime(l.Timestamp), - Fields: fields, - } - } - return retMe, nil -} - -func (td ToDomain) convertProcess(process Process) (*model.Process, error) { - tags, err := td.convertKeyValues(process.Tags) - if err != nil { - return nil, err - } - fieldTags, err := td.convertTagFields(process.Tag) - if err != nil { - return nil, err - } - tags = append(tags, fieldTags...) - - return &model.Process{ - Tags: tags, - ServiceName: process.ServiceName, - }, nil -}