From 6bf8892ec8b33860f58ba441d249e3e0868809ea Mon Sep 17 00:00:00 2001 From: tani <13175394+tttanikawa@users.noreply.github.com> Date: Tue, 8 Feb 2022 19:57:20 +0900 Subject: [PATCH 1/4] Export resource attributes from zipkin exporter --- CHANGELOG.md | 1 + exporters/zipkin/model.go | 22 ++++++++++++++++- exporters/zipkin/model_test.go | 42 ++++++++++++++++++++++++++++++++- exporters/zipkin/zipkin_test.go | 5 ++++ 4 files changed, 68 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b65fd81c2de..9c2d970b7b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Changed - Jaeger exporter takes into additional 70 bytes overhead into consideration when sending UDP packets (#2489, #2512) +- Zipkin exporter exports `Resource` attributes as the `Tags` field. ### Deprecated diff --git a/exporters/zipkin/model.go b/exporters/zipkin/model.go index ba9b0f00987..335fd161d25 100644 --- a/exporters/zipkin/model.go +++ b/exporters/zipkin/model.go @@ -177,7 +177,8 @@ var extraZipkinTags = []string{ func toZipkinTags(data tracesdk.ReadOnlySpan) map[string]string { attr := data.Attributes() - m := make(map[string]string, len(attr)+len(extraZipkinTags)) + resourceAttr := data.Resource().Attributes() + m := make(map[string]string, len(attr)+len(resourceAttr)+len(extraZipkinTags)) for _, kv := range attr { switch kv.Value.Type() { // For slice attributes, serialize as JSON list string. @@ -197,6 +198,25 @@ func toZipkinTags(data tracesdk.ReadOnlySpan) map[string]string { m[(string)(kv.Key)] = kv.Value.Emit() } } + for _, kv := range resourceAttr { + switch kv.Value.Type() { + // For slice attributes, serialize as JSON list string. + case attribute.BOOLSLICE: + json, _ := json.Marshal(kv.Value.AsBoolSlice()) + m[(string)(kv.Key)] = (string)(json) + case attribute.INT64SLICE: + json, _ := json.Marshal(kv.Value.AsInt64Slice()) + m[(string)(kv.Key)] = (string)(json) + case attribute.FLOAT64SLICE: + json, _ := json.Marshal(kv.Value.AsFloat64Slice()) + m[(string)(kv.Key)] = (string)(json) + case attribute.STRINGSLICE: + json, _ := json.Marshal(kv.Value.AsStringSlice()) + m[(string)(kv.Key)] = (string)(json) + default: + m[(string)(kv.Key)] = kv.Value.Emit() + } + } if data.Status().Code != codes.Unset { m["otel.status_code"] = data.Status().Code.String() diff --git a/exporters/zipkin/model_test.go b/exporters/zipkin/model_test.go index d4ca9c43ec0..09c6f704418 100644 --- a/exporters/zipkin/model_test.go +++ b/exporters/zipkin/model_test.go @@ -39,6 +39,9 @@ import ( func TestModelConversion(t *testing.T) { resource := resource.NewSchemaless( semconv.ServiceNameKey.String("model-test"), + semconv.ServiceVersionKey.String("0.1.0"), + attribute.Int64("resource-attr1", 42), + attribute.IntSlice("resource-attr2", []int{0, 1, 2}), ) inputBatch := tracetest.SpanStubs{ @@ -408,6 +411,10 @@ func TestModelConversion(t *testing.T) { "attr3": "[0,1,2]", "otel.status_code": "Error", "error": "404, file not found", + "service.name": "model-test", + "service.version": "0.1.0", + "resource-attr1": "42", + "resource-attr2": "[0,1,2]", }, }, // model for span data with no parent @@ -447,6 +454,10 @@ func TestModelConversion(t *testing.T) { "attr2": "bar", "otel.status_code": "Error", "error": "404, file not found", + "service.name": "model-test", + "service.version": "0.1.0", + "resource-attr1": "42", + "resource-attr2": "[0,1,2]", }, }, // model for span data of unspecified kind @@ -486,6 +497,10 @@ func TestModelConversion(t *testing.T) { "attr2": "bar", "otel.status_code": "Error", "error": "404, file not found", + "service.name": "model-test", + "service.version": "0.1.0", + "resource-attr1": "42", + "resource-attr2": "[0,1,2]", }, }, // model for span data of internal kind @@ -525,6 +540,10 @@ func TestModelConversion(t *testing.T) { "attr2": "bar", "otel.status_code": "Error", "error": "404, file not found", + "service.name": "model-test", + "service.version": "0.1.0", + "resource-attr1": "42", + "resource-attr2": "[0,1,2]", }, }, // model for span data of client kind @@ -570,6 +589,10 @@ func TestModelConversion(t *testing.T) { "peer.hostname": "test-peer-hostname", "otel.status_code": "Error", "error": "404, file not found", + "service.name": "model-test", + "service.version": "0.1.0", + "resource-attr1": "42", + "resource-attr2": "[0,1,2]", }, }, // model for span data of producer kind @@ -609,6 +632,10 @@ func TestModelConversion(t *testing.T) { "attr2": "bar", "otel.status_code": "Error", "error": "404, file not found", + "service.name": "model-test", + "service.version": "0.1.0", + "resource-attr1": "42", + "resource-attr2": "[0,1,2]", }, }, // model for span data of consumer kind @@ -648,6 +675,10 @@ func TestModelConversion(t *testing.T) { "attr2": "bar", "otel.status_code": "Error", "error": "404, file not found", + "service.name": "model-test", + "service.version": "0.1.0", + "resource-attr1": "42", + "resource-attr2": "[0,1,2]", }, }, // model for span data with no events @@ -678,6 +709,10 @@ func TestModelConversion(t *testing.T) { "attr2": "bar", "otel.status_code": "Error", "error": "404, file not found", + "service.name": "model-test", + "service.version": "0.1.0", + "resource-attr1": "42", + "resource-attr2": "[0,1,2]", }, }, // model for span data with an "error" attribute set to "false" @@ -712,7 +747,12 @@ func TestModelConversion(t *testing.T) { Value: "ev2", }, }, - Tags: nil, // should be omitted + Tags: map[string]string{ + "service.name": "model-test", + "service.version": "0.1.0", + "resource-attr1": "42", + "resource-attr2": "[0,1,2]", + }, // only resource tags should be included }, } gottenOutputBatch := SpanModels(inputBatch) diff --git a/exporters/zipkin/zipkin_test.go b/exporters/zipkin/zipkin_test.go index c6984e572e1..c3abe353a37 100644 --- a/exporters/zipkin/zipkin_test.go +++ b/exporters/zipkin/zipkin_test.go @@ -200,6 +200,7 @@ func logStoreLogger(s *logStore) *log.Logger { func TestExportSpans(t *testing.T) { resource := resource.NewSchemaless( semconv.ServiceNameKey.String("exporter-test"), + semconv.ServiceVersionKey.String("0.1.0"), ) spans := tracetest.SpanStubs{ @@ -271,6 +272,8 @@ func TestExportSpans(t *testing.T) { Tags: map[string]string{ "otel.status_code": "Error", "error": "404, file not found", + "service.name": "exporter-test", + "service.version": "0.1.0", }, }, // model of child @@ -299,6 +302,8 @@ func TestExportSpans(t *testing.T) { Tags: map[string]string{ "otel.status_code": "Error", "error": "403, forbidden", + "service.name": "exporter-test", + "service.version": "0.1.0", }, }, } From 9013018982e39aff66f4b40d70f4231af1c4f025 Mon Sep 17 00:00:00 2001 From: tani <13175394+tttanikawa@users.noreply.github.com> Date: Wed, 9 Feb 2022 01:54:12 +0900 Subject: [PATCH 2/4] Update CHANGELOG.md Co-authored-by: Tyler Yahn --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c2d970b7b1..3bb9f3301da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Changed - Jaeger exporter takes into additional 70 bytes overhead into consideration when sending UDP packets (#2489, #2512) -- Zipkin exporter exports `Resource` attributes as the `Tags` field. +- Zipkin exporter exports `Resource` attributes as the `Tags` field. (#2589) ### Deprecated From fa3349174b5b629d6a26091dc3fadafd72f91b2d Mon Sep 17 00:00:00 2001 From: tani <13175394+tttanikawa@users.noreply.github.com> Date: Wed, 9 Feb 2022 01:57:18 +0900 Subject: [PATCH 3/4] Refactoring --- exporters/zipkin/model.go | 63 ++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 38 deletions(-) diff --git a/exporters/zipkin/model.go b/exporters/zipkin/model.go index 335fd161d25..c1e2146346d 100644 --- a/exporters/zipkin/model.go +++ b/exporters/zipkin/model.go @@ -179,44 +179,8 @@ func toZipkinTags(data tracesdk.ReadOnlySpan) map[string]string { attr := data.Attributes() resourceAttr := data.Resource().Attributes() m := make(map[string]string, len(attr)+len(resourceAttr)+len(extraZipkinTags)) - for _, kv := range attr { - switch kv.Value.Type() { - // For slice attributes, serialize as JSON list string. - case attribute.BOOLSLICE: - json, _ := json.Marshal(kv.Value.AsBoolSlice()) - m[(string)(kv.Key)] = (string)(json) - case attribute.INT64SLICE: - json, _ := json.Marshal(kv.Value.AsInt64Slice()) - m[(string)(kv.Key)] = (string)(json) - case attribute.FLOAT64SLICE: - json, _ := json.Marshal(kv.Value.AsFloat64Slice()) - m[(string)(kv.Key)] = (string)(json) - case attribute.STRINGSLICE: - json, _ := json.Marshal(kv.Value.AsStringSlice()) - m[(string)(kv.Key)] = (string)(json) - default: - m[(string)(kv.Key)] = kv.Value.Emit() - } - } - for _, kv := range resourceAttr { - switch kv.Value.Type() { - // For slice attributes, serialize as JSON list string. - case attribute.BOOLSLICE: - json, _ := json.Marshal(kv.Value.AsBoolSlice()) - m[(string)(kv.Key)] = (string)(json) - case attribute.INT64SLICE: - json, _ := json.Marshal(kv.Value.AsInt64Slice()) - m[(string)(kv.Key)] = (string)(json) - case attribute.FLOAT64SLICE: - json, _ := json.Marshal(kv.Value.AsFloat64Slice()) - m[(string)(kv.Key)] = (string)(json) - case attribute.STRINGSLICE: - json, _ := json.Marshal(kv.Value.AsStringSlice()) - m[(string)(kv.Key)] = (string)(json) - default: - m[(string)(kv.Key)] = kv.Value.Emit() - } - } + insertAttributes(m, attr) + insertAttributes(m, resourceAttr) if data.Status().Code != codes.Unset { m["otel.status_code"] = data.Status().Code.String() @@ -242,6 +206,29 @@ func toZipkinTags(data tracesdk.ReadOnlySpan) map[string]string { return m } +// insertAttributes serializes each attribute to string, and inserts to the given map. +func insertAttributes(m map[string]string, attributes []attribute.KeyValue) { + for _, kv := range attributes { + switch kv.Value.Type() { + // For slice attributes, serialize as JSON list string. + case attribute.BOOLSLICE: + json, _ := json.Marshal(kv.Value.AsBoolSlice()) + m[(string)(kv.Key)] = (string)(json) + case attribute.INT64SLICE: + json, _ := json.Marshal(kv.Value.AsInt64Slice()) + m[(string)(kv.Key)] = (string)(json) + case attribute.FLOAT64SLICE: + json, _ := json.Marshal(kv.Value.AsFloat64Slice()) + m[(string)(kv.Key)] = (string)(json) + case attribute.STRINGSLICE: + json, _ := json.Marshal(kv.Value.AsStringSlice()) + m[(string)(kv.Key)] = (string)(json) + default: + m[(string)(kv.Key)] = kv.Value.Emit() + } + } +} + // Rank determines selection order for remote endpoint. See the specification // https://github.com/open-telemetry/opentelemetry-specification/blob/v1.0.1/specification/trace/sdk_exporters/zipkin.md#otlp---zipkin var remoteEndpointKeyRank = map[attribute.Key]int{ From b8738c95bfff41c95b29da168d5bd674db130eff Mon Sep 17 00:00:00 2001 From: tani <13175394+tttanikawa@users.noreply.github.com> Date: Wed, 9 Feb 2022 02:15:03 +0900 Subject: [PATCH 4/4] Refactoring --- exporters/zipkin/model.go | 54 +++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/exporters/zipkin/model.go b/exporters/zipkin/model.go index c1e2146346d..38a7368b7a5 100644 --- a/exporters/zipkin/model.go +++ b/exporters/zipkin/model.go @@ -168,6 +168,27 @@ func attributesToJSONMapString(attributes []attribute.KeyValue) string { return (string)(jsonBytes) } +// attributeToStringPair serializes each attribute to a string pair +func attributeToStringPair(kv attribute.KeyValue) (string, string) { + switch kv.Value.Type() { + // For slice attributes, serialize as JSON list string. + case attribute.BOOLSLICE: + json, _ := json.Marshal(kv.Value.AsBoolSlice()) + return (string)(kv.Key), (string)(json) + case attribute.INT64SLICE: + json, _ := json.Marshal(kv.Value.AsInt64Slice()) + return (string)(kv.Key), (string)(json) + case attribute.FLOAT64SLICE: + json, _ := json.Marshal(kv.Value.AsFloat64Slice()) + return (string)(kv.Key), (string)(json) + case attribute.STRINGSLICE: + json, _ := json.Marshal(kv.Value.AsStringSlice()) + return (string)(kv.Key), (string)(json) + default: + return (string)(kv.Key), kv.Value.Emit() + } +} + // extraZipkinTags are those that may be added to every outgoing span var extraZipkinTags = []string{ "otel.status_code", @@ -179,8 +200,14 @@ func toZipkinTags(data tracesdk.ReadOnlySpan) map[string]string { attr := data.Attributes() resourceAttr := data.Resource().Attributes() m := make(map[string]string, len(attr)+len(resourceAttr)+len(extraZipkinTags)) - insertAttributes(m, attr) - insertAttributes(m, resourceAttr) + for _, kv := range attr { + k, v := attributeToStringPair(kv) + m[k] = v + } + for _, kv := range resourceAttr { + k, v := attributeToStringPair(kv) + m[k] = v + } if data.Status().Code != codes.Unset { m["otel.status_code"] = data.Status().Code.String() @@ -206,29 +233,6 @@ func toZipkinTags(data tracesdk.ReadOnlySpan) map[string]string { return m } -// insertAttributes serializes each attribute to string, and inserts to the given map. -func insertAttributes(m map[string]string, attributes []attribute.KeyValue) { - for _, kv := range attributes { - switch kv.Value.Type() { - // For slice attributes, serialize as JSON list string. - case attribute.BOOLSLICE: - json, _ := json.Marshal(kv.Value.AsBoolSlice()) - m[(string)(kv.Key)] = (string)(json) - case attribute.INT64SLICE: - json, _ := json.Marshal(kv.Value.AsInt64Slice()) - m[(string)(kv.Key)] = (string)(json) - case attribute.FLOAT64SLICE: - json, _ := json.Marshal(kv.Value.AsFloat64Slice()) - m[(string)(kv.Key)] = (string)(json) - case attribute.STRINGSLICE: - json, _ := json.Marshal(kv.Value.AsStringSlice()) - m[(string)(kv.Key)] = (string)(json) - default: - m[(string)(kv.Key)] = kv.Value.Emit() - } - } -} - // Rank determines selection order for remote endpoint. See the specification // https://github.com/open-telemetry/opentelemetry-specification/blob/v1.0.1/specification/trace/sdk_exporters/zipkin.md#otlp---zipkin var remoteEndpointKeyRank = map[attribute.Key]int{