From 9f42a81a5f8b4faf5689b72f9e4093361d8e5885 Mon Sep 17 00:00:00 2001 From: tani <13175394+tttanikawa@users.noreply.github.com> Date: Thu, 10 Feb 2022 03:26:45 +0900 Subject: [PATCH] Export resource attributes from zipkin exporter (#2589) * Export resource attributes from zipkin exporter * Update CHANGELOG.md Co-authored-by: Tyler Yahn * Refactoring * Refactoring Co-authored-by: Tyler Yahn --- CHANGELOG.md | 1 + exporters/zipkin/model.go | 47 ++++++++++++++++++++------------- exporters/zipkin/model_test.go | 42 ++++++++++++++++++++++++++++- exporters/zipkin/zipkin_test.go | 5 ++++ 4 files changed, 76 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b65fd81c2..3bb9f3301 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. (#2589) ### Deprecated diff --git a/exporters/zipkin/model.go b/exporters/zipkin/model.go index ba9b0f009..38a7368b7 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", @@ -177,25 +198,15 @@ 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. - 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() - } + k, v := attributeToStringPair(kv) + m[k] = v + } + for _, kv := range resourceAttr { + k, v := attributeToStringPair(kv) + m[k] = v } if data.Status().Code != codes.Unset { diff --git a/exporters/zipkin/model_test.go b/exporters/zipkin/model_test.go index d4ca9c43e..09c6f7044 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 c6984e572..c3abe353a 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", }, }, }