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..9374ddc3204
--- /dev/null
+++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotation.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+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;
+
+/**
+ * Converts an EventData instance to a String representation of that data, with attributes converted
+ * to JSON.
+ *
+ *
See the
+ * zipkin exporter spec for details.
+ */
+final class EventDataToAnnotation {
+
+ private EventDataToAnnotation() {}
+
+ static String apply(EventData eventData) {
+ String name = eventData.getName();
+ String value = toJson(eventData.getAttributes());
+ return "\"" + name + "\":" + value;
+ }
+
+ private static String toJson(Attributes attributes) {
+ return attributes.asMap().entrySet().stream()
+ .map(entry -> "\"" + entry.getKey() + "\":" + toValue(entry.getValue()))
+ .collect(joining(",", "{", "}"));
+ }
+
+ private static String toValue(Object o) {
+ if (o instanceof String) {
+ return "\"" + o + "\"";
+ }
+ if (o instanceof List) {
+ return ((List>) o)
+ .stream().map(EventDataToAnnotation::toValue).collect(joining(",", "[", "]"));
+ }
+ 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 acb76c242ab..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
@@ -41,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;
-
/**
* 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 +124,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 +136,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..fa2cad0f284
--- /dev/null
+++ b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotationTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.zipkin;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+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);
+
+ String result = EventDataToAnnotation.apply(eventData);
+
+ assertThat(result).isEqualTo(expected);
+ }
+
+ @Test
+ void empty() {
+ Attributes attrs = Attributes.empty();
+ String expected = "\"dog\":{}";
+ EventData eventData = EventData.create(0, "dog", attrs);
+
+ String result = EventDataToAnnotation.apply(eventData);
+
+ assertThat(result).isEqualTo(expected);
+ }
+}
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();
}
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\":{}");
}
}