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\":{}"); } }