From 02aba0204dd7a7181d96e04a57a88b3c9274105a Mon Sep 17 00:00:00 2001 From: Jason Plumb Date: Thu, 10 Nov 2022 14:49:14 -0800 Subject: [PATCH 1/5] serialize EventData attributes to json --- .../zipkin/EventDataToAnnotation.java | 65 +++++++++++++++++++ .../zipkin/OtelToZipkinSpanTransformer.java | 10 +-- .../zipkin/EventDataToAnnotationTest.java | 54 +++++++++++++++ .../exporter/zipkin/ZipkinTestUtil.java | 4 +- 4 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotation.java create mode 100644 exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotationTest.java diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotation.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotation.java new file mode 100644 index 00000000000..29bf45f84d5 --- /dev/null +++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotation.java @@ -0,0 +1,65 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.zipkin; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.trace.data.EventData; +import java.util.List; +import java.util.function.Function; + +/** + * Converts an EventData instance to a String representation of that data, with attributes converted + * to JSON. + * + *

See the + * zipkin exporter spec for details. + */ +class EventDataToAnnotation implements Function { + + @Override + public String apply(EventData eventData) { + String name = eventData.getName(); + String value = toJson(eventData.getAttributes()); + return "\"" + name + "\":" + value; + } + + private String toJson(Attributes attributes) { + StringBuilder sb = new StringBuilder("{"); + attributes.forEach( + (key, o) -> { + if (sb.length() > 1) { + sb.append(","); + } + sb.append("\"").append(key.getKey()).append("\":"); + String value = toValue(o); + sb.append(value); + }); + sb.append("}"); + return sb.toString(); + } + + private String toValue(Object o) { + StringBuilder sb = new StringBuilder(); + if (o instanceof String) { + sb.append("\"").append(o).append("\""); + } else if (o instanceof List) { + sb.append("["); + ((List) o) + .forEach( + v -> { + if (sb.length() > 1) { + sb.append(","); + } + sb.append(toValue(v)); + }); + sb.append("]"); + } else { + sb.append(o); + } + return sb.toString(); + } +} diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/OtelToZipkinSpanTransformer.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/OtelToZipkinSpanTransformer.java index acb76c242ab..9fa60f8aac7 100644 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/OtelToZipkinSpanTransformer.java +++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/OtelToZipkinSpanTransformer.java @@ -20,6 +20,7 @@ import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; import java.net.InetAddress; import java.util.List; +import java.util.function.Function; import java.util.function.Supplier; import javax.annotation.Nullable; import zipkin2.Endpoint; @@ -41,7 +42,7 @@ final class OtelToZipkinSpanTransformer { static final String OTEL_STATUS_CODE = "otel.status_code"; static final AttributeKey STATUS_ERROR = stringKey("error"); private final Supplier ipAddressSupplier; - + private final Function eventDataToAnnotation = new EventDataToAnnotation(); /** * Creates an instance of an OtelToZipkinSpanTransformer with the given Supplier that can produce * an InetAddress, which may be null. This value from this Supplier will be used when creating the @@ -125,8 +126,9 @@ Span generateSpan(SpanData spanData) { KEY_INSTRUMENTATION_LIBRARY_VERSION, instrumentationScopeInfo.getVersion()); } - for (EventData annotation : spanData.getEvents()) { - spanBuilder.addAnnotation(toEpochMicros(annotation.getEpochNanos()), annotation.getName()); + for (EventData eventData : spanData.getEvents()) { + String annotation = eventDataToAnnotation.apply(eventData); + spanBuilder.addAnnotation(toEpochMicros(eventData.getEpochNanos()), annotation); } int droppedEvents = spanData.getTotalRecordedEvents() - spanData.getEvents().size(); if (droppedEvents > 0) { @@ -136,7 +138,7 @@ Span generateSpan(SpanData spanData) { return spanBuilder.build(); } - private static String nullToEmpty(String value) { + private static String nullToEmpty(@Nullable String value) { return value != null ? value : ""; } diff --git a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotationTest.java b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotationTest.java new file mode 100644 index 00000000000..6dd090dc638 --- /dev/null +++ b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotationTest.java @@ -0,0 +1,54 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.zipkin; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.trace.data.EventData; +import org.junit.jupiter.api.Test; + +class EventDataToAnnotationTest { + + @Test + void basicConversion() { + + Attributes attrs = + Attributes.builder() + .put("v1", "v1") + .put("v2", 12L) + .put("v3", 123.45) + .put("v4", false) + .put("v5", "foo", "bar", "baz") + .put("v6", 1, 2, 3) + .put("v7", 1.23, 3.45) + .put("v8", true, false, true) + .build(); + String expected = + "\"cat\":{\"v1\":\"v1\",\"v2\":12,\"v3\":123.45,\"v4\":false,\"v5\":[\"foo\",\"bar\",\"baz\"],\"v6\":[1,2,3],\"v7\":[1.23,3.45],\"v8\":[true,false,true]}"; + EventData eventData = EventData.create(0, "cat", attrs); + + EventDataToAnnotation converter = new EventDataToAnnotation(); + + String result = converter.apply(eventData); + + assertThat(result).isEqualTo(expected); + } + + @Test + void empty() { + Attributes attrs = Attributes.empty(); + String expected = "\"dog\":{}"; + EventData eventData = EventData.create(0, "dog", attrs); + + EventDataToAnnotation converter = new EventDataToAnnotation(); + + String result = converter.apply(eventData); + + assertThat(result).isEqualTo(expected); + } +} diff --git a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinTestUtil.java b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinTestUtil.java index 1c96e034eeb..5f8ffe8c4a9 100644 --- a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinTestUtil.java +++ b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinTestUtil.java @@ -74,7 +74,7 @@ static Span.Builder zipkinSpanBuilder(Span.Kind kind, InetAddress localIp) { .timestamp(1505855794000000L + 194009601L / 1000) .duration((1505855799000000L + 465726528L / 1000) - (1505855794000000L + 194009601L / 1000)) .localEndpoint(Endpoint.newBuilder().ip(localIp).serviceName("tweetiebird").build()) - .addAnnotation(1505855799000000L + 433901068L / 1000, "RECEIVED") - .addAnnotation(1505855799000000L + 459486280L / 1000, "SENT"); + .addAnnotation(1505855799000000L + 433901068L / 1000, "\"RECEIVED\":{}") + .addAnnotation(1505855799000000L + 459486280L / 1000, "\"SENT\":{}"); } } From 786f226ad68fceb1b7f1377492bbe5f5d9d96c34 Mon Sep 17 00:00:00 2001 From: Jason Plumb Date: Thu, 10 Nov 2022 14:57:23 -0800 Subject: [PATCH 2/5] remove import --- .../opentelemetry/exporter/zipkin/EventDataToAnnotationTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotationTest.java b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotationTest.java index 6dd090dc638..68a2568ccae 100644 --- a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotationTest.java +++ b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotationTest.java @@ -6,7 +6,6 @@ package io.opentelemetry.exporter.zipkin; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.junit.jupiter.api.Assertions.*; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.sdk.trace.data.EventData; From 032f5c5d5f074cf76aa35fd95b7fa8b841314e3f Mon Sep 17 00:00:00 2001 From: Jason Plumb Date: Thu, 10 Nov 2022 18:51:27 -0800 Subject: [PATCH 3/5] fix test --- .../exporter/zipkin/ZipkinSpanExporterEndToEndHttpTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterEndToEndHttpTest.java b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterEndToEndHttpTest.java index b0af4f2cf23..50fdabf24f4 100644 --- a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterEndToEndHttpTest.java +++ b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterEndToEndHttpTest.java @@ -230,8 +230,8 @@ private static Span buildZipkinSpan(InetAddress localAddress, String traceId) { .timestamp(START_EPOCH_NANOS / 1000) .duration((END_EPOCH_NANOS / 1000) - (START_EPOCH_NANOS / 1000)) .localEndpoint(Endpoint.newBuilder().serviceName(SERVICE_NAME).ip(localAddress).build()) - .addAnnotation(RECEIVED_TIMESTAMP_NANOS / 1000, "RECEIVED") - .addAnnotation(SENT_TIMESTAMP_NANOS / 1000, "SENT") + .addAnnotation(RECEIVED_TIMESTAMP_NANOS / 1000, "\"RECEIVED\":{}") + .addAnnotation(SENT_TIMESTAMP_NANOS / 1000, "\"SENT\":{}") .putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK") .build(); } From 76da98de49a586464b21a3379fa9e6beb26fd481 Mon Sep 17 00:00:00 2001 From: Jason Plumb Date: Fri, 11 Nov 2022 14:44:17 -0800 Subject: [PATCH 4/5] address code review comments. --- .../zipkin/EventDataToAnnotation.java | 49 ++++++------------- .../zipkin/OtelToZipkinSpanTransformer.java | 4 +- .../zipkin/EventDataToAnnotationTest.java | 8 +-- 3 files changed, 18 insertions(+), 43 deletions(-) diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotation.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotation.java index 29bf45f84d5..9a58b084d46 100644 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotation.java +++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotation.java @@ -5,10 +5,11 @@ package io.opentelemetry.exporter.zipkin; +import static java.util.stream.Collectors.joining; + import io.opentelemetry.api.common.Attributes; import io.opentelemetry.sdk.trace.data.EventData; import java.util.List; -import java.util.function.Function; /** * Converts an EventData instance to a String representation of that data, with attributes converted @@ -18,48 +19,28 @@ * href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk_exporters/zipkin.md#events">the * zipkin exporter spec for details. */ -class EventDataToAnnotation implements Function { +class EventDataToAnnotation { - @Override - public String apply(EventData eventData) { + static String apply(EventData eventData) { String name = eventData.getName(); String value = toJson(eventData.getAttributes()); return "\"" + name + "\":" + value; } - private String toJson(Attributes attributes) { - StringBuilder sb = new StringBuilder("{"); - attributes.forEach( - (key, o) -> { - if (sb.length() > 1) { - sb.append(","); - } - sb.append("\"").append(key.getKey()).append("\":"); - String value = toValue(o); - sb.append(value); - }); - sb.append("}"); - return sb.toString(); + private static String toJson(Attributes attributes) { + return attributes.asMap().entrySet().stream() + .map(entry -> "\"" + entry.getKey() + "\":" + toValue(entry.getValue())) + .collect(joining(",", "{", "}")); } - private String toValue(Object o) { - StringBuilder sb = new StringBuilder(); + private static String toValue(Object o) { if (o instanceof String) { - sb.append("\"").append(o).append("\""); - } else if (o instanceof List) { - sb.append("["); - ((List) o) - .forEach( - v -> { - if (sb.length() > 1) { - sb.append(","); - } - sb.append(toValue(v)); - }); - sb.append("]"); - } else { - sb.append(o); + return "\"" + o + "\""; + } + if (o instanceof List) { + return ((List) o) + .stream().map(EventDataToAnnotation::toValue).collect(joining(",", "[", "]")); } - return sb.toString(); + return String.valueOf(o); } } diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/OtelToZipkinSpanTransformer.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/OtelToZipkinSpanTransformer.java index 9fa60f8aac7..5fb82c7389f 100644 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/OtelToZipkinSpanTransformer.java +++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/OtelToZipkinSpanTransformer.java @@ -20,7 +20,6 @@ import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; import java.net.InetAddress; import java.util.List; -import java.util.function.Function; import java.util.function.Supplier; import javax.annotation.Nullable; import zipkin2.Endpoint; @@ -42,7 +41,6 @@ final class OtelToZipkinSpanTransformer { static final String OTEL_STATUS_CODE = "otel.status_code"; static final AttributeKey STATUS_ERROR = stringKey("error"); private final Supplier ipAddressSupplier; - private final Function eventDataToAnnotation = new EventDataToAnnotation(); /** * Creates an instance of an OtelToZipkinSpanTransformer with the given Supplier that can produce * an InetAddress, which may be null. This value from this Supplier will be used when creating the @@ -127,7 +125,7 @@ Span generateSpan(SpanData spanData) { } for (EventData eventData : spanData.getEvents()) { - String annotation = eventDataToAnnotation.apply(eventData); + String annotation = EventDataToAnnotation.apply(eventData); spanBuilder.addAnnotation(toEpochMicros(eventData.getEpochNanos()), annotation); } int droppedEvents = spanData.getTotalRecordedEvents() - spanData.getEvents().size(); diff --git a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotationTest.java b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotationTest.java index 68a2568ccae..fa2cad0f284 100644 --- a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotationTest.java +++ b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotationTest.java @@ -31,9 +31,7 @@ void basicConversion() { "\"cat\":{\"v1\":\"v1\",\"v2\":12,\"v3\":123.45,\"v4\":false,\"v5\":[\"foo\",\"bar\",\"baz\"],\"v6\":[1,2,3],\"v7\":[1.23,3.45],\"v8\":[true,false,true]}"; EventData eventData = EventData.create(0, "cat", attrs); - EventDataToAnnotation converter = new EventDataToAnnotation(); - - String result = converter.apply(eventData); + String result = EventDataToAnnotation.apply(eventData); assertThat(result).isEqualTo(expected); } @@ -44,9 +42,7 @@ void empty() { String expected = "\"dog\":{}"; EventData eventData = EventData.create(0, "dog", attrs); - EventDataToAnnotation converter = new EventDataToAnnotation(); - - String result = converter.apply(eventData); + String result = EventDataToAnnotation.apply(eventData); assertThat(result).isEqualTo(expected); } From 23425af3968d9d73789c2e32afc096a6ffd1eab1 Mon Sep 17 00:00:00 2001 From: Jason Plumb Date: Mon, 14 Nov 2022 10:41:12 -0800 Subject: [PATCH 5/5] safety first --- .../opentelemetry/exporter/zipkin/EventDataToAnnotation.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotation.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotation.java index 9a58b084d46..9374ddc3204 100644 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotation.java +++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotation.java @@ -19,7 +19,9 @@ * href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk_exporters/zipkin.md#events">the * zipkin exporter spec for details. */ -class EventDataToAnnotation { +final class EventDataToAnnotation { + + private EventDataToAnnotation() {} static String apply(EventData eventData) { String name = eventData.getName();