From facbedc27f01e2eddf7b6f4a01d1ed30d8ef401d Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 12 Jun 2024 17:36:18 +0200 Subject: [PATCH 01/30] WIP --- .../otel/http/OpenTelemetryHttpCollector.java | 32 ++++ docker/Dockerfile | 2 +- encoder-otel-brave/README.md | 8 + encoder-otel-brave/pom.xml | 59 ++++++ .../otel/brave/AttributesExtractor.java | 56 ++++++ .../reporter/otel/brave/OtelEncoder.java | 43 +++++ .../reporter/otel/brave/SpanTranslator.java | 175 ++++++++++++++++++ encoder-otel-zipkin/README.md | 8 + encoder-otel-zipkin/pom.xml | 43 +++++ pom.xml | 88 +++++++-- sender-grpc/pom.xml | 83 +++++++++ sender-http/pom.xml | 56 ++++++ translation-otel/pom.xml | 40 ++++ 13 files changed, 675 insertions(+), 18 deletions(-) create mode 100644 encoder-otel-brave/README.md create mode 100644 encoder-otel-brave/pom.xml create mode 100644 encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/AttributesExtractor.java create mode 100644 encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/OtelEncoder.java create mode 100644 encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java create mode 100644 encoder-otel-zipkin/README.md create mode 100644 encoder-otel-zipkin/pom.xml create mode 100644 sender-grpc/pom.xml create mode 100644 sender-http/pom.xml create mode 100644 translation-otel/pom.xml diff --git a/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java b/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java index c596f50..d546845 100644 --- a/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java +++ b/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java @@ -92,7 +92,39 @@ static final class HttpService extends AbstractHttpService { @Override protected HttpResponse doPost(ServiceRequestContext ctx, HttpRequest req) throws Exception { + /* + // take bytes from req + byte[] serialized = pubsubMessage.getData().toByteArray(); + metrics.incrementMessages(); + metrics.incrementBytes(serialized.length); + collector.acceptSpans(serialized, new SpanCallback(ackReplyConsumer)); + */ throw new RuntimeException("Implement me!"); } } } + +/* +import com.google.cloud.pubsub.v1.AckReplyConsumer; +import zipkin2.Callback; + +final class SpanCallback implements Callback { + + private final AckReplyConsumer ackReplyConsumer; + + public SpanCallback(AckReplyConsumer ackReplyConsumer) { + this.ackReplyConsumer = ackReplyConsumer; + } + + @Override + public void onSuccess(Void value) { + ackReplyConsumer.ack(); + } + + @Override + public void onError(Throwable throwable) { + ackReplyConsumer.nack(); + } +} + + */ diff --git a/docker/Dockerfile b/docker/Dockerfile index 04a8f93..ce66dd9 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -13,7 +13,7 @@ # # zipkin version should match zipkin.version in /pom.xml -ARG zipkin_version=2.26.0 +ARG zipkin_version=3.4.0 # java_version is used during the installation process to build or download the module jar. # diff --git a/encoder-otel-brave/README.md b/encoder-otel-brave/README.md new file mode 100644 index 0000000..68f1ff1 --- /dev/null +++ b/encoder-otel-brave/README.md @@ -0,0 +1,8 @@ +# encoder-otel-brave + +This encodes brave spans into OTLP proto format. + +```java +// connect the sender to the correct encoding +spanHandler = AsyncZipkinSpanHandler.newBuilder(sender).build(new OtelEncoder(Tags.ERROR)); +``` diff --git a/encoder-otel-brave/pom.xml b/encoder-otel-brave/pom.xml new file mode 100644 index 0000000..5924383 --- /dev/null +++ b/encoder-otel-brave/pom.xml @@ -0,0 +1,59 @@ + + + + + io.zipkin.contrib.otel + zipkin-otel-parent + 0.1.0-SNAPSHOT + ../pom.xml + + 4.0.0 + + brave-encoder-otel + Brave Encoder: OpenTelemetry Trace + + + ${project.basedir}/.. + + + + + io.opentelemetry + opentelemetry-api + + + io.opentelemetry.proto + opentelemetry-proto + + + io.opentelemetry.semconv + opentelemetry-semconv + + + com.google.protobuf + protobuf-java + ${protobuf.version} + + provided + + + + + io.zipkin.reporter2 + zipkin-reporter-brave + ${zipkin-reporter.version} + + + ${brave.groupId} + brave + ${brave.version} + + provided + + + \ No newline at end of file diff --git a/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/AttributesExtractor.java b/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/AttributesExtractor.java new file mode 100644 index 0000000..5e14495 --- /dev/null +++ b/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/AttributesExtractor.java @@ -0,0 +1,56 @@ +/* + * Copyright The OpenZipkin Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package zipkin2.reporter.otel.brave; + +import brave.Tag; +import brave.handler.MutableSpan; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.proto.common.v1.AnyValue; +import io.opentelemetry.proto.common.v1.KeyValue; +import java.util.Map; + +/** + * LabelExtractor extracts the set of OTel Span labels equivalent to the annotations in a + * given Zipkin Span. + * + *

Zipkin annotations are converted to OTel Span labels by using annotation.value as the + * key and annotation.timestamp as the value. + * + *

Zipkin tags are converted to OTel Span labels by using annotation.key as the key and + * the String value of annotation.value as the value. + * + *

Zipkin annotations with equivalent OTel labels will be renamed to the Stackdriver + * Trace name. + */ +final class AttributesExtractor { + + private final Tag errorTag; + private final Map renamedLabels; + + AttributesExtractor(Tag errorTag, Map renamedLabels) { + this.errorTag = errorTag; + this.renamedLabels = renamedLabels; + } + + void addErrorTag(KeyValue.Builder target, MutableSpan braveSpan) { + String errorValue = errorTag.value(braveSpan.error(), null); + if (errorValue != null) { + target.setKey(getLabelName("error")).setValue( + AnyValue.newBuilder().setStringValue(errorValue).build()); + } + } + + void addTag(KeyValue.Builder target, String key, String value) { + target.setKey(getLabelName(key)).setValue( + AnyValue.newBuilder().setStringValue(value).build()); + } + + private String getLabelName(String zipkinName) { + String renamed = renamedLabels.get(zipkinName); + return renamed != null ? renamed : zipkinName; + } + +} diff --git a/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/OtelEncoder.java b/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/OtelEncoder.java new file mode 100644 index 0000000..1dc6afa --- /dev/null +++ b/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/OtelEncoder.java @@ -0,0 +1,43 @@ +/* + * Copyright The OpenZipkin Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package zipkin2.reporter.otel.brave; + +import brave.Tag; +import brave.handler.MutableSpan; +import com.google.protobuf.CodedOutputStream; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.proto.trace.v1.TracesData; +import java.io.IOException; +import zipkin2.reporter.BytesEncoder; +import zipkin2.reporter.Encoding; + +@SuppressWarnings("ImmutableEnumChecker") // because span is immutable +public class OtelEncoder implements BytesEncoder { + final SpanTranslator spanTranslator; + + public OtelEncoder(Tag errorTag) { + if (errorTag == null) throw new NullPointerException("errorTag == null"); + this.spanTranslator = new SpanTranslator(errorTag); + } + + @Override + public Encoding encoding() { + return Encoding.PROTO3; + } + + @Override public int sizeInBytes(MutableSpan span) { + // TODO: Optimize this by caching? + TracesData convert = translate(span); + return encoding().listSizeInBytes(convert.getSerializedSize()); + } + + @Override public byte[] encode(MutableSpan span) { + return translate(span).toByteArray(); + } + + TracesData translate(MutableSpan span) { + return spanTranslator.translate(span); + } +} diff --git a/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java b/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java new file mode 100644 index 0000000..503bb5c --- /dev/null +++ b/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java @@ -0,0 +1,175 @@ +/* + * Copyright The OpenZipkin Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package zipkin2.reporter.otel.brave; + +import brave.Span.Kind; +import brave.Tag; +import brave.handler.MutableSpan; +import brave.handler.MutableSpan.AnnotationConsumer; +import brave.handler.MutableSpan.TagConsumer; +import brave.handler.SpanHandler; +import com.google.protobuf.ByteString; +import io.opentelemetry.proto.common.v1.AnyValue; +import io.opentelemetry.proto.common.v1.KeyValue; +import io.opentelemetry.proto.trace.v1.ResourceSpans; +import io.opentelemetry.proto.trace.v1.ResourceSpans.Builder; +import io.opentelemetry.proto.trace.v1.ScopeSpans; +import io.opentelemetry.proto.trace.v1.Span; +import io.opentelemetry.proto.trace.v1.Span.SpanKind; +import io.opentelemetry.proto.trace.v1.TracesData; +import io.opentelemetry.semconv.HttpAttributes; +import io.opentelemetry.semconv.NetworkAttributes; +import io.opentelemetry.semconv.SemanticAttributes; +import io.opentelemetry.semconv.ServerAttributes; +import io.opentelemetry.semconv.ServiceAttributes; +import io.opentelemetry.semconv.UrlAttributes; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + + +/** + * SpanTranslator converts a Zipkin Span to a OpenTelemetry Span. + */ +final class SpanTranslator { + + private static final Map RENAMED_LABELS; + + static { + RENAMED_LABELS = new LinkedHashMap<>(); + RENAMED_LABELS.put("http.host", ServerAttributes.SERVER_ADDRESS.getKey()); + RENAMED_LABELS.put("http.method", HttpAttributes.HTTP_REQUEST_METHOD.getKey()); + RENAMED_LABELS.put("http.status_code", HttpAttributes.HTTP_RESPONSE_STATUS_CODE.getKey()); + RENAMED_LABELS.put("http.request.size", "http.request.body.size"); + RENAMED_LABELS.put("http.response.size", "http.response.body.size"); + RENAMED_LABELS.put("http.url", UrlAttributes.URL_FULL.getKey()); + } + + private final Consumer consumer; + + SpanTranslator(Tag errorTag) { + this.consumer = new Consumer(new AttributesExtractor(errorTag, RENAMED_LABELS)); + } + + /** + * Converts a Zipkin Span into a OpenTelemetry Span. + * + *

Ex. + * + *

{@code
+   * tracesData = SpanTranslator.translate(braveSpan);
+   * }
+ * + * @param braveSpan The Zipkin Span. + * @return A OpenTelemetry Span. + */ + TracesData translate(MutableSpan braveSpan) { + TracesData.Builder tracesDataBuilder = TracesData.newBuilder(); + Builder resourceSpansBuilder = ResourceSpans.newBuilder(); + ScopeSpans.Builder scopeSpanBuilder = ScopeSpans.newBuilder(); + Span.Builder spanBuilder = builderForSingleSpan(braveSpan, resourceSpansBuilder); + scopeSpanBuilder.addSpans(spanBuilder + .build()); + resourceSpansBuilder.addScopeSpans(scopeSpanBuilder + .build()); + tracesDataBuilder.addResourceSpans(resourceSpansBuilder.build()); + return tracesDataBuilder.build(); + } + + private Span.Builder builderForSingleSpan(MutableSpan span, Builder resourceSpansBuilder) { + Span.Builder spanBuilder = Span.newBuilder() + .setTraceId(ByteString.fromHex(span.traceId())) + .setSpanId(ByteString.fromHex(span.id())) + .setName(span.name()); + if (span.parentId() != null) { + spanBuilder.setParentSpanId(ByteString.fromHex(span.parentId())); + } + long start = span.startTimestamp(); + long finish = span.finishTimestamp(); + spanBuilder.setStartTimeUnixNano(TimeUnit.MICROSECONDS.toNanos(start)); + if (start != 0 && finish != 0L) { + spanBuilder.setEndTimeUnixNano(TimeUnit.MICROSECONDS.toNanos(finish)); + } + Kind kind = span.kind(); + if (kind != null) { + switch (kind) { + case CLIENT: + spanBuilder.setKind(SpanKind.SPAN_KIND_CLIENT); + break; + case SERVER: + spanBuilder.setKind(SpanKind.SPAN_KIND_SERVER); + break; + case PRODUCER: + spanBuilder.setKind(SpanKind.SPAN_KIND_PRODUCER); + break; + case CONSUMER: + spanBuilder.setKind(SpanKind.SPAN_KIND_CONSUMER); + break; + default: + spanBuilder.setKind(SpanKind.SPAN_KIND_INTERNAL); //TODO: Should it work like this? + } + } + String localServiceName = span.localServiceName(); + if (localServiceName != null) { + resourceSpansBuilder.getResourceBuilder().addAttributes( + KeyValue.newBuilder().setKey(ServiceAttributes.SERVICE_NAME.getKey()) + .setValue(AnyValue.newBuilder().setStringValue(localServiceName).build()).build()); + } + String localIp = span.localIp(); + if (localIp != null) { + spanBuilder.addAttributes(KeyValue.newBuilder().setKey(NetworkAttributes.NETWORK_LOCAL_ADDRESS.getKey()) + .setValue(AnyValue.newBuilder().setStringValue(localIp).build()).build()); + } + int localPort = span.localPort(); + if (localPort != 0) { + spanBuilder.addAttributes(KeyValue.newBuilder().setKey(ServerAttributes.SERVER_PORT.getKey()) + .setValue(AnyValue.newBuilder().setIntValue(localPort).build()).build()); + } + String peerName = span.remoteIp(); + if (peerName != null) { + spanBuilder.addAttributes(KeyValue.newBuilder().setKey(SemanticAttributes.NET_SOCK_PEER_NAME.getKey()) + .setValue(AnyValue.newBuilder().setStringValue(peerName).build()).build()); + } + String peerIp = span.remoteIp(); + if (peerIp != null) { + spanBuilder.addAttributes(KeyValue.newBuilder().setKey(SemanticAttributes.NET_SOCK_PEER_ADDR.getKey()) + .setValue(AnyValue.newBuilder().setStringValue(peerIp).build()).build()); + } + int peerPort = span.remotePort(); + if (peerPort != 0) { + spanBuilder.addAttributes(KeyValue.newBuilder().setKey(SemanticAttributes.NET_SOCK_PEER_PORT.getKey()) + .setValue(AnyValue.newBuilder().setIntValue(peerPort).build()).build()); + } + span.forEachTag(consumer, spanBuilder); + span.forEachAnnotation(consumer, spanBuilder); + consumer.addErrorTag(spanBuilder, span); + return spanBuilder; + } + + class Consumer implements TagConsumer, AnnotationConsumer { + + private final AttributesExtractor attributesExtractor; + + Consumer(AttributesExtractor attributesExtractor) { + this.attributesExtractor = attributesExtractor; + } + + @Override + public void accept(Span.Builder target, String key, String value) { + attributesExtractor.addTag(target.addAttributesBuilder(), key, value); + } + + void addErrorTag(Span.Builder target, MutableSpan span) { + attributesExtractor.addErrorTag(target.addAttributesBuilder(), span); + } + + @Override + public void accept(Span.Builder target, long timestamp, String value) { + target.addEventsBuilder().setTimeUnixNano(TimeUnit.MICROSECONDS.toNanos(timestamp)) + .setName(value); + } + } + +} diff --git a/encoder-otel-zipkin/README.md b/encoder-otel-zipkin/README.md new file mode 100644 index 0000000..8d58b31 --- /dev/null +++ b/encoder-otel-zipkin/README.md @@ -0,0 +1,8 @@ +# encoder-otel-zipkin + +This encodes zipkin spans into OTLP proto format. + +```java +// connect the sender to the correct encoding +reporter = AsyncReporter.newBuilder(sender).build(OtelEncoder.V2); +``` diff --git a/encoder-otel-zipkin/pom.xml b/encoder-otel-zipkin/pom.xml new file mode 100644 index 0000000..aab1e8f --- /dev/null +++ b/encoder-otel-zipkin/pom.xml @@ -0,0 +1,43 @@ + + + + + io.zipkin.contrib.otel + zipkin-otel-parent + 0.1.0-SNAPSHOT + ../pom.xml + + 4.0.0 + + zipkin-encoder-otel + Zipkin Encoder: OpenTelemetry Trace + + + ${project.basedir}/.. + + + + + ${project.groupId} + zipkin-translation-otel + ${project.version} + + + io.zipkin.reporter2 + zipkin-reporter + ${zipkin-reporter.version} + + + + ${zipkin.groupId} + zipkin-tests + ${zipkin.version} + test + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 915f434..2bfc2ac 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,11 @@ module + translation-otel + encoder-otel-brave + encoder-otel-zipkin + sender-grpc + sender-http collector-grpc collector-http @@ -60,29 +65,45 @@ io.zipkin.zipkin2 - 2.26.0 - 2.17.1 - 2.7.18 + 3.4.0 + 3.4.0 + 3.3.0 com.linecorp.armeria - 1.26.4 - - 3.24.2 - 4.2.0 - 5.10.1 - 5.8.0 + 1.28.4 + + + + + io.zipkin.brave + 6.0.3 + + 1.39.0 + + 1.3.1-alpha + 1.25.0-alpha + + 1.63.0 + + 3.25.1 + + 3.25.3 + 4.2.1 + 5.10.2 + 5.12.0 - 2.24.0 + 2.27.1 ${skipTests} 1.23 1.2.8 - 4.3 - 3.11.0 + 4.5 + 5.1.9 + 3.13.0 3.6.1 3.1.1 @@ -90,12 +111,12 @@ 3.4.0 3.1.1 - 3.6.2 + 3.6.3 3.3.0 3.0.1 - 3.5.1 - 3.3.0 - 3.2.2 + 3.5.2 + 3.3.1 + 3.2.5 1.6.13 @@ -134,6 +155,39 @@ + + + + io.opentelemetry + opentelemetry-bom + ${opentelemetry.version} + pom + import + + + io.opentelemetry.proto + opentelemetry-proto + ${opentelemetry-proto.version} + + + + io.opentelemetry.semconv + opentelemetry-semconv + ${opentelemetry-semconv.version} + + + com.asarkar.grpc + grpc-test + 1.2.2 + + + org.junit.jupiter + * + + + + + org.junit.jupiter @@ -151,7 +205,7 @@ ch.qos.logback logback-classic - 1.2.13 + 1.5.6 test diff --git a/sender-grpc/pom.xml b/sender-grpc/pom.xml new file mode 100644 index 0000000..b465d23 --- /dev/null +++ b/sender-grpc/pom.xml @@ -0,0 +1,83 @@ + + + + + io.zipkin.contrib.otel + zipkin-otel-parent + 0.1.0-SNAPSHOT + ../pom.xml + + 4.0.0 + + zipkin-sender-grpc + Zipkin Sender: OpenTelemetry Proto over GRPC + + + ${project.basedir}/.. + + + + + ${project.groupId} + zipkin-translation-otel + ${project.version} + + + io.zipkin.reporter2 + zipkin-reporter + ${zipkin-reporter.version} + + + io.grpc + grpc-core + ${grpc.version} + + + io.grpc + grpc-protobuf + ${grpc.version} + + + + ${zipkin.groupId} + zipkin-tests + ${zipkin.version} + test + + + ${project.groupId} + zipkin-encoder-otel + ${project.version} + test + + + + com.asarkar.grpc + grpc-test + test + + + io.grpc + grpc-inprocess + ${grpc.version} + test + + + io.grpc + grpc-auth + ${grpc.version} + test + + + org.awaitility + awaitility + ${awaitility.version} + test + + + \ No newline at end of file diff --git a/sender-http/pom.xml b/sender-http/pom.xml new file mode 100644 index 0000000..07f3bb4 --- /dev/null +++ b/sender-http/pom.xml @@ -0,0 +1,56 @@ + + + + + io.zipkin.contrib.otel + zipkin-otel-parent + 0.1.0-SNAPSHOT + ../pom.xml + + 4.0.0 + + zipkin-sender-http + Zipkin Sender: OpenTelemetry Proto over HTTP + + + ${project.basedir}/.. + + + + + ${project.groupId} + zipkin-translation-otel + ${project.version} + + + io.zipkin.reporter2 + zipkin-reporter + ${zipkin-reporter.version} + + + + ${zipkin.groupId} + zipkin-tests + ${zipkin.version} + test + + + ${project.groupId} + zipkin-encoder-otel + ${project.version} + test + + + + org.awaitility + awaitility + ${awaitility.version} + test + + + \ No newline at end of file diff --git a/translation-otel/pom.xml b/translation-otel/pom.xml new file mode 100644 index 0000000..e53edb4 --- /dev/null +++ b/translation-otel/pom.xml @@ -0,0 +1,40 @@ + + + + + io.zipkin.contrib.otel + zipkin-otel-parent + 0.1.0-SNAPSHOT + ../pom.xml + + 4.0.0 + + zipkin-translation-otel + Zipkin to OpenTelemetry Trace Translation + + + ${project.basedir}/.. + + + + + ${zipkin.groupId} + zipkin + ${zipkin.version} + provided + + + + com.google.protobuf + protobuf-java + ${protobuf.version} + + provided + + + \ No newline at end of file From c5bad8b05e44e6ccd8549c6281c4baefce5fed30 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 13 Jun 2024 18:11:22 +0200 Subject: [PATCH 02/30] WIP HTTP sender works fine, GRPC is failing --- .../otel/grpc/OpenTelemetryGrpcCollector.java | 42 +++- .../grpc/ITOpenTelemetryGrpcCollector.java | 7 +- .../otel/http/OpenTelemetryHttpCollector.java | 100 +++++---- .../reporter/otel/brave/SpanTranslator.java | 2 +- .../reporter/otel/zipkin/OtelEncoder.java | 56 +++++ pom.xml | 3 +- sender-grpc/pom.xml | 2 +- .../AwaitableUnaryClientCallListener.java | 100 +++++++++ .../reporter/otel/grpc/OtelGrpcSender.java | 159 ++++++++++++++ sender-http/pom.xml | 56 ----- tests/pom.xml | 40 ++++ tests/sender-tests/pom.xml | 91 ++++++++ .../zipkin2/reporter/otel/BasicUsageTest.java | 202 ++++++++++++++++++ .../zipkin2/reporter/otel/JaegerAllInOne.java | 70 ++++++ translation-otel/pom.xml | 12 ++ .../zipkin/AttributesExtractor.java | 42 ++++ .../translation/zipkin/SpanTranslator.java | 142 ++++++++++++ 17 files changed, 1024 insertions(+), 102 deletions(-) create mode 100644 encoder-otel-zipkin/src/main/java/zipkin2/reporter/otel/zipkin/OtelEncoder.java create mode 100644 sender-grpc/src/main/java/zipkin2/reporter/otel/grpc/AwaitableUnaryClientCallListener.java create mode 100644 sender-grpc/src/main/java/zipkin2/reporter/otel/grpc/OtelGrpcSender.java delete mode 100644 sender-http/pom.xml create mode 100644 tests/pom.xml create mode 100644 tests/sender-tests/pom.xml create mode 100644 tests/sender-tests/src/test/java/zipkin2/reporter/otel/BasicUsageTest.java create mode 100644 tests/sender-tests/src/test/java/zipkin2/reporter/otel/JaegerAllInOne.java create mode 100644 translation-otel/src/main/java/zipkin2/translation/zipkin/AttributesExtractor.java create mode 100644 translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java diff --git a/collector-grpc/src/main/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollector.java b/collector-grpc/src/main/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollector.java index 17bb6f0..df566ab 100644 --- a/collector-grpc/src/main/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollector.java +++ b/collector-grpc/src/main/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollector.java @@ -13,12 +13,18 @@ */ package zipkin2.collector.otel.grpc; +import com.linecorp.armeria.common.ContextAwareBlockingTaskExecutor; import com.linecorp.armeria.server.ServerBuilder; import com.linecorp.armeria.server.ServerConfigurator; import com.linecorp.armeria.server.ServiceRequestContext; import com.linecorp.armeria.server.grpc.protocol.AbstractUnsafeUnaryGrpcService; import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import java.nio.ByteBuffer; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import zipkin2.Callback; +import zipkin2.codec.SpanBytesDecoder; import zipkin2.collector.Collector; import zipkin2.collector.CollectorComponent; import zipkin2.collector.CollectorMetrics; @@ -71,6 +77,7 @@ public static final class Builder extends CollectorComponent.Builder { @Override public OpenTelemetryGrpcCollector start() { return this; } + @Override public String toString() { return "OpenTelemetryGrpcCollector{}"; } @@ -83,15 +90,42 @@ public static final class Builder extends CollectorComponent.Builder { } static final class HttpService extends AbstractUnsafeUnaryGrpcService { - final OpenTelemetryGrpcCollector collector; + final Collector collector; + final CollectorMetrics metrics; HttpService(OpenTelemetryGrpcCollector collector) { - this.collector = collector; + this.collector = collector.collector; + this.metrics = collector.metrics; } @Override - protected CompletionStage handleMessage(ServiceRequestContext ctx, ByteBuf message) { - throw new RuntimeException("Implement me!"); + protected CompletionStage handleMessage(ServiceRequestContext ctx, ByteBuf bytes) { + metrics.incrementMessages(); + metrics.incrementBytes(bytes.readableBytes()); + + if (!bytes.isReadable()) { + return CompletableFuture.completedFuture(bytes); // lenient on empty messages + } + + try { + CompletableFutureCallback result = new CompletableFutureCallback(); + collector.acceptSpans(bytes.nioBuffer(), SpanBytesDecoder.PROTO3, result, ctx.blockingTaskExecutor()); + return result; + } finally { + bytes.release(); + } + } + } + + static final class CompletableFutureCallback extends CompletableFuture + implements Callback { + + @Override public void onSuccess(Void value) { + complete(Unpooled.EMPTY_BUFFER); + } + + @Override public void onError(Throwable t) { + completeExceptionally(t); } } } diff --git a/collector-grpc/src/test/java/zipkin2/collector/otel/grpc/ITOpenTelemetryGrpcCollector.java b/collector-grpc/src/test/java/zipkin2/collector/otel/grpc/ITOpenTelemetryGrpcCollector.java index 99d7e1b..c2426f2 100644 --- a/collector-grpc/src/test/java/zipkin2/collector/otel/grpc/ITOpenTelemetryGrpcCollector.java +++ b/collector-grpc/src/test/java/zipkin2/collector/otel/grpc/ITOpenTelemetryGrpcCollector.java @@ -24,11 +24,13 @@ @TestInstance(TestInstance.Lifecycle.PER_CLASS) class ITOpenTelemetryGrpcCollector { + InMemoryStorage store; InMemoryCollectorMetrics metrics; CollectorComponent collector; - @BeforeEach public void setup() { + @BeforeEach + public void setup() { store = InMemoryStorage.newBuilder().build(); metrics = new InMemoryCollectorMetrics(); @@ -41,7 +43,8 @@ class ITOpenTelemetryGrpcCollector { metrics = metrics.forTransport("otel/grpc"); } - @AfterEach void teardown() throws IOException { + @AfterEach + void teardown() throws IOException { store.close(); collector.close(); } diff --git a/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java b/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java index d546845..9102328 100644 --- a/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java +++ b/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java @@ -13,12 +13,23 @@ */ package zipkin2.collector.otel.http; +import static zipkin2.Call.propagateIfFatal; + +import com.linecorp.armeria.common.AggregationOptions; +import com.linecorp.armeria.common.HttpData; import com.linecorp.armeria.common.HttpRequest; import com.linecorp.armeria.common.HttpResponse; +import com.linecorp.armeria.common.HttpStatus; +import com.linecorp.armeria.common.ResponseHeaders; import com.linecorp.armeria.server.AbstractHttpService; import com.linecorp.armeria.server.ServerBuilder; import com.linecorp.armeria.server.ServerConfigurator; import com.linecorp.armeria.server.ServiceRequestContext; +import io.netty.buffer.ByteBufAllocator; +import java.nio.ByteBuffer; +import java.util.concurrent.CompletableFuture; +import zipkin2.Callback; +import zipkin2.codec.SpanBytesDecoder; import zipkin2.collector.Collector; import zipkin2.collector.CollectorComponent; import zipkin2.collector.CollectorMetrics; @@ -27,6 +38,7 @@ public final class OpenTelemetryHttpCollector extends CollectorComponent implements ServerConfigurator { + public static Builder newBuilder() { return new Builder(); } @@ -36,23 +48,29 @@ public static final class Builder extends CollectorComponent.Builder { Collector.Builder delegate = Collector.newBuilder(OpenTelemetryHttpCollector.class); CollectorMetrics metrics = CollectorMetrics.NOOP_METRICS; - @Override public Builder storage(StorageComponent storageComponent) { + @Override + public Builder storage(StorageComponent storageComponent) { delegate.storage(storageComponent); return this; } - @Override public Builder metrics(CollectorMetrics metrics) { - if (metrics == null) throw new NullPointerException("metrics == null"); + @Override + public Builder metrics(CollectorMetrics metrics) { + if (metrics == null) { + throw new NullPointerException("metrics == null"); + } delegate.metrics(this.metrics = metrics.forTransport("otel/http")); return this; } - @Override public Builder sampler(CollectorSampler sampler) { + @Override + public Builder sampler(CollectorSampler sampler) { delegate.sampler(sampler); return this; } - @Override public OpenTelemetryHttpCollector build() { + @Override + public OpenTelemetryHttpCollector build() { return new OpenTelemetryHttpCollector(this); } @@ -68,63 +86,71 @@ public static final class Builder extends CollectorComponent.Builder { metrics = builder.metrics; } - @Override public OpenTelemetryHttpCollector start() { + @Override + public OpenTelemetryHttpCollector start() { return this; } - @Override public String toString() { + @Override + public String toString() { return "OpenTelemetryHttpCollector{}"; } /** * Reconfigures the service per https://opentelemetry.io/docs/specs/otlp/#otlphttp-request */ - @Override public void reconfigure(ServerBuilder sb) { + @Override + public void reconfigure(ServerBuilder sb) { sb.service("/v1/traces", new HttpService(this)); } static final class HttpService extends AbstractHttpService { + final OpenTelemetryHttpCollector collector; HttpService(OpenTelemetryHttpCollector collector) { this.collector = collector; } - @Override protected HttpResponse doPost(ServiceRequestContext ctx, HttpRequest req) + @Override + protected HttpResponse doPost(ServiceRequestContext ctx, HttpRequest req) throws Exception { - /* - // take bytes from req - byte[] serialized = pubsubMessage.getData().toByteArray(); - metrics.incrementMessages(); - metrics.incrementBytes(serialized.length); - collector.acceptSpans(serialized, new SpanCallback(ackReplyConsumer)); - */ - throw new RuntimeException("Implement me!"); + CompletableCallback result = new CompletableCallback(); + req.aggregate(AggregationOptions.usePooledObjects(ByteBufAllocator.DEFAULT, ctx.eventLoop() + )).handle((msg, t) -> { + if (t != null) { + result.onError(t); + return null; + } + try (HttpData content = msg.content()) { + if (content.isEmpty()) { + result.onSuccess(null); + return null; + } + + final ByteBuffer nioBuffer = content.byteBuf().nioBuffer(); + collector.collector.acceptSpans(nioBuffer, SpanBytesDecoder.PROTO3, result, ctx.blockingTaskExecutor()); + return null; + } + }); + return HttpResponse.of(result); } } -} - -/* -import com.google.cloud.pubsub.v1.AckReplyConsumer; -import zipkin2.Callback; - -final class SpanCallback implements Callback { - private final AckReplyConsumer ackReplyConsumer; + static final class CompletableCallback extends CompletableFuture + implements Callback { - public SpanCallback(AckReplyConsumer ackReplyConsumer) { - this.ackReplyConsumer = ackReplyConsumer; - } + static final ResponseHeaders ACCEPTED_RESPONSE = ResponseHeaders.of(HttpStatus.ACCEPTED); - @Override - public void onSuccess(Void value) { - ackReplyConsumer.ack(); - } + @Override + public void onSuccess(Void value) { + complete(HttpResponse.of(ACCEPTED_RESPONSE)); + } - @Override - public void onError(Throwable throwable) { - ackReplyConsumer.nack(); + @Override + public void onError(Throwable t) { + completeExceptionally(t); + } } -} - */ +} \ No newline at end of file diff --git a/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java b/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java index 503bb5c..0c2c479 100644 --- a/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java +++ b/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java @@ -127,7 +127,7 @@ private Span.Builder builderForSingleSpan(MutableSpan span, Builder resourceSpan spanBuilder.addAttributes(KeyValue.newBuilder().setKey(ServerAttributes.SERVER_PORT.getKey()) .setValue(AnyValue.newBuilder().setIntValue(localPort).build()).build()); } - String peerName = span.remoteIp(); + String peerName = span.remoteServiceName(); if (peerName != null) { spanBuilder.addAttributes(KeyValue.newBuilder().setKey(SemanticAttributes.NET_SOCK_PEER_NAME.getKey()) .setValue(AnyValue.newBuilder().setStringValue(peerName).build()).build()); diff --git a/encoder-otel-zipkin/src/main/java/zipkin2/reporter/otel/zipkin/OtelEncoder.java b/encoder-otel-zipkin/src/main/java/zipkin2/reporter/otel/zipkin/OtelEncoder.java new file mode 100644 index 0000000..c379403 --- /dev/null +++ b/encoder-otel-zipkin/src/main/java/zipkin2/reporter/otel/zipkin/OtelEncoder.java @@ -0,0 +1,56 @@ +/* + * Copyright The OpenZipkin Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package zipkin2.reporter.otel.zipkin; + +import com.google.protobuf.CodedOutputStream; +import io.opentelemetry.proto.trace.v1.TracesData; +import java.io.IOException; +import zipkin2.Span; +import zipkin2.reporter.BytesEncoder; +import zipkin2.reporter.Encoding; +import zipkin2.translation.zipkin.SpanTranslator; + +@SuppressWarnings("ImmutableEnumChecker") // because span is immutable +public enum OtelEncoder implements BytesEncoder { + V1 { + @Override + public Encoding encoding() { + return Encoding.PROTO3; + } + + @Override + public int sizeInBytes(Span input) { + return 32 + translate(input).getSerializedSize(); + } + + /** This encodes a TraceSpan message prefixed by a potentially padded 32 character trace ID */ + @Override + public byte[] encode(Span span) { + TracesData translated = translate(span); + byte[] result = new byte[32 + translated.getSerializedSize()]; + + // Zipkin trace ID is conditionally 16 or 32 characters, but Stackdriver needs 32 + String traceId = span.traceId(); + if (traceId.length() == 16) { + for (int i = 0; i < 16; i++) result[i] = '0'; + for (int i = 0; i < 16; i++) result[i + 16] = (byte) traceId.charAt(i); + } else { + for (int i = 0; i < 32; i++) result[i] = (byte) traceId.charAt(i); + } + + CodedOutputStream output = CodedOutputStream.newInstance(result, 32, result.length - 32); + try { + translated.writeTo(output); + } catch (IOException e) { + throw new AssertionError(e); + } + return result; + } + + TracesData translate(Span span) { + return SpanTranslator.translate(span); + } + } +} diff --git a/pom.xml b/pom.xml index 2bfc2ac..061085d 100644 --- a/pom.xml +++ b/pom.xml @@ -30,9 +30,9 @@ encoder-otel-brave encoder-otel-zipkin sender-grpc - sender-http collector-grpc collector-http + tests @@ -91,6 +91,7 @@ 4.2.1 5.10.2 5.12.0 + 1.19.8 diff --git a/sender-grpc/pom.xml b/sender-grpc/pom.xml index b465d23..347c52b 100644 --- a/sender-grpc/pom.xml +++ b/sender-grpc/pom.xml @@ -14,7 +14,7 @@ 4.0.0 - zipkin-sender-grpc + zipkin-sender-otel-grpc Zipkin Sender: OpenTelemetry Proto over GRPC diff --git a/sender-grpc/src/main/java/zipkin2/reporter/otel/grpc/AwaitableUnaryClientCallListener.java b/sender-grpc/src/main/java/zipkin2/reporter/otel/grpc/AwaitableUnaryClientCallListener.java new file mode 100644 index 0000000..552af9f --- /dev/null +++ b/sender-grpc/src/main/java/zipkin2/reporter/otel/grpc/AwaitableUnaryClientCallListener.java @@ -0,0 +1,100 @@ +/* + * Copyright The OpenZipkin Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package zipkin2.reporter.otel.grpc; + +import io.grpc.ClientCall; +import io.grpc.Metadata; +import io.grpc.Status; +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** Blocks until {@link #onClose}. */ +// ported from zipkin2.reporter.internal.AwaitableCallback +final class AwaitableUnaryClientCallListener extends ClientCall.Listener { + final CountDownLatch countDown = new CountDownLatch(1); + /** this differentiates between not yet set and null */ + boolean resultSet; // guarded by this + + Object result; // guarded by this + + long serverTimeoutMs; // how long to wait for server response in milliseconds + + AwaitableUnaryClientCallListener(long serverTimeoutMs) { + if (serverTimeoutMs <= 0) { + throw new IllegalArgumentException("Server response timeout must be greater than 0"); + } + this.serverTimeoutMs = serverTimeoutMs; + } + + /** + * Blocks until {@link #onClose}. Throws if no value was received, multiple + * values were received, there was a status error, or waited longer than {@link #serverTimeoutMs}. + */ + V await() throws IOException { + boolean interrupted = false; + try { + while (true) { + try { + if (!countDown.await(serverTimeoutMs, TimeUnit.MILLISECONDS)) { + throw new IllegalStateException( + "timeout waiting for onClose. timeoutMs=" + serverTimeoutMs + + ", resultSet=" + resultSet); + } + Object result; + synchronized (this) { + if (!resultSet) continue; + result = this.result; + } + if (result instanceof Throwable) { + if (result instanceof Error) throw (Error) result; + if (result instanceof IOException) throw (IOException) result; + if (result instanceof RuntimeException) throw (RuntimeException) result; + // Don't set interrupted status when the callback received InterruptedException + throw new RuntimeException((Throwable) result); + } + return (V) result; + } catch (InterruptedException e) { + interrupted = true; + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + @Override + public void onHeaders(Metadata headers) { + } + + @Override + public synchronized void onMessage(V value) { + if (resultSet) { + throw Status.INTERNAL + .withDescription("More than one value received for unary call") + .asRuntimeException(); + } + result = value; + resultSet = true; + } + + @Override + public synchronized void onClose(Status status, Metadata trailers) { + if (status.isOk()) { + if (!resultSet) { + result = + Status.INTERNAL + .withDescription("No value received for unary call") + .asRuntimeException(trailers); + } + } else { + result = status.asRuntimeException(trailers); + } + resultSet = true; + countDown.countDown(); + } +} diff --git a/sender-grpc/src/main/java/zipkin2/reporter/otel/grpc/OtelGrpcSender.java b/sender-grpc/src/main/java/zipkin2/reporter/otel/grpc/OtelGrpcSender.java new file mode 100644 index 0000000..c88750a --- /dev/null +++ b/sender-grpc/src/main/java/zipkin2/reporter/otel/grpc/OtelGrpcSender.java @@ -0,0 +1,159 @@ +/* + * Copyright The OpenZipkin Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package zipkin2.reporter.otel.grpc; + +import static io.grpc.CallOptions.DEFAULT; +import static io.grpc.MethodDescriptor.generateFullMethodName; + +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ManagedChannel; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse; +import io.opentelemetry.proto.trace.v1.TracesData; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import zipkin2.reporter.BytesMessageSender; +import zipkin2.reporter.ClosedSenderException; +import zipkin2.reporter.Encoding; + +public final class OtelGrpcSender extends BytesMessageSender.Base { + static final int DEFAULT_SERVER_TIMEOUT_MS = 5000; + + public static Builder newBuilder(Channel channel) { // visible for testing + return new Builder(channel); + } + + public static final class Builder { + final Channel channel; + CallOptions callOptions = DEFAULT; + boolean shutdownChannelOnClose; + long serverResponseTimeoutMs = DEFAULT_SERVER_TIMEOUT_MS; + + Builder(Channel channel) { + if (channel == null) throw new NullPointerException("channel == null"); + this.channel = channel; + } + + public Builder callOptions(CallOptions callOptions) { + if (callOptions == null) throw new NullPointerException("callOptions == null"); + this.callOptions = callOptions; + return this; + } + + public Builder serverResponseTimeoutMs(long serverResponseTimeoutMs) { + if (serverResponseTimeoutMs <= 0) { + throw new IllegalArgumentException("Server response timeout must be greater than 0"); + } + this.serverResponseTimeoutMs = serverResponseTimeoutMs; + return this; + } + + public OtelGrpcSender build() { + return new OtelGrpcSender(this); + } + } + + final Channel channel; + final CallOptions callOptions; + final boolean shutdownChannelOnClose; + final long serverResponseTimeoutMs; + + OtelGrpcSender(Builder builder) { + super(Encoding.PROTO3); + channel = builder.channel; + callOptions = builder.callOptions; + serverResponseTimeoutMs = builder.serverResponseTimeoutMs; + shutdownChannelOnClose = builder.shutdownChannelOnClose; + } + + @Override public int messageMaxBytes() { + return 1024 * 1024; // 1 MiB for now + } + + /** close is typically called from a different thread */ + volatile boolean closeCalled; + + private static final String SERVICE_NAME = "opentelemetry.proto.collector.trace.v1.TraceService"; + + private static final MethodDescriptor.Marshaller REQUEST_MARSHALLER = + new MethodDescriptor.Marshaller() { + @Override + public InputStream stream(TracesData value) { + return value.toByteString().newInput(); + } + + @Override + public TracesData parse(InputStream stream) { + throw new UnsupportedOperationException("Only for serializing"); + } + }; + + private static final MethodDescriptor.Marshaller RESPONSE_MARSHALER = + new MethodDescriptor.Marshaller() { + @Override + public InputStream stream(ExportTraceServiceResponse value) { + throw new UnsupportedOperationException("Only for parsing"); + } + + @Override + public ExportTraceServiceResponse parse(InputStream stream) { + try { + return ExportTraceServiceResponse.parseFrom(stream); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }; + + private static final io.grpc.MethodDescriptor + getExportMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.UNARY) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "Export")) + .setRequestMarshaller(REQUEST_MARSHALLER) + .setResponseMarshaller(RESPONSE_MARSHALER) + .build(); + + @Override public void send(List encodedSpans) throws IOException { + if (closeCalled) throw new ClosedSenderException(); + + TracesData.Builder request = TracesData.newBuilder(); + + for (byte[] encodedSpan : encodedSpans) { + request.mergeFrom(TracesData.parseFrom(encodedSpan)); + } + + ClientCall call = + channel.newCall(getExportMethod, callOptions); + + AwaitableUnaryClientCallListener listener = + new AwaitableUnaryClientCallListener<>(serverResponseTimeoutMs); + try { + call.start(listener, new Metadata()); + call.request(1); + call.sendMessage(request.build()); + call.halfClose(); + } catch (RuntimeException | Error t) { + call.cancel(null, t); + throw t; + } + listener.await(); + } + + @Override public String toString() { + return "OtelGrpcSender{}"; + } + + @Override public void close() { + if (!shutdownChannelOnClose) return; + if (closeCalled) return; + closeCalled = true; + ((ManagedChannel) channel).shutdownNow(); + } +} diff --git a/sender-http/pom.xml b/sender-http/pom.xml deleted file mode 100644 index 07f3bb4..0000000 --- a/sender-http/pom.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - io.zipkin.contrib.otel - zipkin-otel-parent - 0.1.0-SNAPSHOT - ../pom.xml - - 4.0.0 - - zipkin-sender-http - Zipkin Sender: OpenTelemetry Proto over HTTP - - - ${project.basedir}/.. - - - - - ${project.groupId} - zipkin-translation-otel - ${project.version} - - - io.zipkin.reporter2 - zipkin-reporter - ${zipkin-reporter.version} - - - - ${zipkin.groupId} - zipkin-tests - ${zipkin.version} - test - - - ${project.groupId} - zipkin-encoder-otel - ${project.version} - test - - - - org.awaitility - awaitility - ${awaitility.version} - test - - - \ No newline at end of file diff --git a/tests/pom.xml b/tests/pom.xml new file mode 100644 index 0000000..ff079e4 --- /dev/null +++ b/tests/pom.xml @@ -0,0 +1,40 @@ + + + + 4.0.0 + + + io.zipkin.contrib.otel + zipkin-otel-parent + 0.1.0-SNAPSHOT + ../pom.xml + + + zipkin-otel-tests + Zipkin OpenTelemetry Tests + pom + + + ${project.basedir}/.. + + + + sender-tests + + diff --git a/tests/sender-tests/pom.xml b/tests/sender-tests/pom.xml new file mode 100644 index 0000000..f6a665b --- /dev/null +++ b/tests/sender-tests/pom.xml @@ -0,0 +1,91 @@ + + + + 4.0.0 + + + io.zipkin.contrib.otel + zipkin-otel-tests + 0.1.0-SNAPSHOT + ../pom.xml + + + zipkin-sender-otel-tests + Zipkin OpenTelemetry Sender Tests + + + ${project.basedir}/.. + + + + + io.zipkin.brave + brave + ${brave.version} + + + * + * + + + + provided + + + ${project.groupId} + brave-encoder-otel + ${project.version} + + + ${project.groupId} + zipkin-sender-otel-grpc + ${project.version} + + + io.zipkin.reporter2 + zipkin-sender-okhttp3 + ${zipkin-reporter.version} + + + + org.testcontainers + testcontainers + ${testcontainers.version} + test + + + org.testcontainers + junit-jupiter + ${testcontainers.version} + test + + + org.awaitility + awaitility + ${awaitility.version} + test + + + io.grpc + grpc-okhttp + ${grpc.version} + test + + + diff --git a/tests/sender-tests/src/test/java/zipkin2/reporter/otel/BasicUsageTest.java b/tests/sender-tests/src/test/java/zipkin2/reporter/otel/BasicUsageTest.java new file mode 100644 index 0000000..deed31f --- /dev/null +++ b/tests/sender-tests/src/test/java/zipkin2/reporter/otel/BasicUsageTest.java @@ -0,0 +1,202 @@ +/* + * Copyright 2016-2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package zipkin2.reporter.otel; + +import brave.Tags; +import brave.handler.MutableSpan; +import brave.propagation.TraceContext; +import io.grpc.ManagedChannelBuilder; +import java.io.Closeable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import brave.Span; +import brave.Span.Kind; +import brave.Tracer; +import brave.Tracing; +import brave.handler.SpanHandler; +import brave.propagation.ThreadLocalCurrentTraceContext; +import brave.sampler.Sampler; +import okhttp3.OkHttpClient; +import okhttp3.OkHttpClient.Builder; +import okhttp3.Request; +import okhttp3.Response; +import org.assertj.core.api.BDDAssertions; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import zipkin2.reporter.AsyncReporter; +import zipkin2.reporter.Encoding; +import zipkin2.reporter.okhttp3.OkHttpSender; +import zipkin2.reporter.otel.brave.OtelEncoder; +import zipkin2.reporter.otel.grpc.OtelGrpcSender; + +@Testcontainers +class BasicUsageTest { + + @Container + static JaegerAllInOne jaegerAllInOne = new JaegerAllInOne(); + + TestSetup testSetup; + + Tracing tracing; + + @ParameterizedTest + @EnumSource(TestSetup.class) + void shouldSendSpansToOtlpEndpoint(TestSetup testSetup) throws InterruptedException, IOException { + // Setup + ThreadLocalCurrentTraceContext braveCurrentTraceContext = ThreadLocalCurrentTraceContext.newBuilder() + .build(); + this.testSetup = testSetup; + SpanHandler spanHandler = testSetup.apply(jaegerAllInOne.getHttpOtlpPort()); + tracing = Tracing.newBuilder() + .currentTraceContext(braveCurrentTraceContext) + .supportsJoin(false) + .traceId128Bit(true) + .sampler(Sampler.ALWAYS_SAMPLE) + .addSpanHandler(spanHandler) + .localServiceName("my-service") + .build(); + Tracer braveTracer = tracing.tracer(); + + List traceIds = new ArrayList<>(); + final int size = 5; + for (int i = 0; i < size; i++) { + // Given + Span span = braveTracer.nextSpan().name("foo " + i) + .tag("foo tag", "foo value") + .kind(Kind.CONSUMER) + .error(new RuntimeException("BOOOOOM!")) + .remoteServiceName("remote service") + .start(); + String traceId = span.context().traceIdString(); + System.out.println("Trace Id <" + traceId + ">"); + span.remoteIpAndPort("http://localhost", 123456); + Thread.sleep(50); + span.annotate("boom!"); + Thread.sleep(50); + + // When + span.finish(); + traceIds.add(span.context().traceIdString()); + } + + testSetup.close(); + + // Then + Awaitility.await().untilAsserted(() -> { + BDDAssertions.then(traceIds).hasSize(size); + OkHttpClient client = new Builder() + .build(); + traceIds.forEach(traceId -> { + Request request = new Request.Builder().url("http://localhost:" + jaegerAllInOne.getQueryPort() + "/api/traces/" + traceId).build(); + try (Response response = client.newCall(request).execute()) { + BDDAssertions.then(response.isSuccessful()).isTrue(); + } + catch (IOException e) { + throw new RuntimeException(e); + } + }); + + }); + } + + enum TestSetup implements Function, Closeable { + + + OK_HTTP_OTEL_SENDER { + + OkHttpSender okHttpSender; + + AsyncReporter reporter; + + SpanHandler spanHandler; + + @Override + public void close() { + if (reporter != null) { + reporter.close(); + } + if (okHttpSender != null) { + okHttpSender.close(); + } + } + + @Override + public SpanHandler apply(Integer port) { + okHttpSender = OkHttpSender.newBuilder() + .encoding(Encoding.PROTO3) + .endpoint("http://localhost:" + port + "/v1/traces") + .build(); + OtelEncoder otelEncoder = new OtelEncoder(Tags.ERROR); + reporter = AsyncReporter.builder(okHttpSender).build(otelEncoder); + spanHandler = new SpanHandler() { + @Override + public boolean end(TraceContext context, MutableSpan span, Cause cause) { + reporter.report(span); + return true; + } + }; + return spanHandler; + } + }, + + GRPC_SENDER { + + OtelGrpcSender otelGrpcSender; + + AsyncReporter reporter; + + SpanHandler spanHandler; + + @Override + public void close() { + if (otelGrpcSender != null) { + otelGrpcSender.close(); + } + } + + @Override + public SpanHandler apply(Integer port) { + otelGrpcSender = OtelGrpcSender.newBuilder(ManagedChannelBuilder + .forAddress("localhost", jaegerAllInOne.getGrpcOtlpPort()) + .usePlaintext() + .build()).build(); + OtelEncoder otelEncoder = new OtelEncoder(Tags.ERROR); + reporter = AsyncReporter.builder(otelGrpcSender).build(otelEncoder); + spanHandler = new SpanHandler() { + @Override + public boolean end(TraceContext context, MutableSpan span, Cause cause) { + reporter.report(span); + return true; + } + }; + return spanHandler; + } + + } + } + + @AfterEach + void shutdown() throws IOException { + if (tracing != null) { + tracing.close(); + } + } +} \ No newline at end of file diff --git a/tests/sender-tests/src/test/java/zipkin2/reporter/otel/JaegerAllInOne.java b/tests/sender-tests/src/test/java/zipkin2/reporter/otel/JaegerAllInOne.java new file mode 100644 index 0000000..c852a35 --- /dev/null +++ b/tests/sender-tests/src/test/java/zipkin2/reporter/otel/JaegerAllInOne.java @@ -0,0 +1,70 @@ +/* + * Copyright 2016-2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package zipkin2.reporter.otel; + +import java.util.Collections; +import java.util.Set; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; + +class JaegerAllInOne extends GenericContainer { + + static final int JAEGER_QUERY_PORT = 16686; + + static final int JAEGER_ADMIN_PORT = 14269; + + static final int GRPC_OTLP_PORT = 14250; + + static final int HTTP_OTLP_PORT = 4318; + + public JaegerAllInOne() { + super("jaegertracing/all-in-one:1.57"); + init(); + } + + protected void init() { + waitingFor(new BoundPortHttpWaitStrategy(JAEGER_ADMIN_PORT)); + withExposedPorts(JAEGER_ADMIN_PORT, + JAEGER_QUERY_PORT, + GRPC_OTLP_PORT, + HTTP_OTLP_PORT); + } + + public int getHttpOtlpPort() { + return getMappedPort(HTTP_OTLP_PORT); + } + + public int getGrpcOtlpPort() { + return getMappedPort(GRPC_OTLP_PORT); + } + + public int getQueryPort() { + return getMappedPort(JAEGER_QUERY_PORT); + } + + public static class BoundPortHttpWaitStrategy extends HttpWaitStrategy { + private final int port; + + public BoundPortHttpWaitStrategy(int port) { + this.port = port; + } + + @Override + protected Set getLivenessCheckPorts() { + int mappedPort = this.waitStrategyTarget.getMappedPort(port); + return Collections.singleton(mappedPort); + } + } +} \ No newline at end of file diff --git a/translation-otel/pom.xml b/translation-otel/pom.xml index e53edb4..490de1e 100644 --- a/translation-otel/pom.xml +++ b/translation-otel/pom.xml @@ -22,6 +22,18 @@ + + io.opentelemetry + opentelemetry-api + + + io.opentelemetry.proto + opentelemetry-proto + + + io.opentelemetry.semconv + opentelemetry-semconv + ${zipkin.groupId} zipkin diff --git a/translation-otel/src/main/java/zipkin2/translation/zipkin/AttributesExtractor.java b/translation-otel/src/main/java/zipkin2/translation/zipkin/AttributesExtractor.java new file mode 100644 index 0000000..0f984b1 --- /dev/null +++ b/translation-otel/src/main/java/zipkin2/translation/zipkin/AttributesExtractor.java @@ -0,0 +1,42 @@ +/* + * Copyright The OpenZipkin Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package zipkin2.translation.zipkin; + +import io.opentelemetry.proto.common.v1.AnyValue; +import io.opentelemetry.proto.common.v1.KeyValue; +import java.util.Map; + +/** + * LabelExtractor extracts the set of OTel Span labels equivalent to the annotations in a + * given Zipkin Span. + * + *

Zipkin annotations are converted to OTel Span labels by using annotation.value as the + * key and annotation.timestamp as the value. + * + *

Zipkin tags are converted to OTel Span labels by using annotation.key as the key and + * the String value of annotation.value as the value. + * + *

Zipkin annotations with equivalent OTel labels will be renamed to the OpenTelemetry + * Trace name. + */ +final class AttributesExtractor { + + private final Map renamedLabels; + + AttributesExtractor(Map renamedLabels) { + this.renamedLabels = renamedLabels; + } + + void addTag(KeyValue.Builder target, String key, String value) { + target.setKey(getLabelName(key)).setValue( + AnyValue.newBuilder().setStringValue(value).build()); + } + + private String getLabelName(String zipkinName) { + String renamed = renamedLabels.get(zipkinName); + return renamed != null ? renamed : zipkinName; + } + +} diff --git a/translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java b/translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java new file mode 100644 index 0000000..227fd6f --- /dev/null +++ b/translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java @@ -0,0 +1,142 @@ +/* + * Copyright The OpenZipkin Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package zipkin2.translation.zipkin; + +import com.google.protobuf.ByteString; +import io.opentelemetry.proto.common.v1.AnyValue; +import io.opentelemetry.proto.common.v1.KeyValue; +import io.opentelemetry.proto.trace.v1.ResourceSpans; +import io.opentelemetry.proto.trace.v1.ResourceSpans.Builder; +import io.opentelemetry.proto.trace.v1.ScopeSpans; +import io.opentelemetry.proto.trace.v1.Span; +import io.opentelemetry.proto.trace.v1.Span.SpanKind; +import io.opentelemetry.proto.trace.v1.TracesData; +import io.opentelemetry.semconv.HttpAttributes; +import io.opentelemetry.semconv.NetworkAttributes; +import io.opentelemetry.semconv.SemanticAttributes; +import io.opentelemetry.semconv.ServerAttributes; +import io.opentelemetry.semconv.ServiceAttributes; +import io.opentelemetry.semconv.UrlAttributes; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import zipkin2.Span.Kind; + + +/** + * SpanTranslator converts a Zipkin Span to a OpenTelemetry Span. + */ +public final class SpanTranslator { + + static final AttributesExtractor ATTRIBUTES_EXTRACTOR; + + static { + Map renamedLabels = new LinkedHashMap<>(); + renamedLabels.put("http.host", ServerAttributes.SERVER_ADDRESS.getKey()); + renamedLabels.put("http.method", HttpAttributes.HTTP_REQUEST_METHOD.getKey()); + renamedLabels.put("http.status_code", HttpAttributes.HTTP_RESPONSE_STATUS_CODE.getKey()); + renamedLabels.put("http.request.size", "http.request.body.size"); + renamedLabels.put("http.response.size", "http.response.body.size"); + renamedLabels.put("http.url", UrlAttributes.URL_FULL.getKey()); + ATTRIBUTES_EXTRACTOR = new AttributesExtractor(renamedLabels); + } + + /** + * Converts a Zipkin Span into a OpenTelemetry Span. + * + *

Ex. + * + *

{@code
+   * tracesData = SpanTranslator.translate(zipkinSpan);
+   * }
+ * + * @param zipkinSpan The Zipkin Span. + * @return A OpenTelemetry Span. + */ + public static TracesData translate(zipkin2.Span zipkinSpan) { + TracesData.Builder tracesDataBuilder = TracesData.newBuilder(); + Builder resourceSpansBuilder = ResourceSpans.newBuilder(); + ScopeSpans.Builder scopeSpanBuilder = ScopeSpans.newBuilder(); + Span.Builder spanBuilder = builderForSingleSpan(zipkinSpan, resourceSpansBuilder); + scopeSpanBuilder.addSpans(spanBuilder + .build()); + resourceSpansBuilder.addScopeSpans(scopeSpanBuilder + .build()); + tracesDataBuilder.addResourceSpans(resourceSpansBuilder.build()); + return tracesDataBuilder.build(); + } + + private static Span.Builder builderForSingleSpan(zipkin2.Span span, Builder resourceSpansBuilder) { + Span.Builder spanBuilder = Span.newBuilder() + .setTraceId(ByteString.fromHex(span.traceId())) + .setSpanId(ByteString.fromHex(span.id())) + .setName(span.name()); + if (span.parentId() != null) { + spanBuilder.setParentSpanId(ByteString.fromHex(span.parentId())); + } + long start = span.timestamp(); + long finish = span.timestampAsLong() + span.durationAsLong(); + spanBuilder.setStartTimeUnixNano(TimeUnit.MICROSECONDS.toNanos(start)); + if (start != 0 && finish != 0L) { + spanBuilder.setEndTimeUnixNano(TimeUnit.MICROSECONDS.toNanos(finish)); + } + Kind kind = span.kind(); + if (kind != null) { + switch (kind) { + case CLIENT: + spanBuilder.setKind(SpanKind.SPAN_KIND_CLIENT); + break; + case SERVER: + spanBuilder.setKind(SpanKind.SPAN_KIND_SERVER); + break; + case PRODUCER: + spanBuilder.setKind(SpanKind.SPAN_KIND_PRODUCER); + break; + case CONSUMER: + spanBuilder.setKind(SpanKind.SPAN_KIND_CONSUMER); + break; + default: + spanBuilder.setKind(SpanKind.SPAN_KIND_INTERNAL); //TODO: Should it work like this? + } + } + String localServiceName = span.localServiceName(); + if (localServiceName != null) { + resourceSpansBuilder.getResourceBuilder().addAttributes( + KeyValue.newBuilder().setKey(ServiceAttributes.SERVICE_NAME.getKey()) + .setValue(AnyValue.newBuilder().setStringValue(localServiceName).build()).build()); + } + String localIp = span.localEndpoint() != null ? span.localEndpoint().ipv4() : null; + if (localIp != null) { + spanBuilder.addAttributes(KeyValue.newBuilder().setKey(NetworkAttributes.NETWORK_LOCAL_ADDRESS.getKey()) + .setValue(AnyValue.newBuilder().setStringValue(localIp).build()).build()); + } + int localPort = span.localEndpoint() != null ? span.localEndpoint().portAsInt() : 0; + if (localPort != 0) { + spanBuilder.addAttributes(KeyValue.newBuilder().setKey(ServerAttributes.SERVER_PORT.getKey()) + .setValue(AnyValue.newBuilder().setIntValue(localPort).build()).build()); + } + String peerName = span.remoteServiceName(); + if (peerName != null) { + spanBuilder.addAttributes(KeyValue.newBuilder().setKey(SemanticAttributes.NET_SOCK_PEER_NAME.getKey()) + .setValue(AnyValue.newBuilder().setStringValue(peerName).build()).build()); + } + String peerIp = span.remoteEndpoint() != null ? span.remoteEndpoint().ipv4() : null; + if (peerIp != null) { + spanBuilder.addAttributes(KeyValue.newBuilder().setKey(SemanticAttributes.NET_SOCK_PEER_ADDR.getKey()) + .setValue(AnyValue.newBuilder().setStringValue(peerIp).build()).build()); + } + int peerPort = span.remoteEndpoint() != null ? span.remoteEndpoint().portAsInt() : 0; + if (peerPort != 0) { + spanBuilder.addAttributes(KeyValue.newBuilder().setKey(SemanticAttributes.NET_SOCK_PEER_PORT.getKey()) + .setValue(AnyValue.newBuilder().setIntValue(peerPort).build()).build()); + } + span.tags().forEach((key, value) -> ATTRIBUTES_EXTRACTOR.addTag(KeyValue.newBuilder(), key, value)); + span.annotations().forEach(annotation -> spanBuilder.addEventsBuilder().setTimeUnixNano(TimeUnit.MICROSECONDS.toNanos(annotation.timestamp())) + .setName(annotation.value())); + return spanBuilder; + } + + +} From 37ef9657520728f2114f5954a80a396ec916d48d Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 14 Jun 2024 14:10:27 +0200 Subject: [PATCH 03/30] WIP --- .../java/zipkin2/reporter/otel/JaegerAllInOne.java | 2 +- translation-otel/pom.xml | 13 +++++++++++-- .../translation/zipkin/AttributesExtractor.java | 13 +++++++++++-- .../zipkin2/translation/zipkin/SpanTranslator.java | 13 +++++++++++-- 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/tests/sender-tests/src/test/java/zipkin2/reporter/otel/JaegerAllInOne.java b/tests/sender-tests/src/test/java/zipkin2/reporter/otel/JaegerAllInOne.java index c852a35..5b84614 100644 --- a/tests/sender-tests/src/test/java/zipkin2/reporter/otel/JaegerAllInOne.java +++ b/tests/sender-tests/src/test/java/zipkin2/reporter/otel/JaegerAllInOne.java @@ -25,7 +25,7 @@ class JaegerAllInOne extends GenericContainer { static final int JAEGER_ADMIN_PORT = 14269; - static final int GRPC_OTLP_PORT = 14250; + static final int GRPC_OTLP_PORT = 4317; static final int HTTP_OTLP_PORT = 4318; diff --git a/translation-otel/pom.xml b/translation-otel/pom.xml index 490de1e..c273f17 100644 --- a/translation-otel/pom.xml +++ b/translation-otel/pom.xml @@ -1,8 +1,17 @@ diff --git a/translation-otel/src/main/java/zipkin2/translation/zipkin/AttributesExtractor.java b/translation-otel/src/main/java/zipkin2/translation/zipkin/AttributesExtractor.java index 0f984b1..f5fe163 100644 --- a/translation-otel/src/main/java/zipkin2/translation/zipkin/AttributesExtractor.java +++ b/translation-otel/src/main/java/zipkin2/translation/zipkin/AttributesExtractor.java @@ -1,6 +1,15 @@ /* - * Copyright The OpenZipkin Authors - * SPDX-License-Identifier: Apache-2.0 + * Copyright 2016-2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. */ package zipkin2.translation.zipkin; diff --git a/translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java b/translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java index 227fd6f..57405c3 100644 --- a/translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java +++ b/translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java @@ -1,6 +1,15 @@ /* - * Copyright The OpenZipkin Authors - * SPDX-License-Identifier: Apache-2.0 + * Copyright 2016-2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. */ package zipkin2.translation.zipkin; From 0f7ee5afb236bc0323a5217dba0dd24e984e0430 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 14 Jun 2024 18:10:26 +0200 Subject: [PATCH 04/30] Passing build --- .../otel/grpc/OpenTelemetryGrpcCollector.java | 2 -- .../otel/http/OpenTelemetryHttpCollector.java | 2 -- encoder-otel-brave/pom.xml | 13 +++++++++++-- .../reporter/otel/brave/AttributesExtractor.java | 15 +++++++++++---- .../zipkin2/reporter/otel/brave/OtelEncoder.java | 16 +++++++++++----- .../reporter/otel/brave/SpanTranslator.java | 14 +++++++++++--- encoder-otel-zipkin/pom.xml | 13 +++++++++++-- .../reporter/otel/zipkin/OtelEncoder.java | 13 +++++++++++-- .../ZipkinOpenTelemetryHttpCollectorModule.java | 1 - pom.xml | 2 +- sender-grpc/pom.xml | 13 +++++++++++-- .../grpc/AwaitableUnaryClientCallListener.java | 13 +++++++++++-- .../reporter/otel/grpc/OtelGrpcSender.java | 13 +++++++++++-- .../zipkin2/reporter/otel/BasicUsageTest.java | 2 +- .../zipkin2/reporter/otel/JaegerAllInOne.java | 2 +- .../translation/zipkin/AttributesExtractor.java | 2 +- .../translation/zipkin/SpanTranslator.java | 2 +- 17 files changed, 104 insertions(+), 34 deletions(-) diff --git a/collector-grpc/src/main/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollector.java b/collector-grpc/src/main/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollector.java index df566ab..05bc38d 100644 --- a/collector-grpc/src/main/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollector.java +++ b/collector-grpc/src/main/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollector.java @@ -13,14 +13,12 @@ */ package zipkin2.collector.otel.grpc; -import com.linecorp.armeria.common.ContextAwareBlockingTaskExecutor; import com.linecorp.armeria.server.ServerBuilder; import com.linecorp.armeria.server.ServerConfigurator; import com.linecorp.armeria.server.ServiceRequestContext; import com.linecorp.armeria.server.grpc.protocol.AbstractUnsafeUnaryGrpcService; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; -import java.nio.ByteBuffer; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import zipkin2.Callback; diff --git a/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java b/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java index 9102328..b013ebb 100644 --- a/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java +++ b/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java @@ -13,8 +13,6 @@ */ package zipkin2.collector.otel.http; -import static zipkin2.Call.propagateIfFatal; - import com.linecorp.armeria.common.AggregationOptions; import com.linecorp.armeria.common.HttpData; import com.linecorp.armeria.common.HttpRequest; diff --git a/encoder-otel-brave/pom.xml b/encoder-otel-brave/pom.xml index 5924383..b960283 100644 --- a/encoder-otel-brave/pom.xml +++ b/encoder-otel-brave/pom.xml @@ -1,8 +1,17 @@ diff --git a/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/AttributesExtractor.java b/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/AttributesExtractor.java index 5e14495..6dea70d 100644 --- a/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/AttributesExtractor.java +++ b/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/AttributesExtractor.java @@ -1,13 +1,20 @@ /* - * Copyright The OpenZipkin Authors - * SPDX-License-Identifier: Apache-2.0 + * Copyright 2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. */ package zipkin2.reporter.otel.brave; import brave.Tag; import brave.handler.MutableSpan; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.proto.common.v1.AnyValue; import io.opentelemetry.proto.common.v1.KeyValue; import java.util.Map; diff --git a/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/OtelEncoder.java b/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/OtelEncoder.java index 1dc6afa..57643db 100644 --- a/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/OtelEncoder.java +++ b/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/OtelEncoder.java @@ -1,15 +1,21 @@ /* - * Copyright The OpenZipkin Authors - * SPDX-License-Identifier: Apache-2.0 + * Copyright 2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. */ package zipkin2.reporter.otel.brave; import brave.Tag; import brave.handler.MutableSpan; -import com.google.protobuf.CodedOutputStream; -import io.opentelemetry.api.trace.Span; import io.opentelemetry.proto.trace.v1.TracesData; -import java.io.IOException; import zipkin2.reporter.BytesEncoder; import zipkin2.reporter.Encoding; diff --git a/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java b/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java index 0c2c479..9834061 100644 --- a/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java +++ b/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java @@ -1,6 +1,15 @@ /* - * Copyright The OpenZipkin Authors - * SPDX-License-Identifier: Apache-2.0 + * Copyright 2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. */ package zipkin2.reporter.otel.brave; @@ -9,7 +18,6 @@ import brave.handler.MutableSpan; import brave.handler.MutableSpan.AnnotationConsumer; import brave.handler.MutableSpan.TagConsumer; -import brave.handler.SpanHandler; import com.google.protobuf.ByteString; import io.opentelemetry.proto.common.v1.AnyValue; import io.opentelemetry.proto.common.v1.KeyValue; diff --git a/encoder-otel-zipkin/pom.xml b/encoder-otel-zipkin/pom.xml index aab1e8f..0cc027c 100644 --- a/encoder-otel-zipkin/pom.xml +++ b/encoder-otel-zipkin/pom.xml @@ -1,8 +1,17 @@ diff --git a/encoder-otel-zipkin/src/main/java/zipkin2/reporter/otel/zipkin/OtelEncoder.java b/encoder-otel-zipkin/src/main/java/zipkin2/reporter/otel/zipkin/OtelEncoder.java index c379403..39543d3 100644 --- a/encoder-otel-zipkin/src/main/java/zipkin2/reporter/otel/zipkin/OtelEncoder.java +++ b/encoder-otel-zipkin/src/main/java/zipkin2/reporter/otel/zipkin/OtelEncoder.java @@ -1,6 +1,15 @@ /* - * Copyright The OpenZipkin Authors - * SPDX-License-Identifier: Apache-2.0 + * Copyright 2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. */ package zipkin2.reporter.otel.zipkin; diff --git a/module/src/main/java/zipkin/module/otel/ZipkinOpenTelemetryHttpCollectorModule.java b/module/src/main/java/zipkin/module/otel/ZipkinOpenTelemetryHttpCollectorModule.java index 49b51d8..6f55d1e 100644 --- a/module/src/main/java/zipkin/module/otel/ZipkinOpenTelemetryHttpCollectorModule.java +++ b/module/src/main/java/zipkin/module/otel/ZipkinOpenTelemetryHttpCollectorModule.java @@ -20,7 +20,6 @@ import org.springframework.context.annotation.Configuration; import zipkin2.collector.CollectorMetrics; import zipkin2.collector.CollectorSampler; -import zipkin2.collector.otel.grpc.OpenTelemetryGrpcCollector; import zipkin2.collector.otel.http.OpenTelemetryHttpCollector; import zipkin2.storage.StorageComponent; diff --git a/pom.xml b/pom.xml index 061085d..03f98b2 100644 --- a/pom.xml +++ b/pom.xml @@ -429,7 +429,7 @@ ${license-maven-plugin.version} ${license.skip} -
${main.basedir}/src/etc/header.txt
+
${maven.multiModuleProjectDirectory}/src/etc/header.txt
SLASHSTAR_STYLE diff --git a/sender-grpc/pom.xml b/sender-grpc/pom.xml index 347c52b..3e62191 100644 --- a/sender-grpc/pom.xml +++ b/sender-grpc/pom.xml @@ -1,8 +1,17 @@ diff --git a/sender-grpc/src/main/java/zipkin2/reporter/otel/grpc/AwaitableUnaryClientCallListener.java b/sender-grpc/src/main/java/zipkin2/reporter/otel/grpc/AwaitableUnaryClientCallListener.java index 552af9f..fcd44e4 100644 --- a/sender-grpc/src/main/java/zipkin2/reporter/otel/grpc/AwaitableUnaryClientCallListener.java +++ b/sender-grpc/src/main/java/zipkin2/reporter/otel/grpc/AwaitableUnaryClientCallListener.java @@ -1,6 +1,15 @@ /* - * Copyright The OpenZipkin Authors - * SPDX-License-Identifier: Apache-2.0 + * Copyright 2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. */ package zipkin2.reporter.otel.grpc; diff --git a/sender-grpc/src/main/java/zipkin2/reporter/otel/grpc/OtelGrpcSender.java b/sender-grpc/src/main/java/zipkin2/reporter/otel/grpc/OtelGrpcSender.java index c88750a..77b8e22 100644 --- a/sender-grpc/src/main/java/zipkin2/reporter/otel/grpc/OtelGrpcSender.java +++ b/sender-grpc/src/main/java/zipkin2/reporter/otel/grpc/OtelGrpcSender.java @@ -1,6 +1,15 @@ /* - * Copyright The OpenZipkin Authors - * SPDX-License-Identifier: Apache-2.0 + * Copyright 2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. */ package zipkin2.reporter.otel.grpc; diff --git a/tests/sender-tests/src/test/java/zipkin2/reporter/otel/BasicUsageTest.java b/tests/sender-tests/src/test/java/zipkin2/reporter/otel/BasicUsageTest.java index deed31f..88f929d 100644 --- a/tests/sender-tests/src/test/java/zipkin2/reporter/otel/BasicUsageTest.java +++ b/tests/sender-tests/src/test/java/zipkin2/reporter/otel/BasicUsageTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 The OpenZipkin Authors + * Copyright 2024 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at diff --git a/tests/sender-tests/src/test/java/zipkin2/reporter/otel/JaegerAllInOne.java b/tests/sender-tests/src/test/java/zipkin2/reporter/otel/JaegerAllInOne.java index 5b84614..fbc4a84 100644 --- a/tests/sender-tests/src/test/java/zipkin2/reporter/otel/JaegerAllInOne.java +++ b/tests/sender-tests/src/test/java/zipkin2/reporter/otel/JaegerAllInOne.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 The OpenZipkin Authors + * Copyright 2024 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at diff --git a/translation-otel/src/main/java/zipkin2/translation/zipkin/AttributesExtractor.java b/translation-otel/src/main/java/zipkin2/translation/zipkin/AttributesExtractor.java index f5fe163..9baa04e 100644 --- a/translation-otel/src/main/java/zipkin2/translation/zipkin/AttributesExtractor.java +++ b/translation-otel/src/main/java/zipkin2/translation/zipkin/AttributesExtractor.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 The OpenZipkin Authors + * Copyright 2024 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at diff --git a/translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java b/translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java index 57405c3..493cafa 100644 --- a/translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java +++ b/translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 The OpenZipkin Authors + * Copyright 2024 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at From 45c1d4078762fc0e2c5d488c490fcd8eb0a4566a Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 14 Jun 2024 20:42:42 +0200 Subject: [PATCH 05/30] Collectors are working fine --- collector-grpc/pom.xml | 23 +- .../otel/grpc/OpenTelemetryGrpcCollector.java | 61 +++-- .../grpc/ITOpenTelemetryGrpcCollector.java | 64 ++++- collector-http/pom.xml | 22 +- .../otel/http/OpenTelemetryHttpCollector.java | 24 +- .../http/ITOpenTelemetryHttpCollector.java | 67 ++++- .../zipkin2/reporter/otel/BasicUsageTest.java | 174 +++++++++---- .../translation/zipkin/SpanTranslator.java | 244 +++++++++++++++++- 8 files changed, 581 insertions(+), 98 deletions(-) diff --git a/collector-grpc/pom.xml b/collector-grpc/pom.xml index 146ca56..18c81d1 100644 --- a/collector-grpc/pom.xml +++ b/collector-grpc/pom.xml @@ -39,6 +39,11 @@ zipkin-collector ${zipkin.version}
+ + ${project.groupId} + zipkin-encoder-otel + ${project.version} + ${armeria.groupId} @@ -52,18 +57,26 @@ ${zipkin.version} test - ${armeria.groupId} armeria-grpc ${armeria.version} test - - ${armeria.groupId} - armeria-junit5 - ${armeria.version} + org.awaitility + awaitility + ${awaitility.version} + test + + + io.opentelemetry + opentelemetry-sdk + test + + + io.opentelemetry + opentelemetry-exporter-otlp test
diff --git a/collector-grpc/src/main/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollector.java b/collector-grpc/src/main/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollector.java index 05bc38d..6c4282d 100644 --- a/collector-grpc/src/main/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollector.java +++ b/collector-grpc/src/main/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollector.java @@ -13,24 +13,31 @@ */ package zipkin2.collector.otel.grpc; +import com.google.protobuf.InvalidProtocolBufferException; import com.linecorp.armeria.server.ServerBuilder; import com.linecorp.armeria.server.ServerConfigurator; import com.linecorp.armeria.server.ServiceRequestContext; import com.linecorp.armeria.server.grpc.protocol.AbstractUnsafeUnaryGrpcService; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; +import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import zipkin2.Callback; -import zipkin2.codec.SpanBytesDecoder; +import zipkin2.codec.SpanBytesEncoder; import zipkin2.collector.Collector; import zipkin2.collector.CollectorComponent; import zipkin2.collector.CollectorMetrics; import zipkin2.collector.CollectorSampler; +import zipkin2.internal.ReadBuffer; import zipkin2.storage.StorageComponent; +import zipkin2.translation.zipkin.SpanTranslator; public final class OpenTelemetryGrpcCollector extends CollectorComponent implements ServerConfigurator { + public static Builder newBuilder() { return new Builder(); } @@ -40,23 +47,29 @@ public static final class Builder extends CollectorComponent.Builder { Collector.Builder delegate = Collector.newBuilder(OpenTelemetryGrpcCollector.class); CollectorMetrics metrics = CollectorMetrics.NOOP_METRICS; - @Override public Builder storage(StorageComponent storageComponent) { + @Override + public Builder storage(StorageComponent storageComponent) { delegate.storage(storageComponent); return this; } - @Override public Builder metrics(CollectorMetrics metrics) { - if (metrics == null) throw new NullPointerException("metrics == null"); + @Override + public Builder metrics(CollectorMetrics metrics) { + if (metrics == null) { + throw new NullPointerException("metrics == null"); + } delegate.metrics(this.metrics = metrics.forTransport("otel/grpc")); return this; } - @Override public Builder sampler(CollectorSampler sampler) { + @Override + public Builder sampler(CollectorSampler sampler) { delegate.sampler(sampler); return this; } - @Override public OpenTelemetryGrpcCollector build() { + @Override + public OpenTelemetryGrpcCollector build() { return new OpenTelemetryGrpcCollector(this); } @@ -72,22 +85,28 @@ public static final class Builder extends CollectorComponent.Builder { metrics = builder.metrics; } - @Override public OpenTelemetryGrpcCollector start() { + @Override + public OpenTelemetryGrpcCollector start() { return this; } - @Override public String toString() { + @Override + public String toString() { return "OpenTelemetryGrpcCollector{}"; } /** - * Reconfigures the service per https://github.com/open-telemetry/opentelemetry-proto/blob/v1.0.0/opentelemetry/proto/collector/trace/v1/trace_service.proto + * Reconfigures the service per + * https://github.com/open-telemetry/opentelemetry-proto/blob/v1.0.0/opentelemetry/proto/collector/trace/v1/trace_service.proto */ - @Override public void reconfigure(ServerBuilder sb) { - sb.service("/opentelemetry.proto.collector.trace.v1.TraceService/Export", new HttpService(this)); + @Override + public void reconfigure(ServerBuilder sb) { + sb.service("/opentelemetry.proto.collector.trace.v1.TraceService/Export", + new HttpService(this)); } static final class HttpService extends AbstractUnsafeUnaryGrpcService { + final Collector collector; final CollectorMetrics metrics; @@ -105,10 +124,18 @@ protected CompletionStage handleMessage(ServiceRequestContext ctx, Byte return CompletableFuture.completedFuture(bytes); // lenient on empty messages } - try { + try (ReadBuffer readBuffer = ReadBuffer.wrapUnsafe(bytes.nioBuffer())) { CompletableFutureCallback result = new CompletableFutureCallback(); - collector.acceptSpans(bytes.nioBuffer(), SpanBytesDecoder.PROTO3, result, ctx.blockingTaskExecutor()); - return result; + try { + ExportTraceServiceRequest request = ExportTraceServiceRequest.parseFrom( + ByteBufUtil.getBytes(bytes)); + List spans = SpanTranslator.translate(request); + byte[] encoded = SpanBytesEncoder.PROTO3.encodeList(spans); + collector.acceptSpans(encoded, result); + return result; + } catch (InvalidProtocolBufferException e) { + throw new RuntimeException(e); + } } finally { bytes.release(); } @@ -118,11 +145,13 @@ protected CompletionStage handleMessage(ServiceRequestContext ctx, Byte static final class CompletableFutureCallback extends CompletableFuture implements Callback { - @Override public void onSuccess(Void value) { + @Override + public void onSuccess(Void value) { complete(Unpooled.EMPTY_BUFFER); } - @Override public void onError(Throwable t) { + @Override + public void onError(Throwable t) { completeExceptionally(t); } } diff --git a/collector-grpc/src/test/java/zipkin2/collector/otel/grpc/ITOpenTelemetryGrpcCollector.java b/collector-grpc/src/test/java/zipkin2/collector/otel/grpc/ITOpenTelemetryGrpcCollector.java index c2426f2..22afa14 100644 --- a/collector-grpc/src/test/java/zipkin2/collector/otel/grpc/ITOpenTelemetryGrpcCollector.java +++ b/collector-grpc/src/test/java/zipkin2/collector/otel/grpc/ITOpenTelemetryGrpcCollector.java @@ -13,9 +13,27 @@ */ package zipkin2.collector.otel.grpc; +import static io.opentelemetry.sdk.trace.samplers.Sampler.alwaysOn; +import static org.assertj.core.api.Assertions.assertThat; + +import com.linecorp.armeria.common.SessionProtocol; +import com.linecorp.armeria.server.Server; +import com.linecorp.armeria.server.ServerBuilder; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; +import io.opentelemetry.sdk.trace.export.SpanExporter; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.awaitility.Awaitility; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import zipkin2.collector.CollectorComponent; import zipkin2.collector.CollectorSampler; @@ -29,6 +47,22 @@ class ITOpenTelemetryGrpcCollector { InMemoryCollectorMetrics metrics; CollectorComponent collector; + SpanExporter spanExporter = OtlpGrpcSpanExporter.builder().build(); + + SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder() + .setSampler(alwaysOn()) + .addSpanProcessor(BatchSpanProcessor.builder(spanExporter).build()) + .build(); + + OpenTelemetrySdk openTelemetrySdk = OpenTelemetrySdk.builder() + .setTracerProvider(sdkTracerProvider) + .build(); + + Tracer tracer = openTelemetrySdk.getTracerProvider() + .get("zipkin2.collector.otel.grpc"); + + Server server; + @BeforeEach public void setup() { store = InMemoryStorage.newBuilder().build(); @@ -40,14 +74,42 @@ public void setup() { .storage(store) .build() .start(); + ServerBuilder serverBuilder = Server.builder().http(4317); + ((OpenTelemetryGrpcCollector) collector).reconfigure(serverBuilder); metrics = metrics.forTransport("otel/grpc"); + server = serverBuilder.build(); + server.start().join(); } @AfterEach void teardown() throws IOException { store.close(); collector.close(); + server.stop().join(); } - // TODO: integration test + @Test + void otelGrpcExporterWorksWithZipkinOtelCollector() throws InterruptedException { + List traceIds = new ArrayList<>(); + final int size = 5; + for (int i = 0; i < size; i++) { + // Given + Span span = tracer.spanBuilder("foo " + i) + .setAttribute("foo tag", "foo value") + .setSpanKind(SpanKind.CONSUMER) + .startSpan(); + String traceId = span.getSpanContext().getTraceId(); + System.out.println("Trace Id <" + traceId + ">"); + Thread.sleep(50); + span.addEvent("boom!"); + Thread.sleep(50); + + // When + span.end(); + traceIds.add(traceId); + } + + Awaitility.await().untilAsserted(() -> assertThat(store.acceptedSpanCount()).isEqualTo(5)); + + } } diff --git a/collector-http/pom.xml b/collector-http/pom.xml index 4d122ff..547e009 100644 --- a/collector-http/pom.xml +++ b/collector-http/pom.xml @@ -39,6 +39,11 @@ zipkin-collector ${zipkin.version}
+ + ${project.groupId} + zipkin-encoder-otel + ${project.version} + ${armeria.groupId} @@ -52,11 +57,20 @@ ${zipkin.version} test - - ${armeria.groupId} - armeria-junit5 - ${armeria.version} + org.awaitility + awaitility + ${awaitility.version} + test + + + io.opentelemetry + opentelemetry-sdk + test + + + io.opentelemetry + opentelemetry-exporter-otlp test
diff --git a/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java b/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java index b013ebb..670a2ee 100644 --- a/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java +++ b/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java @@ -13,6 +13,7 @@ */ package zipkin2.collector.otel.http; +import com.google.protobuf.InvalidProtocolBufferException; import com.linecorp.armeria.common.AggregationOptions; import com.linecorp.armeria.common.HttpData; import com.linecorp.armeria.common.HttpRequest; @@ -23,16 +24,24 @@ import com.linecorp.armeria.server.ServerBuilder; import com.linecorp.armeria.server.ServerConfigurator; import com.linecorp.armeria.server.ServiceRequestContext; +import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.ByteBufUtil; +import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; import java.nio.ByteBuffer; +import java.util.List; import java.util.concurrent.CompletableFuture; import zipkin2.Callback; +import zipkin2.Span; import zipkin2.codec.SpanBytesDecoder; +import zipkin2.codec.SpanBytesEncoder; import zipkin2.collector.Collector; import zipkin2.collector.CollectorComponent; import zipkin2.collector.CollectorMetrics; import zipkin2.collector.CollectorSampler; +import zipkin2.internal.ReadBuffer; import zipkin2.storage.StorageComponent; +import zipkin2.translation.zipkin.SpanTranslator; public final class OpenTelemetryHttpCollector extends CollectorComponent implements ServerConfigurator { @@ -126,8 +135,19 @@ protected HttpResponse doPost(ServiceRequestContext ctx, HttpRequest req) return null; } - final ByteBuffer nioBuffer = content.byteBuf().nioBuffer(); - collector.collector.acceptSpans(nioBuffer, SpanBytesDecoder.PROTO3, result, ctx.blockingTaskExecutor()); + ByteBuf byteBuf = content.byteBuf(); + final ByteBuffer nioBuffer = byteBuf.nioBuffer(); + try (ReadBuffer readBuffer = ReadBuffer.wrapUnsafe(nioBuffer)) { + try { + ExportTraceServiceRequest request = ExportTraceServiceRequest.parseFrom( + ByteBufUtil.getBytes(byteBuf)); + List spans = SpanTranslator.translate(request); + byte[] encoded = SpanBytesEncoder.PROTO3.encodeList(spans); + collector.collector.acceptSpans(encoded, result); + } catch (InvalidProtocolBufferException e) { + throw new RuntimeException(e); + } + } return null; } }); diff --git a/collector-http/src/test/java/zipkin2/collector/otel/http/ITOpenTelemetryHttpCollector.java b/collector-http/src/test/java/zipkin2/collector/otel/http/ITOpenTelemetryHttpCollector.java index 94e844c..35bd326 100644 --- a/collector-http/src/test/java/zipkin2/collector/otel/http/ITOpenTelemetryHttpCollector.java +++ b/collector-http/src/test/java/zipkin2/collector/otel/http/ITOpenTelemetryHttpCollector.java @@ -13,9 +13,27 @@ */ package zipkin2.collector.otel.http; +import static io.opentelemetry.sdk.trace.samplers.Sampler.alwaysOn; +import static org.assertj.core.api.Assertions.assertThat; + +import com.linecorp.armeria.server.Server; +import com.linecorp.armeria.server.ServerBuilder; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; +import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; +import io.opentelemetry.sdk.trace.export.SpanExporter; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.awaitility.Awaitility; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import zipkin2.collector.CollectorComponent; import zipkin2.collector.CollectorSampler; @@ -28,6 +46,22 @@ class ITOpenTelemetryHttpCollector { InMemoryCollectorMetrics metrics; CollectorComponent collector; + SpanExporter spanExporter = OtlpHttpSpanExporter.builder().build(); + + SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder() + .setSampler(alwaysOn()) + .addSpanProcessor(BatchSpanProcessor.builder(spanExporter).build()) + .build(); + + OpenTelemetrySdk openTelemetrySdk = OpenTelemetrySdk.builder() + .setTracerProvider(sdkTracerProvider) + .build(); + + Tracer tracer = openTelemetrySdk.getTracerProvider() + .get("zipkin2.collector.otel.http"); + + Server server; + @BeforeEach public void setup() { store = InMemoryStorage.newBuilder().build(); metrics = new InMemoryCollectorMetrics(); @@ -38,13 +72,42 @@ class ITOpenTelemetryHttpCollector { .storage(store) .build() .start(); + ServerBuilder serverBuilder = Server.builder().http(4318); + ((OpenTelemetryHttpCollector) collector).reconfigure(serverBuilder); metrics = metrics.forTransport("otel/http"); + server = serverBuilder.build(); + server.start().join(); } - @AfterEach void teardown() throws IOException { + @AfterEach + void teardown() throws IOException { store.close(); collector.close(); + server.stop().join(); } - // TODO: integration test + @Test + void otelHttpExporterWorksWithZipkinOtelCollector() throws InterruptedException { + List traceIds = new ArrayList<>(); + final int size = 5; + for (int i = 0; i < size; i++) { + // Given + Span span = tracer.spanBuilder("foo " + i) + .setAttribute("foo tag", "foo value") + .setSpanKind(SpanKind.CONSUMER) + .startSpan(); + String traceId = span.getSpanContext().getTraceId(); + System.out.println("Trace Id <" + traceId + ">"); + Thread.sleep(50); + span.addEvent("boom!"); + Thread.sleep(50); + + // When + span.end(); + traceIds.add(traceId); + } + + Awaitility.await().untilAsserted(() -> assertThat(store.acceptedSpanCount()).isEqualTo(5)); + + } } diff --git a/tests/sender-tests/src/test/java/zipkin2/reporter/otel/BasicUsageTest.java b/tests/sender-tests/src/test/java/zipkin2/reporter/otel/BasicUsageTest.java index 88f929d..5218c99 100644 --- a/tests/sender-tests/src/test/java/zipkin2/reporter/otel/BasicUsageTest.java +++ b/tests/sender-tests/src/test/java/zipkin2/reporter/otel/BasicUsageTest.java @@ -30,6 +30,7 @@ import brave.handler.SpanHandler; import brave.propagation.ThreadLocalCurrentTraceContext; import brave.sampler.Sampler; +import java.util.function.Supplier; import okhttp3.OkHttpClient; import okhttp3.OkHttpClient.Builder; import okhttp3.Request; @@ -64,7 +65,7 @@ void shouldSendSpansToOtlpEndpoint(TestSetup testSetup) throws InterruptedExcept ThreadLocalCurrentTraceContext braveCurrentTraceContext = ThreadLocalCurrentTraceContext.newBuilder() .build(); this.testSetup = testSetup; - SpanHandler spanHandler = testSetup.apply(jaegerAllInOne.getHttpOtlpPort()); + SpanHandler spanHandler = testSetup.get(); tracing = Tracing.newBuilder() .currentTraceContext(braveCurrentTraceContext) .supportsJoin(false) @@ -117,86 +118,149 @@ void shouldSendSpansToOtlpEndpoint(TestSetup testSetup) throws InterruptedExcept }); } - enum TestSetup implements Function, Closeable { + @AfterEach + void shutdown() throws IOException { + if (tracing != null) { + tracing.close(); + } + } + static class HttpSenderProvider implements Function, Closeable { - OK_HTTP_OTEL_SENDER { + OkHttpSender okHttpSender; - OkHttpSender okHttpSender; + AsyncReporter reporter; - AsyncReporter reporter; + SpanHandler spanHandler; - SpanHandler spanHandler; + @Override + public void close() { + if (reporter != null) { + reporter.close(); + } + if (okHttpSender != null) { + okHttpSender.close(); + } + } - @Override - public void close() { - if (reporter != null) { - reporter.close(); + @Override + public SpanHandler apply(Integer port) { + okHttpSender = OkHttpSender.newBuilder() + .encoding(Encoding.PROTO3) + .endpoint("http://localhost:" + port + "/v1/traces") + .build(); + OtelEncoder otelEncoder = new OtelEncoder(Tags.ERROR); + reporter = AsyncReporter.builder(okHttpSender).build(otelEncoder); + spanHandler = new SpanHandler() { + @Override + public boolean end(TraceContext context, MutableSpan span, Cause cause) { + reporter.report(span); + return true; } - if (okHttpSender != null) { - okHttpSender.close(); + }; + return spanHandler; + } + } + + static class GrpcSenderProvider implements Function, Closeable { + + OtelGrpcSender otelGrpcSender; + + AsyncReporter reporter; + + SpanHandler spanHandler; + + @Override + public void close() { + if (otelGrpcSender != null) { + otelGrpcSender.close(); + } + } + + @Override + public SpanHandler apply(Integer port) { + otelGrpcSender = OtelGrpcSender.newBuilder(ManagedChannelBuilder + .forAddress("localhost", jaegerAllInOne.getGrpcOtlpPort()) + .usePlaintext() + .build()).build(); + OtelEncoder otelEncoder = new OtelEncoder(Tags.ERROR); + reporter = AsyncReporter.builder(otelGrpcSender).build(otelEncoder); + spanHandler = new SpanHandler() { + @Override + public boolean end(TraceContext context, MutableSpan span, Cause cause) { + reporter.report(span); + return true; } + }; + return spanHandler; + } + + } + + enum TestSetup implements Supplier, Closeable { + + + OK_HTTP_OTEL_SENDER_TO_JAEGER { + + private final HttpSenderProvider httpSenderProvider = new HttpSenderProvider(); + + @Override + public void close() { + httpSenderProvider.close(); } @Override - public SpanHandler apply(Integer port) { - okHttpSender = OkHttpSender.newBuilder() - .encoding(Encoding.PROTO3) - .endpoint("http://localhost:" + port + "/v1/traces") - .build(); - OtelEncoder otelEncoder = new OtelEncoder(Tags.ERROR); - reporter = AsyncReporter.builder(okHttpSender).build(otelEncoder); - spanHandler = new SpanHandler() { - @Override - public boolean end(TraceContext context, MutableSpan span, Cause cause) { - reporter.report(span); - return true; - } - }; - return spanHandler; + public SpanHandler get() { + return httpSenderProvider.apply(jaegerAllInOne.getHttpOtlpPort()); } }, - GRPC_SENDER { + GRPC_SENDER_TO_JAEGER { + + private final GrpcSenderProvider grpcSenderProvider = new GrpcSenderProvider(); + + @Override + public void close() { + grpcSenderProvider.close(); + } + + @Override + public SpanHandler get() { + return grpcSenderProvider.apply(jaegerAllInOne.getGrpcOtlpPort()); + } - OtelGrpcSender otelGrpcSender; + }, - AsyncReporter reporter; + OK_HTTP_OTEL_SENDER_TO_ZIPKIN { - SpanHandler spanHandler; + private final HttpSenderProvider httpSenderProvider = new HttpSenderProvider(); @Override public void close() { - if (otelGrpcSender != null) { - otelGrpcSender.close(); - } + httpSenderProvider.close(); } @Override - public SpanHandler apply(Integer port) { - otelGrpcSender = OtelGrpcSender.newBuilder(ManagedChannelBuilder - .forAddress("localhost", jaegerAllInOne.getGrpcOtlpPort()) - .usePlaintext() - .build()).build(); - OtelEncoder otelEncoder = new OtelEncoder(Tags.ERROR); - reporter = AsyncReporter.builder(otelGrpcSender).build(otelEncoder); - spanHandler = new SpanHandler() { - @Override - public boolean end(TraceContext context, MutableSpan span, Cause cause) { - reporter.report(span); - return true; - } - }; - return spanHandler; + public SpanHandler get() { + return httpSenderProvider.apply(jaegerAllInOne.getHttpOtlpPort()); } + }, - } - } + GRPC_SENDER_TO_ZIPKIN { + + private final GrpcSenderProvider grpcSenderProvider = new GrpcSenderProvider(); + + @Override + public void close() { + grpcSenderProvider.close(); + } + + @Override + public SpanHandler get() { + return grpcSenderProvider.apply(jaegerAllInOne.getGrpcOtlpPort()); + } - @AfterEach - void shutdown() throws IOException { - if (tracing != null) { - tracing.close(); } } + } \ No newline at end of file diff --git a/translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java b/translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java index 493cafa..bbe6677 100644 --- a/translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java +++ b/translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java @@ -14,6 +14,7 @@ package zipkin2.translation.zipkin; import com.google.protobuf.ByteString; +import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; import io.opentelemetry.proto.common.v1.AnyValue; import io.opentelemetry.proto.common.v1.KeyValue; import io.opentelemetry.proto.trace.v1.ResourceSpans; @@ -28,14 +29,17 @@ import io.opentelemetry.semconv.ServerAttributes; import io.opentelemetry.semconv.ServiceAttributes; import io.opentelemetry.semconv.UrlAttributes; +import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import zipkin2.Endpoint; import zipkin2.Span.Kind; /** - * SpanTranslator converts a Zipkin Span to a OpenTelemetry Span. + * SpanTranslator converts a Zipkin Span to a OpenTelemetry Span and vice versa */ public final class SpanTranslator { @@ -77,7 +81,8 @@ public static TracesData translate(zipkin2.Span zipkinSpan) { return tracesDataBuilder.build(); } - private static Span.Builder builderForSingleSpan(zipkin2.Span span, Builder resourceSpansBuilder) { + private static Span.Builder builderForSingleSpan(zipkin2.Span span, + Builder resourceSpansBuilder) { Span.Builder spanBuilder = Span.newBuilder() .setTraceId(ByteString.fromHex(span.traceId())) .setSpanId(ByteString.fromHex(span.id())) @@ -118,8 +123,9 @@ private static Span.Builder builderForSingleSpan(zipkin2.Span span, Builder reso } String localIp = span.localEndpoint() != null ? span.localEndpoint().ipv4() : null; if (localIp != null) { - spanBuilder.addAttributes(KeyValue.newBuilder().setKey(NetworkAttributes.NETWORK_LOCAL_ADDRESS.getKey()) - .setValue(AnyValue.newBuilder().setStringValue(localIp).build()).build()); + spanBuilder.addAttributes( + KeyValue.newBuilder().setKey(NetworkAttributes.NETWORK_LOCAL_ADDRESS.getKey()) + .setValue(AnyValue.newBuilder().setStringValue(localIp).build()).build()); } int localPort = span.localEndpoint() != null ? span.localEndpoint().portAsInt() : 0; if (localPort != 0) { @@ -128,24 +134,236 @@ private static Span.Builder builderForSingleSpan(zipkin2.Span span, Builder reso } String peerName = span.remoteServiceName(); if (peerName != null) { - spanBuilder.addAttributes(KeyValue.newBuilder().setKey(SemanticAttributes.NET_SOCK_PEER_NAME.getKey()) - .setValue(AnyValue.newBuilder().setStringValue(peerName).build()).build()); + spanBuilder.addAttributes( + KeyValue.newBuilder().setKey(SemanticAttributes.NET_SOCK_PEER_NAME.getKey()) + .setValue(AnyValue.newBuilder().setStringValue(peerName).build()).build()); } String peerIp = span.remoteEndpoint() != null ? span.remoteEndpoint().ipv4() : null; if (peerIp != null) { - spanBuilder.addAttributes(KeyValue.newBuilder().setKey(SemanticAttributes.NET_SOCK_PEER_ADDR.getKey()) - .setValue(AnyValue.newBuilder().setStringValue(peerIp).build()).build()); + spanBuilder.addAttributes( + KeyValue.newBuilder().setKey(SemanticAttributes.NET_SOCK_PEER_ADDR.getKey()) + .setValue(AnyValue.newBuilder().setStringValue(peerIp).build()).build()); } int peerPort = span.remoteEndpoint() != null ? span.remoteEndpoint().portAsInt() : 0; if (peerPort != 0) { - spanBuilder.addAttributes(KeyValue.newBuilder().setKey(SemanticAttributes.NET_SOCK_PEER_PORT.getKey()) - .setValue(AnyValue.newBuilder().setIntValue(peerPort).build()).build()); + spanBuilder.addAttributes( + KeyValue.newBuilder().setKey(SemanticAttributes.NET_SOCK_PEER_PORT.getKey()) + .setValue(AnyValue.newBuilder().setIntValue(peerPort).build()).build()); } - span.tags().forEach((key, value) -> ATTRIBUTES_EXTRACTOR.addTag(KeyValue.newBuilder(), key, value)); - span.annotations().forEach(annotation -> spanBuilder.addEventsBuilder().setTimeUnixNano(TimeUnit.MICROSECONDS.toNanos(annotation.timestamp())) + span.tags() + .forEach((key, value) -> ATTRIBUTES_EXTRACTOR.addTag(KeyValue.newBuilder(), key, value)); + span.annotations().forEach(annotation -> spanBuilder.addEventsBuilder() + .setTimeUnixNano(TimeUnit.MICROSECONDS.toNanos(annotation.timestamp())) .setName(annotation.value())); return spanBuilder; } + /** + * Converts OpenTelemetry Spans into Zipkin spans. + * + *

Ex. + * + *

{@code
+   * zipkinSpans = SpanTranslator.translate(exportTraceServiceRequest);
+   * }
+ * + * @param otelSpans The OpenTelemetry Spans. + * @return Zipkin Spans. + */ + public static List translate(ExportTraceServiceRequest otelSpans) { + List spans = new ArrayList<>(); + List spansList = otelSpans.getResourceSpansList(); + for (ResourceSpans resourceSpans : spansList) { + // TODO: Use semantic attributes + KeyValue localServiceName = getValueFromAttributes("service.name", resourceSpans); + KeyValue localIp = getValueFromAttributes("net.host.ip", resourceSpans); + KeyValue localPort = getValueFromAttributes("net.host.port", resourceSpans); + KeyValue peerName = getValueFromAttributes("net.sock.peer.name", resourceSpans); + KeyValue peerIp = getValueFromAttributes("net.sock.peer.addr", resourceSpans); + KeyValue peerPort = getValueFromAttributes("net.sock.peer.port", resourceSpans); + for (ScopeSpans scopeSpans : resourceSpans.getScopeSpansList()) { + for (io.opentelemetry.proto.trace.v1.Span span : scopeSpans.getSpansList()) { + zipkin2.Span.Builder builder = zipkin2.Span.newBuilder(); + builder.name(span.getName()); + builder.traceId(OtelEncodingUtils.traceIdFromBytes(span.getTraceId().toByteArray())); + builder.id(OtelEncodingUtils.spanIdFromBytes(span.getSpanId().toByteArray())); + ByteString parent = span.getParentSpanId(); + if (parent != null) { + builder.parentId(OtelEncodingUtils.spanIdFromBytes(parent.toByteArray())); + } + long startMicros = TimeUnit.NANOSECONDS.toMicros(span.getStartTimeUnixNano()); + builder.timestamp(startMicros); + builder.duration(TimeUnit.NANOSECONDS.toMicros(span.getEndTimeUnixNano()) - startMicros); + SpanKind spanKind = span.getKind(); + switch (spanKind) { + case SPAN_KIND_UNSPECIFIED: + break; + case SPAN_KIND_INTERNAL: + break; + case SPAN_KIND_SERVER: + builder.kind(Kind.SERVER); + break; + case SPAN_KIND_CLIENT: + builder.kind(Kind.CLIENT); + break; + case SPAN_KIND_PRODUCER: + builder.kind(Kind.PRODUCER); + break; + case SPAN_KIND_CONSUMER: + builder.kind(Kind.CONSUMER); + break; + case UNRECOGNIZED: + break; + } + Endpoint.Builder localEndpointBuilder = Endpoint.newBuilder(); + if (localServiceName != null) { + localEndpointBuilder.serviceName(localServiceName.getValue().getStringValue()); + } + if (localPort != null) { + localEndpointBuilder.port((int) localPort.getValue().getIntValue()); + } + if (localIp != null) { + localEndpointBuilder.ip(localIp.getValue().getStringValue()); + } + builder.localEndpoint(localEndpointBuilder.build()); + Endpoint.Builder remoteEndpointBuilder = Endpoint.newBuilder(); + if (peerName != null) { + remoteEndpointBuilder.serviceName(peerName.getValue().getStringValue()); + } + if (peerPort != null) { + remoteEndpointBuilder.port((int) peerPort.getValue().getIntValue()); + } + if (peerIp != null) { + remoteEndpointBuilder.ip(peerIp.getValue().getStringValue()); + } + builder.remoteEndpoint(remoteEndpointBuilder.build()); + // TODO: Remove the ones from above + span.getAttributesList().forEach( + keyValue -> builder.putTag(keyValue.getKey(), keyValue.getValue().getStringValue())); + span.getEventsList().forEach( + event -> builder.addAnnotation(TimeUnit.NANOSECONDS.toMicros(event.getTimeUnixNano()), + event.getName())); + spans.add(builder.shared(false).build()); + } + } + } + return spans; + + } + + private static KeyValue getValueFromAttributes(String key, ResourceSpans resourceSpans) { + return resourceSpans.getResource().getAttributesList().stream() + .filter(keyValue -> keyValue.getKey().equals(key)).findFirst().orElse(null); + } + + /** + * Taken from OpenTelemetry codebase. + */ + static class OtelEncodingUtils { + + private static final String ALPHABET = "0123456789abcdef"; + + private static final char[] ENCODING = buildEncodingArray(); + + private static final String INVALID_TRACE = "00000000000000000000000000000000"; + + private static final int TRACE_BYTES_LENGTH = 16; + + private static final int TRACE_HEX_LENGTH = 2 * TRACE_BYTES_LENGTH; + + private static final int SPAN_BYTES_LENGTH = 8; + + private static final int SPAN_HEX_LENGTH = 2 * SPAN_BYTES_LENGTH; + + private static final String INVALID_SPAN = "0000000000000000"; + + private static char[] buildEncodingArray() { + char[] encoding = new char[512]; + for (int i = 0; i < 256; ++i) { + encoding[i] = ALPHABET.charAt(i >>> 4); + encoding[i | 0x100] = ALPHABET.charAt(i & 0xF); + } + return encoding; + } + + /** + * Fills {@code dest} with the hex encoding of {@code bytes}. + */ + public static void bytesToBase16(byte[] bytes, char[] dest, int length) { + for (int i = 0; i < length; i++) { + byteToBase16(bytes[i], dest, i * 2); + } + } + + /** + * Encodes the specified byte, and returns the encoded {@code String}. + * + * @param value the value to be converted. + * @param dest the destination char array. + * @param destOffset the starting offset in the destination char array. + */ + public static void byteToBase16(byte value, char[] dest, int destOffset) { + int b = value & 0xFF; + dest[destOffset] = ENCODING[b]; + dest[destOffset + 1] = ENCODING[b | 0x100]; + } + + /** + * Returns the lowercase hex (base16) representation of the {@code TraceId} converted from the + * given bytes representation, or {@link #INVALID_TRACE} if input is {@code null} or the given + * byte array is too short. + * + *

It converts the first 26 bytes of the given byte array. + * + * @param traceIdBytes the bytes (16-byte array) representation of the {@code TraceId}. + * @return the lowercase hex (base16) representation of the {@code TraceId}. + */ + static String traceIdFromBytes(byte[] traceIdBytes) { + if (traceIdBytes == null || traceIdBytes.length < TRACE_BYTES_LENGTH) { + return INVALID_TRACE; + } + char[] result = TemporaryBuffers.chars(TRACE_HEX_LENGTH); + OtelEncodingUtils.bytesToBase16(traceIdBytes, result, TRACE_BYTES_LENGTH); + return new String(result, 0, TRACE_HEX_LENGTH); + } + + static String spanIdFromBytes(byte[] spanIdBytes) { + if (spanIdBytes == null || spanIdBytes.length < SPAN_BYTES_LENGTH) { + return INVALID_SPAN; + } + char[] result = TemporaryBuffers.chars(SPAN_HEX_LENGTH); + OtelEncodingUtils.bytesToBase16(spanIdBytes, result, SPAN_BYTES_LENGTH); + return new String(result, 0, SPAN_HEX_LENGTH); + } + + static final class TemporaryBuffers { + + private static final ThreadLocal CHAR_ARRAY = new ThreadLocal<>(); + + /** + * A {@link ThreadLocal} {@code char[]} of size {@code len}. Take care when using a large + * value of {@code len} as this buffer will remain for the lifetime of the thread. The + * returned buffer will not be zeroed and may be larger than the requested size, you must make + * sure to fill the entire content to the desired value and set the length explicitly when + * converting to a {@link String}. + */ + public static char[] chars(int len) { + char[] buffer = CHAR_ARRAY.get(); + if (buffer == null || buffer.length < len) { + buffer = new char[len]; + CHAR_ARRAY.set(buffer); + } + return buffer; + } + + // Visible for testing + static void clearChars() { + CHAR_ARRAY.set(null); + } + + private TemporaryBuffers() { + } + } + } -} +} \ No newline at end of file From 538da4f08259b73fa318a3fb1db7acdf95053078 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 21 Jun 2024 14:02:28 +0200 Subject: [PATCH 06/30] HTTP collector tests passing --- collector-grpc/README.md | 5 - collector-grpc/pom.xml | 83 --------- .../otel/grpc/OpenTelemetryGrpcCollector.java | 158 ---------------- .../grpc/ITOpenTelemetryGrpcCollector.java | 115 ------------ .../grpc/OpenTelemetryGrpcCollectorTest.java | 38 ---- .../otel/http/OpenTelemetryHttpCollector.java | 15 +- ...ipkinOpenTelemetryGrpcCollectorModule.java | 43 ----- ...nOpenTelemetryGrpcCollectorProperties.java | 20 -- ...nOpenTelemetryGrpcCollectorModuleTest.java | 80 -------- pom.xml | 15 -- sender-grpc/pom.xml | 92 ---------- .../AwaitableUnaryClientCallListener.java | 109 ----------- .../reporter/otel/grpc/OtelGrpcSender.java | 168 ----------------- tests/sender-tests/pom.xml | 59 +++++- ...{BasicUsageTest.java => ITBasicUsage.java} | 171 +++++++++--------- 15 files changed, 153 insertions(+), 1018 deletions(-) delete mode 100644 collector-grpc/README.md delete mode 100644 collector-grpc/pom.xml delete mode 100644 collector-grpc/src/main/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollector.java delete mode 100644 collector-grpc/src/test/java/zipkin2/collector/otel/grpc/ITOpenTelemetryGrpcCollector.java delete mode 100644 collector-grpc/src/test/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollectorTest.java delete mode 100644 module/src/main/java/zipkin/module/otel/ZipkinOpenTelemetryGrpcCollectorModule.java delete mode 100644 module/src/main/java/zipkin/module/otel/ZipkinOpenTelemetryGrpcCollectorProperties.java delete mode 100644 module/src/test/java/zipkin/module/otel/ZipkinOpenTelemetryGrpcCollectorModuleTest.java delete mode 100644 sender-grpc/pom.xml delete mode 100644 sender-grpc/src/main/java/zipkin2/reporter/otel/grpc/AwaitableUnaryClientCallListener.java delete mode 100644 sender-grpc/src/main/java/zipkin2/reporter/otel/grpc/OtelGrpcSender.java rename tests/sender-tests/src/test/java/zipkin2/reporter/otel/{BasicUsageTest.java => ITBasicUsage.java} (66%) diff --git a/collector-grpc/README.md b/collector-grpc/README.md deleted file mode 100644 index e8b13b2..0000000 --- a/collector-grpc/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# zipkin-collector-otel-grpc - -This component implements -the [OTLP/gRPC protocol](https://opentelemetry.io/docs/specs/otlp/#otlpgrpc) -with [Armeria](https://armeria.dev/). diff --git a/collector-grpc/pom.xml b/collector-grpc/pom.xml deleted file mode 100644 index 18c81d1..0000000 --- a/collector-grpc/pom.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - 4.0.0 - - - io.zipkin.contrib.otel - zipkin-otel-parent - 0.1.0-SNAPSHOT - ../pom.xml - - - zipkin-collector-otel-grpc - Zipkin Collector: OpenTelemetry gRPC - - - ${project.basedir}/.. - - - - - ${zipkin.groupId} - zipkin-collector - ${zipkin.version} - - - ${project.groupId} - zipkin-encoder-otel - ${project.version} - - - - ${armeria.groupId} - armeria-grpc-protocol - ${armeria.version} - - - - ${zipkin.groupId} - zipkin-tests - ${zipkin.version} - test - - - ${armeria.groupId} - armeria-grpc - ${armeria.version} - test - - - org.awaitility - awaitility - ${awaitility.version} - test - - - io.opentelemetry - opentelemetry-sdk - test - - - io.opentelemetry - opentelemetry-exporter-otlp - test - - - diff --git a/collector-grpc/src/main/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollector.java b/collector-grpc/src/main/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollector.java deleted file mode 100644 index 6c4282d..0000000 --- a/collector-grpc/src/main/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollector.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin2.collector.otel.grpc; - -import com.google.protobuf.InvalidProtocolBufferException; -import com.linecorp.armeria.server.ServerBuilder; -import com.linecorp.armeria.server.ServerConfigurator; -import com.linecorp.armeria.server.ServiceRequestContext; -import com.linecorp.armeria.server.grpc.protocol.AbstractUnsafeUnaryGrpcService; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufUtil; -import io.netty.buffer.Unpooled; -import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import zipkin2.Callback; -import zipkin2.codec.SpanBytesEncoder; -import zipkin2.collector.Collector; -import zipkin2.collector.CollectorComponent; -import zipkin2.collector.CollectorMetrics; -import zipkin2.collector.CollectorSampler; -import zipkin2.internal.ReadBuffer; -import zipkin2.storage.StorageComponent; -import zipkin2.translation.zipkin.SpanTranslator; - -public final class OpenTelemetryGrpcCollector extends CollectorComponent - implements ServerConfigurator { - - public static Builder newBuilder() { - return new Builder(); - } - - public static final class Builder extends CollectorComponent.Builder { - - Collector.Builder delegate = Collector.newBuilder(OpenTelemetryGrpcCollector.class); - CollectorMetrics metrics = CollectorMetrics.NOOP_METRICS; - - @Override - public Builder storage(StorageComponent storageComponent) { - delegate.storage(storageComponent); - return this; - } - - @Override - public Builder metrics(CollectorMetrics metrics) { - if (metrics == null) { - throw new NullPointerException("metrics == null"); - } - delegate.metrics(this.metrics = metrics.forTransport("otel/grpc")); - return this; - } - - @Override - public Builder sampler(CollectorSampler sampler) { - delegate.sampler(sampler); - return this; - } - - @Override - public OpenTelemetryGrpcCollector build() { - return new OpenTelemetryGrpcCollector(this); - } - - Builder() { - } - } - - final Collector collector; - final CollectorMetrics metrics; - - OpenTelemetryGrpcCollector(Builder builder) { - collector = builder.delegate.build(); - metrics = builder.metrics; - } - - @Override - public OpenTelemetryGrpcCollector start() { - return this; - } - - @Override - public String toString() { - return "OpenTelemetryGrpcCollector{}"; - } - - /** - * Reconfigures the service per - * https://github.com/open-telemetry/opentelemetry-proto/blob/v1.0.0/opentelemetry/proto/collector/trace/v1/trace_service.proto - */ - @Override - public void reconfigure(ServerBuilder sb) { - sb.service("/opentelemetry.proto.collector.trace.v1.TraceService/Export", - new HttpService(this)); - } - - static final class HttpService extends AbstractUnsafeUnaryGrpcService { - - final Collector collector; - final CollectorMetrics metrics; - - HttpService(OpenTelemetryGrpcCollector collector) { - this.collector = collector.collector; - this.metrics = collector.metrics; - } - - @Override - protected CompletionStage handleMessage(ServiceRequestContext ctx, ByteBuf bytes) { - metrics.incrementMessages(); - metrics.incrementBytes(bytes.readableBytes()); - - if (!bytes.isReadable()) { - return CompletableFuture.completedFuture(bytes); // lenient on empty messages - } - - try (ReadBuffer readBuffer = ReadBuffer.wrapUnsafe(bytes.nioBuffer())) { - CompletableFutureCallback result = new CompletableFutureCallback(); - try { - ExportTraceServiceRequest request = ExportTraceServiceRequest.parseFrom( - ByteBufUtil.getBytes(bytes)); - List spans = SpanTranslator.translate(request); - byte[] encoded = SpanBytesEncoder.PROTO3.encodeList(spans); - collector.acceptSpans(encoded, result); - return result; - } catch (InvalidProtocolBufferException e) { - throw new RuntimeException(e); - } - } finally { - bytes.release(); - } - } - } - - static final class CompletableFutureCallback extends CompletableFuture - implements Callback { - - @Override - public void onSuccess(Void value) { - complete(Unpooled.EMPTY_BUFFER); - } - - @Override - public void onError(Throwable t) { - completeExceptionally(t); - } - } -} diff --git a/collector-grpc/src/test/java/zipkin2/collector/otel/grpc/ITOpenTelemetryGrpcCollector.java b/collector-grpc/src/test/java/zipkin2/collector/otel/grpc/ITOpenTelemetryGrpcCollector.java deleted file mode 100644 index 22afa14..0000000 --- a/collector-grpc/src/test/java/zipkin2/collector/otel/grpc/ITOpenTelemetryGrpcCollector.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin2.collector.otel.grpc; - -import static io.opentelemetry.sdk.trace.samplers.Sampler.alwaysOn; -import static org.assertj.core.api.Assertions.assertThat; - -import com.linecorp.armeria.common.SessionProtocol; -import com.linecorp.armeria.server.Server; -import com.linecorp.armeria.server.ServerBuilder; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.trace.SdkTracerProvider; -import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; -import io.opentelemetry.sdk.trace.export.SpanExporter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import org.awaitility.Awaitility; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import zipkin2.collector.CollectorComponent; -import zipkin2.collector.CollectorSampler; -import zipkin2.collector.InMemoryCollectorMetrics; -import zipkin2.storage.InMemoryStorage; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class ITOpenTelemetryGrpcCollector { - - InMemoryStorage store; - InMemoryCollectorMetrics metrics; - CollectorComponent collector; - - SpanExporter spanExporter = OtlpGrpcSpanExporter.builder().build(); - - SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder() - .setSampler(alwaysOn()) - .addSpanProcessor(BatchSpanProcessor.builder(spanExporter).build()) - .build(); - - OpenTelemetrySdk openTelemetrySdk = OpenTelemetrySdk.builder() - .setTracerProvider(sdkTracerProvider) - .build(); - - Tracer tracer = openTelemetrySdk.getTracerProvider() - .get("zipkin2.collector.otel.grpc"); - - Server server; - - @BeforeEach - public void setup() { - store = InMemoryStorage.newBuilder().build(); - metrics = new InMemoryCollectorMetrics(); - - collector = OpenTelemetryGrpcCollector.newBuilder() - .metrics(metrics) - .sampler(CollectorSampler.ALWAYS_SAMPLE) - .storage(store) - .build() - .start(); - ServerBuilder serverBuilder = Server.builder().http(4317); - ((OpenTelemetryGrpcCollector) collector).reconfigure(serverBuilder); - metrics = metrics.forTransport("otel/grpc"); - server = serverBuilder.build(); - server.start().join(); - } - - @AfterEach - void teardown() throws IOException { - store.close(); - collector.close(); - server.stop().join(); - } - - @Test - void otelGrpcExporterWorksWithZipkinOtelCollector() throws InterruptedException { - List traceIds = new ArrayList<>(); - final int size = 5; - for (int i = 0; i < size; i++) { - // Given - Span span = tracer.spanBuilder("foo " + i) - .setAttribute("foo tag", "foo value") - .setSpanKind(SpanKind.CONSUMER) - .startSpan(); - String traceId = span.getSpanContext().getTraceId(); - System.out.println("Trace Id <" + traceId + ">"); - Thread.sleep(50); - span.addEvent("boom!"); - Thread.sleep(50); - - // When - span.end(); - traceIds.add(traceId); - } - - Awaitility.await().untilAsserted(() -> assertThat(store.acceptedSpanCount()).isEqualTo(5)); - - } -} diff --git a/collector-grpc/src/test/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollectorTest.java b/collector-grpc/src/test/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollectorTest.java deleted file mode 100644 index 53b33fa..0000000 --- a/collector-grpc/src/test/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollectorTest.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin2.collector.otel.grpc; - -import org.junit.jupiter.api.Test; -import zipkin2.storage.InMemoryStorage; - -import static org.assertj.core.api.Assertions.assertThat; - -class OpenTelemetryGrpcCollectorTest { - OpenTelemetryGrpcCollector collector = OpenTelemetryGrpcCollector.newBuilder() - .storage(InMemoryStorage.newBuilder().build()) - .build(); - - @Test void check_ok() { - assertThat(collector.check().ok()).isTrue(); - } - - /** - * The output of toString() on {@link zipkin2.collector.Collector} implementations appear in the - * /health endpoint. Make sure it is minimal and human-readable. - */ - @Test void toStringContainsOnlyConfigurableFields() { - assertThat(collector.toString()) - .isEqualTo("OpenTelemetryGrpcCollector{}"); - } -} diff --git a/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java b/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java index 670a2ee..f1a5770 100644 --- a/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java +++ b/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java @@ -28,9 +28,12 @@ import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufUtil; import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; +import java.io.IOException; +import java.io.InputStream; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.zip.GZIPInputStream; import zipkin2.Callback; import zipkin2.Span; import zipkin2.codec.SpanBytesDecoder; @@ -139,12 +142,14 @@ protected HttpResponse doPost(ServiceRequestContext ctx, HttpRequest req) final ByteBuffer nioBuffer = byteBuf.nioBuffer(); try (ReadBuffer readBuffer = ReadBuffer.wrapUnsafe(nioBuffer)) { try { - ExportTraceServiceRequest request = ExportTraceServiceRequest.parseFrom( - ByteBufUtil.getBytes(byteBuf)); + InputStream inputStream = readBuffer; + if ("gzip".equals(req.headers().get("content-encoding"))) { + inputStream = new GZIPInputStream(content.toInputStream()); + } + ExportTraceServiceRequest request = ExportTraceServiceRequest.parseFrom(inputStream); List spans = SpanTranslator.translate(request); - byte[] encoded = SpanBytesEncoder.PROTO3.encodeList(spans); - collector.collector.acceptSpans(encoded, result); - } catch (InvalidProtocolBufferException e) { + collector.collector.accept(spans, result); + } catch (IOException e) { throw new RuntimeException(e); } } diff --git a/module/src/main/java/zipkin/module/otel/ZipkinOpenTelemetryGrpcCollectorModule.java b/module/src/main/java/zipkin/module/otel/ZipkinOpenTelemetryGrpcCollectorModule.java deleted file mode 100644 index ae9e950..0000000 --- a/module/src/main/java/zipkin/module/otel/ZipkinOpenTelemetryGrpcCollectorModule.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin.module.otel; - -import com.linecorp.armeria.spring.ArmeriaServerConfigurator; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import zipkin2.collector.CollectorMetrics; -import zipkin2.collector.CollectorSampler; -import zipkin2.collector.otel.grpc.OpenTelemetryGrpcCollector; -import zipkin2.storage.StorageComponent; - -@Configuration -@ConditionalOnProperty(name = "zipkin.collector.otel.grpc.enabled", matchIfMissing = true) -@EnableConfigurationProperties(ZipkinOpenTelemetryGrpcCollectorProperties.class) -class ZipkinOpenTelemetryGrpcCollectorModule { - @Bean OpenTelemetryGrpcCollector otelGrpcCollector(StorageComponent storage, - CollectorSampler sampler, CollectorMetrics metrics) { - return OpenTelemetryGrpcCollector.newBuilder() - .storage(storage) - .sampler(sampler) - .metrics(metrics) - .build(); - } - - @Bean ArmeriaServerConfigurator otelGrpcCollectorConfigurator( - OpenTelemetryGrpcCollector collector) { - return collector::reconfigure; - } -} diff --git a/module/src/main/java/zipkin/module/otel/ZipkinOpenTelemetryGrpcCollectorProperties.java b/module/src/main/java/zipkin/module/otel/ZipkinOpenTelemetryGrpcCollectorProperties.java deleted file mode 100644 index 31a9c4a..0000000 --- a/module/src/main/java/zipkin/module/otel/ZipkinOpenTelemetryGrpcCollectorProperties.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin.module.otel; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties("zipkin.collector.otel.grpc") -public class ZipkinOpenTelemetryGrpcCollectorProperties { -} diff --git a/module/src/test/java/zipkin/module/otel/ZipkinOpenTelemetryGrpcCollectorModuleTest.java b/module/src/test/java/zipkin/module/otel/ZipkinOpenTelemetryGrpcCollectorModuleTest.java deleted file mode 100644 index bc9a07b..0000000 --- a/module/src/test/java/zipkin/module/otel/ZipkinOpenTelemetryGrpcCollectorModuleTest.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin.module.otel; - -import com.linecorp.armeria.spring.ArmeriaServerConfigurator; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import zipkin2.collector.CollectorMetrics; -import zipkin2.collector.CollectorSampler; -import zipkin2.collector.otel.grpc.OpenTelemetryGrpcCollector; -import zipkin2.storage.InMemoryStorage; -import zipkin2.storage.StorageComponent; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; - -class ZipkinOpenTelemetryGrpcCollectorModuleTest { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - - @AfterEach void close() { - context.close(); - } - - @Test void grpcCollector_enabledByDefault() { - context.register( - ZipkinOpenTelemetryGrpcCollectorProperties.class, - ZipkinOpenTelemetryGrpcCollectorModule.class, - InMemoryConfiguration.class - ); - context.refresh(); - - assertThat(context.getBean(OpenTelemetryGrpcCollector.class)).isNotNull(); - assertThat(context.getBean(ArmeriaServerConfigurator.class)).isNotNull(); - } - - @Test void grpcCollector_canDisable() { - assertThatExceptionOfType(NoSuchBeanDefinitionException.class).isThrownBy(() -> { - TestPropertyValues.of("zipkin.collector.otel.grpc.enabled:false").applyTo(context); - context.register( - ZipkinOpenTelemetryGrpcCollectorProperties.class, - ZipkinOpenTelemetryGrpcCollectorModule.class, - InMemoryConfiguration.class - ); - context.refresh(); - - context.getBean(OpenTelemetryGrpcCollector.class); - }); - } - - @Configuration - static class InMemoryConfiguration { - @Bean CollectorSampler sampler() { - return CollectorSampler.ALWAYS_SAMPLE; - } - - @Bean CollectorMetrics metrics() { - return CollectorMetrics.NOOP_METRICS; - } - - @Bean StorageComponent storage() { - return InMemoryStorage.newBuilder().build(); - } - } -} diff --git a/pom.xml b/pom.xml index 03f98b2..71b9048 100644 --- a/pom.xml +++ b/pom.xml @@ -29,8 +29,6 @@ translation-otel encoder-otel-brave encoder-otel-zipkin - sender-grpc - collector-grpc collector-http tests @@ -82,8 +80,6 @@ 1.3.1-alpha 1.25.0-alpha - - 1.63.0 3.25.1 @@ -176,17 +172,6 @@ opentelemetry-semconv ${opentelemetry-semconv.version} - - com.asarkar.grpc - grpc-test - 1.2.2 - - - org.junit.jupiter - * - - - diff --git a/sender-grpc/pom.xml b/sender-grpc/pom.xml deleted file mode 100644 index 3e62191..0000000 --- a/sender-grpc/pom.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - - - io.zipkin.contrib.otel - zipkin-otel-parent - 0.1.0-SNAPSHOT - ../pom.xml - - 4.0.0 - - zipkin-sender-otel-grpc - Zipkin Sender: OpenTelemetry Proto over GRPC - - - ${project.basedir}/.. - - - - - ${project.groupId} - zipkin-translation-otel - ${project.version} - - - io.zipkin.reporter2 - zipkin-reporter - ${zipkin-reporter.version} - - - io.grpc - grpc-core - ${grpc.version} - - - io.grpc - grpc-protobuf - ${grpc.version} - - - - ${zipkin.groupId} - zipkin-tests - ${zipkin.version} - test - - - ${project.groupId} - zipkin-encoder-otel - ${project.version} - test - - - - com.asarkar.grpc - grpc-test - test - - - io.grpc - grpc-inprocess - ${grpc.version} - test - - - io.grpc - grpc-auth - ${grpc.version} - test - - - org.awaitility - awaitility - ${awaitility.version} - test - - - \ No newline at end of file diff --git a/sender-grpc/src/main/java/zipkin2/reporter/otel/grpc/AwaitableUnaryClientCallListener.java b/sender-grpc/src/main/java/zipkin2/reporter/otel/grpc/AwaitableUnaryClientCallListener.java deleted file mode 100644 index fcd44e4..0000000 --- a/sender-grpc/src/main/java/zipkin2/reporter/otel/grpc/AwaitableUnaryClientCallListener.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin2.reporter.otel.grpc; - -import io.grpc.ClientCall; -import io.grpc.Metadata; -import io.grpc.Status; -import java.io.IOException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -/** Blocks until {@link #onClose}. */ -// ported from zipkin2.reporter.internal.AwaitableCallback -final class AwaitableUnaryClientCallListener extends ClientCall.Listener { - final CountDownLatch countDown = new CountDownLatch(1); - /** this differentiates between not yet set and null */ - boolean resultSet; // guarded by this - - Object result; // guarded by this - - long serverTimeoutMs; // how long to wait for server response in milliseconds - - AwaitableUnaryClientCallListener(long serverTimeoutMs) { - if (serverTimeoutMs <= 0) { - throw new IllegalArgumentException("Server response timeout must be greater than 0"); - } - this.serverTimeoutMs = serverTimeoutMs; - } - - /** - * Blocks until {@link #onClose}. Throws if no value was received, multiple - * values were received, there was a status error, or waited longer than {@link #serverTimeoutMs}. - */ - V await() throws IOException { - boolean interrupted = false; - try { - while (true) { - try { - if (!countDown.await(serverTimeoutMs, TimeUnit.MILLISECONDS)) { - throw new IllegalStateException( - "timeout waiting for onClose. timeoutMs=" + serverTimeoutMs - + ", resultSet=" + resultSet); - } - Object result; - synchronized (this) { - if (!resultSet) continue; - result = this.result; - } - if (result instanceof Throwable) { - if (result instanceof Error) throw (Error) result; - if (result instanceof IOException) throw (IOException) result; - if (result instanceof RuntimeException) throw (RuntimeException) result; - // Don't set interrupted status when the callback received InterruptedException - throw new RuntimeException((Throwable) result); - } - return (V) result; - } catch (InterruptedException e) { - interrupted = true; - } - } - } finally { - if (interrupted) { - Thread.currentThread().interrupt(); - } - } - } - - @Override - public void onHeaders(Metadata headers) { - } - - @Override - public synchronized void onMessage(V value) { - if (resultSet) { - throw Status.INTERNAL - .withDescription("More than one value received for unary call") - .asRuntimeException(); - } - result = value; - resultSet = true; - } - - @Override - public synchronized void onClose(Status status, Metadata trailers) { - if (status.isOk()) { - if (!resultSet) { - result = - Status.INTERNAL - .withDescription("No value received for unary call") - .asRuntimeException(trailers); - } - } else { - result = status.asRuntimeException(trailers); - } - resultSet = true; - countDown.countDown(); - } -} diff --git a/sender-grpc/src/main/java/zipkin2/reporter/otel/grpc/OtelGrpcSender.java b/sender-grpc/src/main/java/zipkin2/reporter/otel/grpc/OtelGrpcSender.java deleted file mode 100644 index 77b8e22..0000000 --- a/sender-grpc/src/main/java/zipkin2/reporter/otel/grpc/OtelGrpcSender.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin2.reporter.otel.grpc; - -import static io.grpc.CallOptions.DEFAULT; -import static io.grpc.MethodDescriptor.generateFullMethodName; - -import io.grpc.CallOptions; -import io.grpc.Channel; -import io.grpc.ClientCall; -import io.grpc.ManagedChannel; -import io.grpc.Metadata; -import io.grpc.MethodDescriptor; -import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse; -import io.opentelemetry.proto.trace.v1.TracesData; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; -import zipkin2.reporter.BytesMessageSender; -import zipkin2.reporter.ClosedSenderException; -import zipkin2.reporter.Encoding; - -public final class OtelGrpcSender extends BytesMessageSender.Base { - static final int DEFAULT_SERVER_TIMEOUT_MS = 5000; - - public static Builder newBuilder(Channel channel) { // visible for testing - return new Builder(channel); - } - - public static final class Builder { - final Channel channel; - CallOptions callOptions = DEFAULT; - boolean shutdownChannelOnClose; - long serverResponseTimeoutMs = DEFAULT_SERVER_TIMEOUT_MS; - - Builder(Channel channel) { - if (channel == null) throw new NullPointerException("channel == null"); - this.channel = channel; - } - - public Builder callOptions(CallOptions callOptions) { - if (callOptions == null) throw new NullPointerException("callOptions == null"); - this.callOptions = callOptions; - return this; - } - - public Builder serverResponseTimeoutMs(long serverResponseTimeoutMs) { - if (serverResponseTimeoutMs <= 0) { - throw new IllegalArgumentException("Server response timeout must be greater than 0"); - } - this.serverResponseTimeoutMs = serverResponseTimeoutMs; - return this; - } - - public OtelGrpcSender build() { - return new OtelGrpcSender(this); - } - } - - final Channel channel; - final CallOptions callOptions; - final boolean shutdownChannelOnClose; - final long serverResponseTimeoutMs; - - OtelGrpcSender(Builder builder) { - super(Encoding.PROTO3); - channel = builder.channel; - callOptions = builder.callOptions; - serverResponseTimeoutMs = builder.serverResponseTimeoutMs; - shutdownChannelOnClose = builder.shutdownChannelOnClose; - } - - @Override public int messageMaxBytes() { - return 1024 * 1024; // 1 MiB for now - } - - /** close is typically called from a different thread */ - volatile boolean closeCalled; - - private static final String SERVICE_NAME = "opentelemetry.proto.collector.trace.v1.TraceService"; - - private static final MethodDescriptor.Marshaller REQUEST_MARSHALLER = - new MethodDescriptor.Marshaller() { - @Override - public InputStream stream(TracesData value) { - return value.toByteString().newInput(); - } - - @Override - public TracesData parse(InputStream stream) { - throw new UnsupportedOperationException("Only for serializing"); - } - }; - - private static final MethodDescriptor.Marshaller RESPONSE_MARSHALER = - new MethodDescriptor.Marshaller() { - @Override - public InputStream stream(ExportTraceServiceResponse value) { - throw new UnsupportedOperationException("Only for parsing"); - } - - @Override - public ExportTraceServiceResponse parse(InputStream stream) { - try { - return ExportTraceServiceResponse.parseFrom(stream); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - }; - - private static final io.grpc.MethodDescriptor - getExportMethod = - io.grpc.MethodDescriptor.newBuilder() - .setType(io.grpc.MethodDescriptor.MethodType.UNARY) - .setFullMethodName(generateFullMethodName(SERVICE_NAME, "Export")) - .setRequestMarshaller(REQUEST_MARSHALLER) - .setResponseMarshaller(RESPONSE_MARSHALER) - .build(); - - @Override public void send(List encodedSpans) throws IOException { - if (closeCalled) throw new ClosedSenderException(); - - TracesData.Builder request = TracesData.newBuilder(); - - for (byte[] encodedSpan : encodedSpans) { - request.mergeFrom(TracesData.parseFrom(encodedSpan)); - } - - ClientCall call = - channel.newCall(getExportMethod, callOptions); - - AwaitableUnaryClientCallListener listener = - new AwaitableUnaryClientCallListener<>(serverResponseTimeoutMs); - try { - call.start(listener, new Metadata()); - call.request(1); - call.sendMessage(request.build()); - call.halfClose(); - } catch (RuntimeException | Error t) { - call.cancel(null, t); - throw t; - } - listener.await(); - } - - @Override public String toString() { - return "OtelGrpcSender{}"; - } - - @Override public void close() { - if (!shutdownChannelOnClose) return; - if (closeCalled) return; - closeCalled = true; - ((ManagedChannel) channel).shutdownNow(); - } -} diff --git a/tests/sender-tests/pom.xml b/tests/sender-tests/pom.xml index f6a665b..47e15ab 100644 --- a/tests/sender-tests/pom.xml +++ b/tests/sender-tests/pom.xml @@ -52,6 +52,11 @@ brave-encoder-otel ${project.version} + + ${project.groupId} + zipkin-module-otel + ${project.version} + ${project.groupId} zipkin-sender-otel-grpc @@ -68,6 +73,12 @@ testcontainers ${testcontainers.version} test + + + org.slf4j + * + + org.testcontainers @@ -75,6 +86,12 @@ ${testcontainers.version} test + + com.fasterxml.jackson.core + jackson-annotations + 2.17.0 + test + org.awaitility awaitility @@ -82,10 +99,46 @@ test - io.grpc - grpc-okhttp - ${grpc.version} + org.springframework.boot + spring-boot-autoconfigure + ${spring-boot.version} + test + + + org.springframework.boot + spring-boot-starter-web + ${spring-boot.version} test + + ${armeria.groupId} + armeria-spring-boot2-autoconfigure + ${armeria.version} + test + + + org.slf4j + * + + + + + org.springframework.boot + spring-boot-test + ${spring-boot.version} + test + + + io.zipkin + zipkin-server + ${zipkin.version} + test + + + org.springframework.boot + spring-boot-starter-log4j2 + + + diff --git a/tests/sender-tests/src/test/java/zipkin2/reporter/otel/BasicUsageTest.java b/tests/sender-tests/src/test/java/zipkin2/reporter/otel/ITBasicUsage.java similarity index 66% rename from tests/sender-tests/src/test/java/zipkin2/reporter/otel/BasicUsageTest.java rename to tests/sender-tests/src/test/java/zipkin2/reporter/otel/ITBasicUsage.java index 5218c99..348d162 100644 --- a/tests/sender-tests/src/test/java/zipkin2/reporter/otel/BasicUsageTest.java +++ b/tests/sender-tests/src/test/java/zipkin2/reporter/otel/ITBasicUsage.java @@ -13,23 +13,21 @@ */ package zipkin2.reporter.otel; +import brave.Span; +import brave.Span.Kind; import brave.Tags; +import brave.Tracer; +import brave.Tracing; import brave.handler.MutableSpan; +import brave.handler.SpanHandler; +import brave.propagation.ThreadLocalCurrentTraceContext; import brave.propagation.TraceContext; -import io.grpc.ManagedChannelBuilder; +import brave.sampler.Sampler; import java.io.Closeable; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.function.Function; - -import brave.Span; -import brave.Span.Kind; -import brave.Tracer; -import brave.Tracing; -import brave.handler.SpanHandler; -import brave.propagation.ThreadLocalCurrentTraceContext; -import brave.sampler.Sampler; import java.util.function.Supplier; import okhttp3.OkHttpClient; import okhttp3.OkHttpClient.Builder; @@ -40,16 +38,22 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.test.util.TestSocketUtils; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; +import zipkin.module.otel.ZipkinOpenTelemetryHttpCollectorProperties; +import zipkin.server.ZipkinServer; import zipkin2.reporter.AsyncReporter; import zipkin2.reporter.Encoding; import zipkin2.reporter.okhttp3.OkHttpSender; import zipkin2.reporter.otel.brave.OtelEncoder; -import zipkin2.reporter.otel.grpc.OtelGrpcSender; -@Testcontainers -class BasicUsageTest { +@Testcontainers(disabledWithoutDocker = true) +class ITBasicUsage { @Container static JaegerAllInOne jaegerAllInOne = new JaegerAllInOne(); @@ -63,17 +67,20 @@ class BasicUsageTest { void shouldSendSpansToOtlpEndpoint(TestSetup testSetup) throws InterruptedException, IOException { // Setup ThreadLocalCurrentTraceContext braveCurrentTraceContext = ThreadLocalCurrentTraceContext.newBuilder() - .build(); + .build(); this.testSetup = testSetup; + + testSetup.setup(); + SpanHandler spanHandler = testSetup.get(); tracing = Tracing.newBuilder() - .currentTraceContext(braveCurrentTraceContext) - .supportsJoin(false) - .traceId128Bit(true) - .sampler(Sampler.ALWAYS_SAMPLE) - .addSpanHandler(spanHandler) - .localServiceName("my-service") - .build(); + .currentTraceContext(braveCurrentTraceContext) + .supportsJoin(false) + .traceId128Bit(true) + .sampler(Sampler.ALWAYS_SAMPLE) + .addSpanHandler(spanHandler) + .localServiceName("my-service") + .build(); Tracer braveTracer = tracing.tracer(); List traceIds = new ArrayList<>(); @@ -81,11 +88,11 @@ void shouldSendSpansToOtlpEndpoint(TestSetup testSetup) throws InterruptedExcept for (int i = 0; i < size; i++) { // Given Span span = braveTracer.nextSpan().name("foo " + i) - .tag("foo tag", "foo value") - .kind(Kind.CONSUMER) - .error(new RuntimeException("BOOOOOM!")) - .remoteServiceName("remote service") - .start(); + .tag("foo tag", "foo value") + .kind(Kind.CONSUMER) + .error(new RuntimeException("BOOOOOM!")) + .remoteServiceName("remote service") + .start(); String traceId = span.context().traceIdString(); System.out.println("Trace Id <" + traceId + ">"); span.remoteIpAndPort("http://localhost", 123456); @@ -98,24 +105,26 @@ void shouldSendSpansToOtlpEndpoint(TestSetup testSetup) throws InterruptedExcept traceIds.add(span.context().traceIdString()); } - testSetup.close(); + testSetup.flush(); // Then Awaitility.await().untilAsserted(() -> { BDDAssertions.then(traceIds).hasSize(size); OkHttpClient client = new Builder() - .build(); + .build(); + traceIds.forEach(traceId -> { - Request request = new Request.Builder().url("http://localhost:" + jaegerAllInOne.getQueryPort() + "/api/traces/" + traceId).build(); + Request request = new Request.Builder().url(testSetup.queryUrl() + traceId).build(); try (Response response = client.newCall(request).execute()) { BDDAssertions.then(response.isSuccessful()).isTrue(); - } - catch (IOException e) { - throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); } }); }); + + testSetup.close(); } @AfterEach @@ -160,44 +169,22 @@ public boolean end(TraceContext context, MutableSpan span, Cause cause) { }; return spanHandler; } + + void flush() { + reporter.flush(); + } } - static class GrpcSenderProvider implements Function, Closeable { + interface TraceSetup { - OtelGrpcSender otelGrpcSender; + void setup(); - AsyncReporter reporter; - - SpanHandler spanHandler; - - @Override - public void close() { - if (otelGrpcSender != null) { - otelGrpcSender.close(); - } - } - - @Override - public SpanHandler apply(Integer port) { - otelGrpcSender = OtelGrpcSender.newBuilder(ManagedChannelBuilder - .forAddress("localhost", jaegerAllInOne.getGrpcOtlpPort()) - .usePlaintext() - .build()).build(); - OtelEncoder otelEncoder = new OtelEncoder(Tags.ERROR); - reporter = AsyncReporter.builder(otelGrpcSender).build(otelEncoder); - spanHandler = new SpanHandler() { - @Override - public boolean end(TraceContext context, MutableSpan span, Cause cause) { - reporter.report(span); - return true; - } - }; - return spanHandler; - } + String queryUrl(); + void flush(); } - enum TestSetup implements Supplier, Closeable { + enum TestSetup implements Supplier, TraceSetup, Closeable { OK_HTTP_OTEL_SENDER_TO_JAEGER { @@ -205,62 +192,78 @@ enum TestSetup implements Supplier, Closeable { private final HttpSenderProvider httpSenderProvider = new HttpSenderProvider(); @Override - public void close() { - httpSenderProvider.close(); + public void setup() { + } @Override - public SpanHandler get() { - return httpSenderProvider.apply(jaegerAllInOne.getHttpOtlpPort()); + public String queryUrl() { + return "http://localhost:" + jaegerAllInOne.getQueryPort() + "/api/traces/"; } - }, - - GRPC_SENDER_TO_JAEGER { - private final GrpcSenderProvider grpcSenderProvider = new GrpcSenderProvider(); + @Override + public void flush() { + httpSenderProvider.flush(); + } @Override public void close() { - grpcSenderProvider.close(); + httpSenderProvider.close(); } @Override public SpanHandler get() { - return grpcSenderProvider.apply(jaegerAllInOne.getGrpcOtlpPort()); + return httpSenderProvider.apply(jaegerAllInOne.getHttpOtlpPort()); } - }, + // Why is it so slow? OK_HTTP_OTEL_SENDER_TO_ZIPKIN { private final HttpSenderProvider httpSenderProvider = new HttpSenderProvider(); + private ConfigurableApplicationContext ctx; + + private final int port = TestSocketUtils.findAvailableTcpPort(); + @Override - public void close() { - httpSenderProvider.close(); + public void setup() { + ctx = new SpringApplicationBuilder(Config.class) + .web(WebApplicationType.NONE) + .run("--spring.main.allow-bean-definition-overriding=true", "--server.port=0", + "--armeria.ports[0].port=" + port, "--logging.level.zipkin2=trace", + "--logging.level.com.linecorp=debug"); } + @Override - public SpanHandler get() { - return httpSenderProvider.apply(jaegerAllInOne.getHttpOtlpPort()); + public String queryUrl() { + return "http://localhost:" + port + "/api/v2/trace/"; } - }, - - GRPC_SENDER_TO_ZIPKIN { - private final GrpcSenderProvider grpcSenderProvider = new GrpcSenderProvider(); + @Override + public void flush() { + httpSenderProvider.flush(); + } @Override public void close() { - grpcSenderProvider.close(); + httpSenderProvider.close(); + if (ctx != null) { + ctx.close(); + } } @Override public SpanHandler get() { - return grpcSenderProvider.apply(jaegerAllInOne.getGrpcOtlpPort()); + return httpSenderProvider.apply(port); } - } } + @SpringBootApplication(scanBasePackageClasses = {ZipkinOpenTelemetryHttpCollectorProperties.class, + ZipkinServer.class}) + static class Config { + + } } \ No newline at end of file From a8101841e42c83b9024fc332c6ded07f38b4a02b Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 24 Jun 2024 11:41:31 +0200 Subject: [PATCH 07/30] Added OTel HTTP exporter test --- tests/sender-tests/pom.xml | 87 ++-- .../zipkin2/reporter/otel/ITBasicUsage.java | 421 ++++++++++++------ .../zipkin2/reporter/otel/JaegerAllInOne.java | 14 +- 3 files changed, 350 insertions(+), 172 deletions(-) diff --git a/tests/sender-tests/pom.xml b/tests/sender-tests/pom.xml index 47e15ab..f150b6b 100644 --- a/tests/sender-tests/pom.xml +++ b/tests/sender-tests/pom.xml @@ -51,21 +51,72 @@ ${project.groupId} brave-encoder-otel ${project.version} + test ${project.groupId} zipkin-module-otel ${project.version} + test ${project.groupId} zipkin-sender-otel-grpc ${project.version} + test io.zipkin.reporter2 zipkin-sender-okhttp3 ${zipkin-reporter.version} + test + + + org.springframework.boot + spring-boot-autoconfigure + ${spring-boot.version} + test + + + org.springframework.boot + spring-boot-starter-web + ${spring-boot.version} + test + + + ${armeria.groupId} + armeria-spring-boot2-autoconfigure + ${armeria.version} + + + org.slf4j + * + + + test + + + io.zipkin + zipkin-server + ${zipkin.version} + + + org.springframework.boot + spring-boot-starter-log4j2 + + + test + + + io.opentelemetry + opentelemetry-sdk + test + + + + io.opentelemetry + opentelemetry-exporter-otlp + test @@ -98,47 +149,11 @@ ${awaitility.version} test - - org.springframework.boot - spring-boot-autoconfigure - ${spring-boot.version} - test - - - org.springframework.boot - spring-boot-starter-web - ${spring-boot.version} - test - - - ${armeria.groupId} - armeria-spring-boot2-autoconfigure - ${armeria.version} - test - - - org.slf4j - * - - - org.springframework.boot spring-boot-test ${spring-boot.version} test - - io.zipkin - zipkin-server - ${zipkin.version} - test - - - org.springframework.boot - spring-boot-starter-log4j2 - - - diff --git a/tests/sender-tests/src/test/java/zipkin2/reporter/otel/ITBasicUsage.java b/tests/sender-tests/src/test/java/zipkin2/reporter/otel/ITBasicUsage.java index 348d162..640c4df 100644 --- a/tests/sender-tests/src/test/java/zipkin2/reporter/otel/ITBasicUsage.java +++ b/tests/sender-tests/src/test/java/zipkin2/reporter/otel/ITBasicUsage.java @@ -17,18 +17,27 @@ import brave.Span.Kind; import brave.Tags; import brave.Tracer; +import brave.Tracer.SpanInScope; import brave.Tracing; import brave.handler.MutableSpan; import brave.handler.SpanHandler; import brave.propagation.ThreadLocalCurrentTraceContext; import brave.propagation.TraceContext; import brave.sampler.Sampler; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; import java.io.Closeable; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.function.Function; -import java.util.function.Supplier; import okhttp3.OkHttpClient; import okhttp3.OkHttpClient.Builder; import okhttp3.Request; @@ -36,8 +45,12 @@ import org.assertj.core.api.BDDAssertions; import org.awaitility.Awaitility; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; @@ -52,180 +65,212 @@ import zipkin2.reporter.okhttp3.OkHttpSender; import zipkin2.reporter.otel.brave.OtelEncoder; -@Testcontainers(disabledWithoutDocker = true) class ITBasicUsage { - @Container - static JaegerAllInOne jaegerAllInOne = new JaegerAllInOne(); + private static final Logger log = LoggerFactory.getLogger(ITBasicUsage.class); - TestSetup testSetup; + private static final int EXPECTED_TRACE_SIZE = 5; - Tracing tracing; + @Nested + class ZipkinCollectorTests { - @ParameterizedTest - @EnumSource(TestSetup.class) - void shouldSendSpansToOtlpEndpoint(TestSetup testSetup) throws InterruptedException, IOException { - // Setup - ThreadLocalCurrentTraceContext braveCurrentTraceContext = ThreadLocalCurrentTraceContext.newBuilder() - .build(); - this.testSetup = testSetup; - - testSetup.setup(); - - SpanHandler spanHandler = testSetup.get(); - tracing = Tracing.newBuilder() - .currentTraceContext(braveCurrentTraceContext) - .supportsJoin(false) - .traceId128Bit(true) - .sampler(Sampler.ALWAYS_SAMPLE) - .addSpanHandler(spanHandler) - .localServiceName("my-service") - .build(); - Tracer braveTracer = tracing.tracer(); - - List traceIds = new ArrayList<>(); - final int size = 5; - for (int i = 0; i < size; i++) { - // Given - Span span = braveTracer.nextSpan().name("foo " + i) - .tag("foo tag", "foo value") - .kind(Kind.CONSUMER) - .error(new RuntimeException("BOOOOOM!")) - .remoteServiceName("remote service") - .start(); - String traceId = span.context().traceIdString(); - System.out.println("Trace Id <" + traceId + ">"); - span.remoteIpAndPort("http://localhost", 123456); - Thread.sleep(50); - span.annotate("boom!"); - Thread.sleep(50); - - // When - span.finish(); - traceIds.add(span.context().traceIdString()); - } + TestingScenario testingScenario; - testSetup.flush(); + @ParameterizedTest + @EnumSource(TestingScenario.class) + void shouldSendOtlpHttpSpansToOtlpEndpoint(TestingScenario testingScenario) throws Exception { + // Setup + this.testingScenario = testingScenario; + testingScenario.setup(); - // Then - Awaitility.await().untilAsserted(() -> { - BDDAssertions.then(traceIds).hasSize(size); - OkHttpClient client = new Builder() - .build(); + List traceIds = testingScenario.exportedTraceIds(); - traceIds.forEach(traceId -> { - Request request = new Request.Builder().url(testSetup.queryUrl() + traceId).build(); - try (Response response = client.newCall(request).execute()) { - BDDAssertions.then(response.isSuccessful()).isTrue(); - } catch (IOException e) { - throw new RuntimeException(e); - } + // Then + Awaitility.await().untilAsserted(() -> { + BDDAssertions.then(traceIds).hasSize(EXPECTED_TRACE_SIZE); + thenAllTraceIdsPresentInBackend(testingScenario.queryUrl(), traceIds); }); + } - }); - - testSetup.close(); + @AfterEach + void shutdown() throws IOException { + testingScenario.close(); + } } - @AfterEach - void shutdown() throws IOException { - if (tracing != null) { - tracing.close(); + @Nested + @Testcontainers(disabledWithoutDocker = true) + class ZipkinSenderTests { + + @Container + JaegerAllInOne jaegerAllInOne = new JaegerAllInOne(); + + JaegerTestingScenario testingScenario; + + @Test + void shouldSendBraveSpansToJaegerOtlpEndpoint() throws Exception { + // Setup + testingScenario = new JaegerTestingScenario(jaegerAllInOne.getHttpOtlpPort()); + testingScenario.setup(); + + List traceIds = testingScenario.exportedTraceIds(); + + // Then + Awaitility.await().untilAsserted(() -> { + BDDAssertions.then(traceIds).hasSize(EXPECTED_TRACE_SIZE); + thenAllTraceIdsPresentInBackend(testingScenario.queryUrl(), traceIds); + }); } - } - static class HttpSenderProvider implements Function, Closeable { + /** + * Sender: Brave OKHttp with OTLP proto over HTTP ; Receiver: Jaeger OTLP + */ + class JaegerTestingScenario implements ScenarioSetup { - OkHttpSender okHttpSender; + private final BraveTraceIdGenerator braveTraceIdGenerator; - AsyncReporter reporter; + JaegerTestingScenario(int otlpPort) { + this.braveTraceIdGenerator = new BraveTraceIdGenerator(otlpPort); + } - SpanHandler spanHandler; + @Override + public String queryUrl() { + return "http://localhost:" + jaegerAllInOne.getQueryPort() + "/api/traces/"; + } - @Override - public void close() { - if (reporter != null) { - reporter.close(); + @Override + public List exportedTraceIds() throws Exception { + return braveTraceIdGenerator.traceIds(); } - if (okHttpSender != null) { - okHttpSender.close(); + + @Override + public void close() { + braveTraceIdGenerator.close(); } } + } - @Override - public SpanHandler apply(Integer port) { - okHttpSender = OkHttpSender.newBuilder() - .encoding(Encoding.PROTO3) - .endpoint("http://localhost:" + port + "/v1/traces") - .build(); - OtelEncoder otelEncoder = new OtelEncoder(Tags.ERROR); - reporter = AsyncReporter.builder(okHttpSender).build(otelEncoder); - spanHandler = new SpanHandler() { - @Override - public boolean end(TraceContext context, MutableSpan span, Cause cause) { - reporter.report(span); - return true; - } - }; - return spanHandler; - } + private static void thenAllTraceIdsPresentInBackend(String queryUrl, List traceIds) { + OkHttpClient client = new Builder() + .build(); - void flush() { - reporter.flush(); - } + traceIds.forEach(traceId -> { + Request request = new Request.Builder().url(queryUrl + traceId).build(); + try (Response response = client.newCall(request).execute()) { + BDDAssertions.then(response.isSuccessful()).isTrue(); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); } - interface TraceSetup { - void setup(); + interface ScenarioSetup extends Closeable { + + /** + * Code to be run before tests are executed. + */ + default void setup() { + + } + /** + * URL from which trace data can be collected. + * + * @return query URL + */ String queryUrl(); - void flush(); + /** + * Actual testing code that will create spans and send them to the backend. + * + * @return list of generated trace ids + * @throws Exception exception + */ + List exportedTraceIds() throws Exception; + } - enum TestSetup implements Supplier, TraceSetup, Closeable { + enum TestingScenario implements ScenarioSetup { + // TODO: Why is it so slow? + /** + * Sender: Brave OKHttp with OTLP proto over HTTP ; Receiver: Zipkin OTLP + */ + BRAVE_OK_HTTP_OTLP_SENDER_TO_ZIPKIN_OLTP { - OK_HTTP_OTEL_SENDER_TO_JAEGER { + private ConfigurableApplicationContext ctx; - private final HttpSenderProvider httpSenderProvider = new HttpSenderProvider(); + private final int port = TestSocketUtils.findAvailableTcpPort(); + + private final BraveTraceIdGenerator braveTraceIdGenerator = new BraveTraceIdGenerator(port); @Override public void setup() { - + ctx = new SpringApplicationBuilder(Config.class) + .web(WebApplicationType.NONE) + .run("--spring.main.allow-bean-definition-overriding=true", "--server.port=0", + "--armeria.ports[0].port=" + port, "--logging.level.zipkin2=trace", + "--logging.level.com.linecorp=debug"); } + @Override public String queryUrl() { - return "http://localhost:" + jaegerAllInOne.getQueryPort() + "/api/traces/"; + return "http://localhost:" + port + "/api/v2/trace/"; } @Override - public void flush() { - httpSenderProvider.flush(); + public List exportedTraceIds() throws Exception { + return braveTraceIdGenerator.traceIds(); } @Override public void close() { - httpSenderProvider.close(); - } - - @Override - public SpanHandler get() { - return httpSenderProvider.apply(jaegerAllInOne.getHttpOtlpPort()); + braveTraceIdGenerator.close(); + if (ctx != null) { + ctx.close(); + } } }, - // Why is it so slow? - OK_HTTP_OTEL_SENDER_TO_ZIPKIN { - - private final HttpSenderProvider httpSenderProvider = new HttpSenderProvider(); + /** + * Sender: OpenTelemetry OTLP proto over HTTP Exporter ; Receiver: Zipkin OTLP + */ + OTEL_OTLP_EXPORTER_TO_ZIPKIN_OTLP { private ConfigurableApplicationContext ctx; private final int port = TestSocketUtils.findAvailableTcpPort(); + private final OpenTelemetry openTelemetry = initOpenTelemetry(port); + + /** + * Initialize OpenTelemetry. + * + * @return a ready-to-use {@link OpenTelemetry} instance. + */ + OpenTelemetry initOpenTelemetry(int port) { + OpenTelemetrySdk openTelemetrySdk = + OpenTelemetrySdk.builder() + .setTracerProvider( + SdkTracerProvider.builder() + .addSpanProcessor( + BatchSpanProcessor.builder( + OtlpHttpSpanExporter.builder() + .setEndpoint("http://localhost:" + port + "/v1/traces") + .setTimeout(2, TimeUnit.SECONDS) + .build()) + .setScheduleDelay(100, TimeUnit.MILLISECONDS) + .build()) + .build()) + .buildAndRegisterGlobal(); + + Runtime.getRuntime().addShutdownHook(new Thread(openTelemetrySdk::close)); + + return openTelemetrySdk; + } + @Override public void setup() { ctx = new SpringApplicationBuilder(Config.class) @@ -242,22 +287,36 @@ public String queryUrl() { } @Override - public void flush() { - httpSenderProvider.flush(); + public List exportedTraceIds() throws Exception { + io.opentelemetry.api.trace.Tracer tracer = openTelemetry.getTracer( + "io.opentelemetry.example"); + List traceIds = new ArrayList<>(); + for (int i = 0; i < EXPECTED_TRACE_SIZE; i++) { + io.opentelemetry.api.trace.Span span = tracer.spanBuilder("foo " + i) + .setAttribute("foo tag", "foo value") + .setSpanKind(SpanKind.CONSUMER) + .startSpan() + .recordException(new RuntimeException("BOOOOOM!")); + String traceId = span.getSpanContext().getTraceId(); + traceIds.add(traceId); + try (Scope scope = Context.current().with(span).makeCurrent()) { + log.info("Trace Id <" + traceId + ">"); + Thread.sleep(50); + span.addEvent("boom!"); + Thread.sleep(50); + } finally { + span.end(); + } + } + return traceIds; } @Override public void close() { - httpSenderProvider.close(); if (ctx != null) { ctx.close(); } } - - @Override - public SpanHandler get() { - return httpSenderProvider.apply(port); - } } } @@ -266,4 +325,112 @@ public SpanHandler get() { static class Config { } + + /** + * Actual testing logic that uses Brave to generate spans and send them to the backend. + */ + static class BraveTraceIdGenerator implements Closeable { + + private final BraveHttpSenderProvider braveHttpSenderProvider = new BraveHttpSenderProvider(); + + private final int port; + + Tracing tracing; + + BraveTraceIdGenerator(int port) { + this.port = port; + } + + List traceIds() throws Exception { + ThreadLocalCurrentTraceContext braveCurrentTraceContext = ThreadLocalCurrentTraceContext.newBuilder() + .build(); + List traceIds = new ArrayList<>(); + tracing = Tracing.newBuilder() + .currentTraceContext(braveCurrentTraceContext) + .supportsJoin(false) + .traceId128Bit(true) + .sampler(Sampler.ALWAYS_SAMPLE) + .addSpanHandler(braveHttpSenderProvider.apply(port)) + .localServiceName("my-service") + .build(); + Tracer braveTracer = tracing.tracer(); + + for (int i = 0; i < EXPECTED_TRACE_SIZE; i++) { + Span span = braveTracer.nextSpan().name("foo " + i) + .tag("foo tag", "foo value") + .kind(Kind.CONSUMER) + .error(new RuntimeException("BOOOOOM!")) + .remoteServiceName("remote service") + .start(); + try (SpanInScope scope = braveTracer.withSpanInScope(span)) { + String traceId = span.context().traceIdString(); + log.info("Trace Id <" + traceId + ">"); + span.remoteIpAndPort("http://localhost", 123456); + Thread.sleep(50); + span.annotate("boom!"); + Thread.sleep(50); + } finally { + span.finish(); + } + + traceIds.add(span.context().traceIdString()); + } + flush(); + return traceIds; + } + + void flush() { + braveHttpSenderProvider.flush(); + } + + @Override + public void close() { + braveHttpSenderProvider.close(); + tracing.close(); + } + + /** + * Provides a {@link SpanHandler} that uses OKHttp to send spans to a given port. + */ + static class BraveHttpSenderProvider implements Function, Closeable { + + OkHttpSender okHttpSender; + + AsyncReporter reporter; + + SpanHandler spanHandler; + + @Override + public void close() { + if (reporter != null) { + reporter.close(); + } + if (okHttpSender != null) { + okHttpSender.close(); + } + } + + @Override + public SpanHandler apply(Integer port) { + okHttpSender = OkHttpSender.newBuilder() + .encoding(Encoding.PROTO3) + .endpoint("http://localhost:" + port + "/v1/traces") + .build(); + OtelEncoder otelEncoder = new OtelEncoder(Tags.ERROR); + reporter = AsyncReporter.builder(okHttpSender).build(otelEncoder); + spanHandler = new SpanHandler() { + @Override + public boolean end(TraceContext context, MutableSpan span, Cause cause) { + reporter.report(span); + return true; + } + }; + return spanHandler; + } + + void flush() { + reporter.flush(); + } + } + } } \ No newline at end of file diff --git a/tests/sender-tests/src/test/java/zipkin2/reporter/otel/JaegerAllInOne.java b/tests/sender-tests/src/test/java/zipkin2/reporter/otel/JaegerAllInOne.java index fbc4a84..40d937e 100644 --- a/tests/sender-tests/src/test/java/zipkin2/reporter/otel/JaegerAllInOne.java +++ b/tests/sender-tests/src/test/java/zipkin2/reporter/otel/JaegerAllInOne.java @@ -29,12 +29,12 @@ class JaegerAllInOne extends GenericContainer { static final int HTTP_OTLP_PORT = 4318; - public JaegerAllInOne() { + JaegerAllInOne() { super("jaegertracing/all-in-one:1.57"); init(); } - protected void init() { + private void init() { waitingFor(new BoundPortHttpWaitStrategy(JAEGER_ADMIN_PORT)); withExposedPorts(JAEGER_ADMIN_PORT, JAEGER_QUERY_PORT, @@ -42,19 +42,15 @@ protected void init() { HTTP_OTLP_PORT); } - public int getHttpOtlpPort() { + int getHttpOtlpPort() { return getMappedPort(HTTP_OTLP_PORT); } - public int getGrpcOtlpPort() { - return getMappedPort(GRPC_OTLP_PORT); - } - - public int getQueryPort() { + int getQueryPort() { return getMappedPort(JAEGER_QUERY_PORT); } - public static class BoundPortHttpWaitStrategy extends HttpWaitStrategy { + private static class BoundPortHttpWaitStrategy extends HttpWaitStrategy { private final int port; public BoundPortHttpWaitStrategy(int port) { From 81e36ed052d9e263ea119332f4d004c376f9f998 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 24 Jun 2024 11:56:07 +0200 Subject: [PATCH 08/30] Polish --- .github/workflows/create_release.yml | 28 ++++++------ .github/workflows/deploy.yml | 53 ++++++++++++---------- .github/workflows/docker_push.yml | 27 +++++++----- .github/workflows/test.yml | 66 ++++++++++------------------ README.md | 12 ++++- encoder-otel-zipkin/README.md | 2 +- pom.xml | 2 +- 7 files changed, 95 insertions(+), 95 deletions(-) diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index 303f885..39878e4 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -1,13 +1,12 @@ -# yamllint --format github .github/workflows/create_release.yml --- name: create_release -# We create a release version on a trigger tag, regardless of if the commit is documentation-only. -# -# See https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet -on: +# We create a release version on a trigger tag, regardless of if the commit is +# documentation-only. +on: # yamllint disable-line rule:truthy push: - tags: 'release-[0-9]+.[0-9]+.[0-9]+**' # Ex. release-1.2.3 + tags: # e.g. release-1.2.3 + - 'release-[0-9]+.[0-9]+.[0-9]+**' jobs: create_release: @@ -16,29 +15,28 @@ jobs: - name: Checkout Repository uses: actions/checkout@v4 with: - # Prevent use of implicit GitHub Actions read-only token GITHUB_TOKEN. We don't deploy on - # the tag MAJOR.MINOR.PATCH event, but we still need to deploy the maven-release-plugin main commit. + # Prevent use of implicit GitHub Actions read-only GITHUB_TOKEN + # because maven-release-plugin pushes commits to master. token: ${{ secrets.GH_TOKEN }} - fetch-depth: 1 # only need the HEAD commit as license check isn't run - name: Setup java uses: actions/setup-java@v4 with: distribution: 'zulu' # zulu as it supports a wide version range - java-version: '11' # earliest LTS and last that can compile the 1.6 release profile. + java-version: '17' # earliest LTS supported by Spring Boot 3 - name: Cache local Maven repository uses: actions/cache@v3 with: path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-maven- + key: ${{ runner.os }}-jdk-17-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-jdk-17-maven- - name: Create Release env: # GH_USER= GH_USER: ${{ secrets.GH_USER }} # GH_TOKEN= - # - makes release commits and tags - # - needs repo:status, public_repo - # - referenced in .settings.xml + # * makes release commits and tags + # * needs repo:status, public_repo + # * referenced in .settings.xml GH_TOKEN: ${{ secrets.GH_TOKEN }} run: | # GITHUB_REF will be refs/tags/release-MAJOR.MINOR.PATCH build-bin/git/login_git && diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c72e997..034bc35 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -2,15 +2,16 @@ --- name: deploy -# We deploy on main and release versions, regardless of if the commit is +# We deploy on master and release versions, regardless of if the commit is # documentation-only or not. -# -# See https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet -on: +on: # yamllint disable-line rule:truthy push: - # Don't deploy tags as they conflict with [maven-release-plugin] prepare release MAJOR.MINOR.PATCH - tags: '' - branches: main + branches: + - main + # Don't deploy tags because the same commit for MAJOR.MINOR.PATCH is also + # on master: Redundant deployment of a release version will fail uploading. + tags-ignore: + - '*' jobs: deploy: @@ -19,21 +20,20 @@ jobs: - name: Checkout Repository uses: actions/checkout@v4 with: - # Prevent use of implicit GitHub Actions read-only token GITHUB_TOKEN. - # We push Javadocs to the gh-pages branch on commit. + # Prevent use of implicit GitHub Actions read-only GITHUB_TOKEN + # because javadoc_to_gh_pages pushes commits to the gh-pages branch. token: ${{ secrets.GH_TOKEN }} - fetch-depth: 0 # allow build-bin/idl_to_gh_pages to get the full history - name: Setup java uses: actions/setup-java@v4 with: distribution: 'zulu' # zulu as it supports a wide version range - java-version: '11' # earliest LTS and last that can compile the 1.6 release profile. + java-version: '17' # earliest LTS supported by Spring Boot 3 - name: Cache local Maven repository uses: actions/cache@v3 with: path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-maven- + key: ${{ runner.os }}-jdk-17-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-jdk-17-maven- # Don't attempt to cache Docker. Sensitive information can be stolen # via forks, and login session ends up in ~/.docker. This is ok because # we publish DOCKER_PARENT_IMAGE to ghcr.io, hence local to the runner. @@ -42,23 +42,30 @@ jobs: # GH_USER= GH_USER: ${{ secrets.GH_USER }} # GH_TOKEN= - # - pushes Docker images to ghcr.io - # - create via https://github.com/settings/tokens - # - needs repo:status, public_repo, write:packages, delete:packages + # * pushes gh-pages during build-bin/javadoc_to_gh_pages + # * pushes Docker images to ghcr.io + # * create via https://github.com/settings/tokens + # * needs repo:status, public_repo, write:packages, delete:packages GH_TOKEN: ${{ secrets.GH_TOKEN }} GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }} # GPG_PASSPHRASE= - # - referenced in .settings.xml + # * referenced in .settings.xml GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} # SONATYPE_USER= - # - deploys snapshots and releases to Sonatype - # - needs access to io.zipkin via https://issues.sonatype.org/browse/OSSRH-16669 - # - generate via https://oss.sonatype.org/#profile;User%20Token - # - referenced in .settings.xml + # * deploys snapshots and releases to Sonatype + # * needs access to io.zipkin via OSSRH-16669 + # * generate via https://oss.sonatype.org/#profile;User%20Token + # * referenced in .settings.xml SONATYPE_USER: ${{ secrets.SONATYPE_USER }} # SONATYPE_PASSWORD= - # - referenced in .settings.xml + # * referenced in .settings.xml SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} - run: | # GITHUB_REF will be refs/heads/main or refs/tags/MAJOR.MINOR.PATCH + # DOCKERHUB_USER= + # * only push repos in openzipkin org to Docker Hub on release + DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }} + # DOCKERHUB_TOKEN= + # * Access Token from here https://hub.docker.com/settings/security + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} + run: | # GITHUB_REF = refs/heads/main or refs/tags/MAJOR.MINOR.PATCH build-bin/configure_deploy && build-bin/deploy $(echo ${GITHUB_REF} | cut -d/ -f 3) diff --git a/.github/workflows/docker_push.yml b/.github/workflows/docker_push.yml index 3cd27e4..0943939 100644 --- a/.github/workflows/docker_push.yml +++ b/.github/workflows/docker_push.yml @@ -1,13 +1,12 @@ -# yamllint --format github .github/workflows/docker_push.yml --- name: docker_push -# We re-push docker images on a trigger tag, regardless of if the commit is documentation-only. -# -# See https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet -on: +# We re-push docker on a trigger tag, regardless of if the commit is +# documentation-only. +on: # yamllint disable-line rule:truthy push: - tags: 'docker-[0-9]+.[0-9]+.[0-9]+**' # Ex. docker-1.2.3 + tags: # e.g. docker-1.2.3 + - 'docker-[0-9]+.[0-9]+.[0-9]+**' jobs: docker_push: @@ -15,13 +14,11 @@ jobs: steps: - name: Checkout Repository uses: actions/checkout@v4 - with: - fetch-depth: 1 # only needed to get the sha label # Don't attempt to cache Docker. Sensitive information can be stolen # via forks, and login session ends up in ~/.docker. This is ok because # we publish DOCKER_PARENT_IMAGE to ghcr.io, hence local to the runner. - name: Docker Push - run: | # GITHUB_REF will be refs/tags/docker-MAJOR.MINOR.PATCH + run: | # GITHUB_REF = refs/tags/docker-MAJOR.MINOR.PATCH build-bin/git/login_git && build-bin/docker/configure_docker_push && build-bin/docker_push $(echo ${GITHUB_REF} | cut -d/ -f 3) @@ -29,7 +26,13 @@ jobs: # GH_USER= GH_USER: ${{ secrets.GH_USER }} # GH_TOKEN= - # - pushes Docker images to ghcr.io - # - create via https://github.com/settings/tokens - # - needs repo:status, public_repo, write:packages, delete:packages + # * pushes Docker images to ghcr.io + # * create via https://github.com/settings/tokens + # * needs repo:status, public_repo, write:packages, delete:packages GH_TOKEN: ${{ secrets.GH_TOKEN }} + # DOCKERHUB_USER= + # * only push repos in openzipkin org to Docker Hub on release + DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }} + # DOCKERHUB_TOKEN= + # * Access Token from here https://hub.docker.com/settings/security + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1fd4247..6b8a80c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,59 +1,40 @@ -# yamllint --format github .github/workflows/test.yml --- name: test # We don't test documentation-only commits. -on: - # We run tests on non-tagged pushes to main that aren't a commit made by the release plugin - push: - tags: '' - branches: main - paths-ignore: '**/*.md' - # We also run tests on pull requests targeted at the main branch. - pull_request: - branches: main - paths-ignore: '**/*.md' +on: # yamllint disable-line rule:truthy + push: # non-tagged pushes to main + branches: + - main + tags-ignore: + - '*' + paths-ignore: + - '**/*.md' + - './build-bin/*lint' + - ./build-bin/mlc_config.json + pull_request: # pull requests targeted at the main branch. + branches: + - main + paths-ignore: + - '**/*.md' + - './build-bin/*lint' + - ./build-bin/mlc_config.json jobs: - test-javadoc: - name: Test JavaDoc Builds - runs-on: ubuntu-22.04 # newest available distribution, aka jellyfish - if: "!contains(github.event.head_commit.message, 'maven-release-plugin')" - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 # full git history for license check - - name: Setup java - uses: actions/setup-java@v4 - with: - distribution: 'zulu' # zulu as it supports a wide version range - java-version: '11' # earliest LTS and last that can compile the 1.6 release profile. - - name: Cache local Maven repository - uses: actions/cache@v3 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-jdk-11-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-jdk-11-maven- - - name: Build JavaDoc - run: ./mvnw clean javadoc:aggregate -Prelease - test: name: test (JDK ${{ matrix.java_version }}) - runs-on: ubuntu-22.04 # newest available distribution, aka jellyfish + runs-on: ubuntu-22.04 # newest available distribution, aka jellyfish if: "!contains(github.event.head_commit.message, 'maven-release-plugin')" strategy: - fail-fast: false # don't fail fast as sometimes failures are operating system specific - matrix: # use latest available versions and be consistent on all workflows! + fail-fast: false # don't fail fast as some failures are LTS specific + matrix: # match with maven-enforcer-plugin rules in pom.xml include: - - java_version: 11 # Last that can compile zipkin core to 1.6 for zipkin-reporter - maven_args: -Prelease -Dgpg.skip -Dmaven.javadoc.skip=true + - java_version: 17 # earliest LTS supported by Spring Boot 3 + maven_args: -Prelease -Dgpg.skip - java_version: 21 # Most recent LTS steps: - name: Checkout Repository uses: actions/checkout@v4 - with: - fetch-depth: 0 # full git history for license check - name: Setup java uses: actions/setup-java@v4 with: @@ -63,10 +44,11 @@ jobs: uses: actions/cache@v3 with: path: ~/.m2/repository + # yamllint disable-line rule:line-length key: ${{ runner.os }}-jdk-${{ matrix.java_version }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-jdk-${{ matrix.java_version }}-maven- # Don't attempt to cache Docker. Sensitive information can be stolen # via forks, and login session ends up in ~/.docker. This is ok because # we publish DOCKER_PARENT_IMAGE to ghcr.io, hence local to the runner. - name: Test - run: build-bin/configure_test && build-bin/test + run: build-bin/configure_test && build-bin/test ${{ matrix.maven_args }} diff --git a/README.md b/README.md index 4b80a0d..3e46748 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,19 @@ persists them to a configured collector component. | Collector | Description | |------------------------------------|-----------------------------------------------------------------------------------------| -| [collector-grpc](./collector-grpc) | Implements the [OTLP/gRPC protocol](https://opentelemetry.io/docs/specs/otlp/#otlpgrpc) | | [collector-http](./collector-http) | Implements the [OTLP/HTTP protocol](https://opentelemetry.io/docs/specs/otlp/#otlphttp) | +### Encoders + +Encoding is library-specific, as some libraries use `zipkin2.Span` and others +`brave.handler.MutableSpan`. Both options are available to encode to the +OTLP format. + +| Encoder | Description | +|-------------------------------------------|------------------------------------------------| +| [`OtelEncoder.V1`](./encoder-otel-zipkin) | zipkin-reporter `AsyncReporter` | +| [`OtelEncoder`](./encoder-otel-brave) | zipkin-reporter-brave `AsyncZipkinSpanHandler` | + ## Server integration If you cannot use our [Docker image](./docker/README.md), you can still integrate diff --git a/encoder-otel-zipkin/README.md b/encoder-otel-zipkin/README.md index 8d58b31..c033951 100644 --- a/encoder-otel-zipkin/README.md +++ b/encoder-otel-zipkin/README.md @@ -4,5 +4,5 @@ This encodes zipkin spans into OTLP proto format. ```java // connect the sender to the correct encoding -reporter = AsyncReporter.newBuilder(sender).build(OtelEncoder.V2); +reporter = AsyncReporter.newBuilder(sender).build(OtelEncoder.V1); ``` diff --git a/pom.xml b/pom.xml index 71b9048..fb569c4 100644 --- a/pom.xml +++ b/pom.xml @@ -400,7 +400,7 @@ - [11,12),[17,18),[21,22) + [17,18),[21,22) From d55db5f12d643ef7db60d95064c01e0a84b49ab6 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 24 Jun 2024 11:57:42 +0200 Subject: [PATCH 09/30] Removed OTel GRPC module --- benchmarks/pom.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index b6d2a32..e0a1287 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -34,11 +34,6 @@ - - ${project.groupId} - zipkin-collector-otel-grpc - ${project.version} - ${project.groupId} zipkin-collector-otel-http From f0680f4d76988783a1909fa9a04eee7c3685cffa Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 24 Jun 2024 12:04:45 +0200 Subject: [PATCH 10/30] Removed OTel GRPC module --- module/pom.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/module/pom.xml b/module/pom.xml index caf72b5..06b1313 100644 --- a/module/pom.xml +++ b/module/pom.xml @@ -32,11 +32,6 @@ - - ${project.groupId} - zipkin-collector-otel-grpc - ${project.version} - ${project.groupId} zipkin-collector-otel-http From b766e5960c6f218a0b3773e0594f6e515781fc60 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 24 Jun 2024 12:05:47 +0200 Subject: [PATCH 11/30] Removed OTel GRPC module --- tests/sender-tests/pom.xml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/sender-tests/pom.xml b/tests/sender-tests/pom.xml index f150b6b..46b9767 100644 --- a/tests/sender-tests/pom.xml +++ b/tests/sender-tests/pom.xml @@ -59,12 +59,6 @@ ${project.version} test - - ${project.groupId} - zipkin-sender-otel-grpc - ${project.version} - test - io.zipkin.reporter2 zipkin-sender-okhttp3 From 3639e4dccb5b63857ad2dd842dde2c3ac28f54ec Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 24 Jun 2024 16:57:02 +0200 Subject: [PATCH 12/30] WIP on ported tests from OTel --- .../otel/http/OpenTelemetryHttpCollector.java | 9 +- .../http/ITOpenTelemetryHttpCollector.java | 7 +- encoder-otel-brave/pom.xml | 64 +++ .../AbstractHttpTelemetryExporterTest.java | 485 ++++++++++++++++++ .../reporter/otel/brave/Base64Compressor.java | 29 ++ .../otel/brave/CloseableSpanHandler.java | 59 +++ .../otel/brave/FakeTelemetryUtil.java | 38 ++ .../brave/HttpSpanExporterBuilderWrapper.java | 131 +++++ .../OtlpHttpSpanExporterOkHttpSenderTest.java | 31 ++ .../otel/brave/TelemetryExporterBuilder.java | 41 ++ module/pom.xml | 2 +- pom.xml | 1 + tests/sender-tests/pom.xml | 2 +- 13 files changed, 891 insertions(+), 8 deletions(-) create mode 100644 encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/AbstractHttpTelemetryExporterTest.java create mode 100644 encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/Base64Compressor.java create mode 100644 encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/CloseableSpanHandler.java create mode 100644 encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/FakeTelemetryUtil.java create mode 100644 encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/HttpSpanExporterBuilderWrapper.java create mode 100644 encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/OtlpHttpSpanExporterOkHttpSenderTest.java create mode 100644 encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/TelemetryExporterBuilder.java diff --git a/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java b/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java index f1a5770..bb8b182 100644 --- a/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java +++ b/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java @@ -13,7 +13,6 @@ */ package zipkin2.collector.otel.http; -import com.google.protobuf.InvalidProtocolBufferException; import com.linecorp.armeria.common.AggregationOptions; import com.linecorp.armeria.common.HttpData; import com.linecorp.armeria.common.HttpRequest; @@ -26,18 +25,16 @@ import com.linecorp.armeria.server.ServiceRequestContext; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.ByteBufUtil; import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; +import java.util.Base64; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.zip.GZIPInputStream; import zipkin2.Callback; import zipkin2.Span; -import zipkin2.codec.SpanBytesDecoder; -import zipkin2.codec.SpanBytesEncoder; import zipkin2.collector.Collector; import zipkin2.collector.CollectorComponent; import zipkin2.collector.CollectorMetrics; @@ -143,8 +140,10 @@ protected HttpResponse doPost(ServiceRequestContext ctx, HttpRequest req) try (ReadBuffer readBuffer = ReadBuffer.wrapUnsafe(nioBuffer)) { try { InputStream inputStream = readBuffer; - if ("gzip".equals(req.headers().get("content-encoding"))) { + if ("gzip".equalsIgnoreCase(req.headers().get("content-encoding"))) { inputStream = new GZIPInputStream(content.toInputStream()); + } else if ("base64".equalsIgnoreCase(req.headers().get("content-encoding"))) { + inputStream = Base64.getDecoder().wrap(content.toInputStream()); } ExportTraceServiceRequest request = ExportTraceServiceRequest.parseFrom(inputStream); List spans = SpanTranslator.translate(request); diff --git a/collector-http/src/test/java/zipkin2/collector/otel/http/ITOpenTelemetryHttpCollector.java b/collector-http/src/test/java/zipkin2/collector/otel/http/ITOpenTelemetryHttpCollector.java index 35bd326..9b4ee9e 100644 --- a/collector-http/src/test/java/zipkin2/collector/otel/http/ITOpenTelemetryHttpCollector.java +++ b/collector-http/src/test/java/zipkin2/collector/otel/http/ITOpenTelemetryHttpCollector.java @@ -35,6 +35,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import zipkin2.collector.CollectorComponent; import zipkin2.collector.CollectorSampler; import zipkin2.collector.InMemoryCollectorMetrics; @@ -42,6 +44,9 @@ @TestInstance(TestInstance.Lifecycle.PER_CLASS) class ITOpenTelemetryHttpCollector { + + private static final Logger log = LoggerFactory.getLogger(ITOpenTelemetryHttpCollector.class); + InMemoryStorage store; InMemoryCollectorMetrics metrics; CollectorComponent collector; @@ -97,7 +102,7 @@ void otelHttpExporterWorksWithZipkinOtelCollector() throws InterruptedException .setSpanKind(SpanKind.CONSUMER) .startSpan(); String traceId = span.getSpanContext().getTraceId(); - System.out.println("Trace Id <" + traceId + ">"); + log.info("Trace Id <" + traceId + ">"); Thread.sleep(50); span.addEvent("boom!"); Thread.sleep(50); diff --git a/encoder-otel-brave/pom.xml b/encoder-otel-brave/pom.xml index b960283..d5be7ab 100644 --- a/encoder-otel-brave/pom.xml +++ b/encoder-otel-brave/pom.xml @@ -64,5 +64,69 @@ provided + + + org.mock-server + mockserver-netty + 5.15.0 + test + shaded + + + io.opentelemetry + opentelemetry-sdk + test + + + io.opentelemetry + opentelemetry-sdk-testing + test + + + io.opentelemetry + opentelemetry-exporter-common + test + + + io.opentelemetry + opentelemetry-exporter-otlp-common + test + + + com.squareup.okhttp3 + okhttp + ${okhttp.version} + test + + + io.zipkin.reporter2 + zipkin-sender-okhttp3 + ${zipkin-reporter.version} + test + + + com.squareup.okhttp3 + mockwebserver + ${okhttp.version} + test + + + ${armeria.groupId} + armeria-junit5 + ${armeria.version} + test + + + + + + io.netty + netty-bom + 4.1.108.Final + pom + import + + + \ No newline at end of file diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/AbstractHttpTelemetryExporterTest.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/AbstractHttpTelemetryExporterTest.java new file mode 100644 index 0000000..103f8e4 --- /dev/null +++ b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/AbstractHttpTelemetryExporterTest.java @@ -0,0 +1,485 @@ +/* + * Copyright 2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package zipkin2.reporter.otel.brave; + +import static org.assertj.core.api.Assertions.assertThat; + +import brave.Tags; +import brave.handler.MutableSpan; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.Message; +import com.linecorp.armeria.common.HttpRequest; +import com.linecorp.armeria.common.HttpResponse; +import com.linecorp.armeria.common.HttpStatus; +import com.linecorp.armeria.common.MediaType; +import com.linecorp.armeria.common.RequestHeaders; +import com.linecorp.armeria.server.HttpService; +import com.linecorp.armeria.server.ServerBuilder; +import com.linecorp.armeria.server.ServiceRequestContext; +import com.linecorp.armeria.testing.junit5.server.ServerExtension; +import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; +import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse; +import io.opentelemetry.proto.trace.v1.ResourceSpans; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.common.export.ProxyOptions; +import io.opentelemetry.sdk.common.export.RetryPolicy; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.InetSocketAddress; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.stream.Collectors; +import okio.Buffer; +import okio.GzipSource; +import okio.Okio; +import okio.Source; +import org.assertj.core.api.iterable.ThrowingExtractor; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockserver.integration.ClientAndServer; + +/** + * Taken from https://github.com/open-telemetry/opentelemetry-java/blob/v1.39.0/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractHttpTelemetryExporterTest.java + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public abstract class AbstractHttpTelemetryExporterTest { + + private static final ConcurrentLinkedQueue exportedResourceTelemetry = + new ConcurrentLinkedQueue<>(); + + private static final ConcurrentLinkedQueue httpErrors = + new ConcurrentLinkedQueue<>(); + + private static final AtomicInteger attempts = new AtomicInteger(); + + private static final ConcurrentLinkedQueue httpRequests = + new ConcurrentLinkedQueue<>(); + + @RegisterExtension + @Order(3) + static final ServerExtension server = + new ServerExtension() { + @Override + protected void configure(ServerBuilder sb) { + sb.service( + "/v1/traces", + new CollectorService<>( + ExportTraceServiceRequest::parseFrom, + ExportTraceServiceRequest::getResourceSpansList, + ExportTraceServiceResponse.getDefaultInstance().toByteArray())); + + sb.http(0); + } + }; + + private static class CollectorService implements HttpService { + private final ThrowingExtractor parse; + private final Function> getResourceTelemetry; + private final byte[] successResponse; + + private CollectorService( + ThrowingExtractor parse, + Function> getResourceTelemetry, + byte[] successResponse) { + this.parse = parse; + this.getResourceTelemetry = getResourceTelemetry; + this.successResponse = successResponse; + } + + @Override + public HttpResponse serve(ServiceRequestContext ctx, HttpRequest req) { + httpRequests.add(ctx.request()); + attempts.incrementAndGet(); + CompletableFuture responseFuture = + req.aggregate() + .thenApply( + aggReq -> { + T request; + try { + byte[] requestBody = maybeInflate(aggReq.headers(), aggReq.content().array()); + request = parse.extractThrows(requestBody); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + exportedResourceTelemetry.addAll(getResourceTelemetry.apply(request)); + HttpResponse errorResponse = httpErrors.poll(); + return errorResponse != null + ? errorResponse + : HttpResponse.of( + HttpStatus.OK, + MediaType.parse("application/x-protobuf"), + successResponse); + }); + return HttpResponse.of(responseFuture); + } + + private static byte[] maybeInflate(RequestHeaders requestHeaders, byte[] content) + throws IOException { + if (requestHeaders.contains("content-encoding", "gzip")) { + Buffer buffer = new Buffer(); + GzipSource gzipSource = new GzipSource(Okio.source(new ByteArrayInputStream(content))); + gzipSource.read(buffer, Integer.MAX_VALUE); + return buffer.readByteArray(); + } + if (requestHeaders.contains("content-encoding", "base64")) { + Buffer buffer = new Buffer(); + Source base64Source = + Okio.source(Base64.getDecoder().wrap(new ByteArrayInputStream(content))); + base64Source.read(buffer, Integer.MAX_VALUE); + return buffer.readByteArray(); + } + return content; + } + } + + private final String type; + private final String path; + private final U resourceTelemetryInstance; + + private CloseableSpanHandler exporter; // Brave OKHttp sender + + protected AbstractHttpTelemetryExporterTest( + String type, String path, U resourceTelemetryInstance) { + this.type = type; + this.path = path; + this.resourceTelemetryInstance = resourceTelemetryInstance; + } + + @BeforeAll + void setUp() { + exporter = exporterBuilder().setEndpoint(server.httpUri() + path).build(); + } + + @AfterAll + void shutdown() { + if (exporter != null) { + exporter.shutdown(); + } + } + + @AfterEach + void reset() { + exportedResourceTelemetry.clear(); + httpErrors.clear(); + attempts.set(0); + httpRequests.clear(); + } + + @Test + void export() { + List telemetry = Collections.singletonList(generateFakeTelemetry()); + assertThat(exporter.export(telemetry).join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); + List expectedResourceTelemetry = toProto(telemetry); + assertThat(exportedResourceTelemetry).containsExactlyElementsOf(expectedResourceTelemetry); + + // Assert request contains OTLP spec compliant User-Agent header + assertThat(httpRequests) + .singleElement() + .satisfies( + req -> + assertThat(req.headers().get("User-Agent")) + .matches("OTel-OTLP-Exporter-Java/1\\..*")); + } + + @Test + void multipleItems() { + List telemetry = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + telemetry.add(generateFakeTelemetry()); + } + assertThat(exporter.export(telemetry).join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); + List expectedResourceTelemetry = toProto(telemetry); + assertThat(exportedResourceTelemetry).containsExactlyElementsOf(expectedResourceTelemetry); + } + + @Test + void compressionWithNone() { + CloseableSpanHandler exporter = + exporterBuilder().setEndpoint(server.httpUri() + path).setCompression("none").build(); + try { + CompletableResultCode result = + exporter.export(Collections.singletonList(generateFakeTelemetry())); + assertThat(result.join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); + assertThat(httpRequests) + .singleElement() + .satisfies(req -> assertThat(req.headers().get("content-encoding")).isNull()); + } finally { + exporter.shutdown(); + } + } + + @Test + void compressionWithGzip() { + CloseableSpanHandler exporter = + exporterBuilder().setEndpoint(server.httpUri() + path).setCompression("gzip").build(); + try { + CompletableResultCode result = + exporter.export(Collections.singletonList(generateFakeTelemetry())); + assertThat(result.join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); + assertThat(httpRequests) + .singleElement() + .satisfies(req -> assertThat(req.headers().get("content-encoding")).isEqualTo("gzip")); + } finally { + exporter.shutdown(); + } + } + + @Test + void authorityWithAuth() { + CloseableSpanHandler exporter = + exporterBuilder() + .setEndpoint("http://foo:bar@localhost:" + server.httpPort() + path) + .build(); + try { + CompletableResultCode result = + exporter.export(Collections.singletonList(generateFakeTelemetry())); + assertThat(result.join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); + } finally { + exporter.shutdown(); + } + } + + @Test + void withHeaders() { + AtomicInteger count = new AtomicInteger(); + CloseableSpanHandler exporter = + exporterBuilder() + .setEndpoint(server.httpUri() + path) + .addHeader("key1", "value1") + .setHeaders(() -> Collections.singletonMap("key2", "value" + count.incrementAndGet())) + .build(); + try { + // Export twice to ensure header supplier gets invoked twice + CompletableResultCode result = + exporter.export(Collections.singletonList(generateFakeTelemetry())); + assertThat(result.join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); + result = exporter.export(Collections.singletonList(generateFakeTelemetry())); + assertThat(result.join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); + + assertThat(httpRequests) + .satisfiesExactly( + req -> { + assertThat(req.headers().get("key1")).isEqualTo("value1"); + assertThat(req.headers().get("key2")).isEqualTo("value" + (count.get() - 1)); + }, + req -> { + assertThat(req.headers().get("key1")).isEqualTo("value1"); + assertThat(req.headers().get("key2")).isEqualTo("value" + count.get()); + }); + } finally { + exporter.shutdown(); + } + } + + @Test + void connectTimeout() { + CloseableSpanHandler exporter = + exporterBuilder() + // Connecting to a non-routable IP address to trigger connection error + .setEndpoint("http://10.255.255.1") + .setConnectTimeout(Duration.ofMillis(1)) + .build(); + try { + long startTimeMillis = System.currentTimeMillis(); + CompletableResultCode result = + exporter.export(Collections.singletonList(generateFakeTelemetry())); + assertThat(result.join(10, TimeUnit.SECONDS).isSuccess()).isFalse(); + // Assert that the export request fails well before the default connect timeout of 10s + assertThat(System.currentTimeMillis() - startTimeMillis) + .isLessThan(TimeUnit.SECONDS.toMillis(1)); + } finally { + exporter.shutdown(); + } + } + + @Test + void deadlineSetPerExport() throws InterruptedException { + CloseableSpanHandler exporter = + exporterBuilder() + .setEndpoint(server.httpUri() + path) + .setTimeout(Duration.ofMillis(1500)) + .build(); + try { + TimeUnit.MILLISECONDS.sleep(2000); + CompletableResultCode result = + exporter.export(Collections.singletonList(generateFakeTelemetry())); + assertThat(result.join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); + } finally { + exporter.shutdown(); + } + } + + @Test + void exportAfterShutdown() { + CloseableSpanHandler exporter = exporterBuilder().setEndpoint(server.httpUri() + path).build(); + exporter.shutdown(); + assertThat( + exporter + .export(Collections.singletonList(generateFakeTelemetry())) + .join(10, TimeUnit.SECONDS) + .isSuccess()) + .isFalse(); + assertThat(httpRequests).isEmpty(); + } + + @ParameterizedTest + @ValueSource(ints = {429, 502, 503, 504}) + void retryableError(int code) { + addHttpError(code); + + CloseableSpanHandler exporter = retryingExporter(); + + try { + assertThat( + exporter + .export(Collections.singletonList(generateFakeTelemetry())) + .join(10, TimeUnit.SECONDS) + .isSuccess()) + .isTrue(); + } finally { + exporter.shutdown(); + } + + assertThat(attempts).hasValue(2); + } + + @Test + void retryableError_tooManyAttempts() { + addHttpError(502); + addHttpError(502); + + CloseableSpanHandler exporter = retryingExporter(); + + try { + assertThat( + exporter + .export(Collections.singletonList(generateFakeTelemetry())) + .join(10, TimeUnit.SECONDS) + .isSuccess()) + .isFalse(); + } finally { + exporter.shutdown(); + } + + assertThat(attempts).hasValue(2); + } + + @ParameterizedTest + @ValueSource(ints = {400, 401, 403, 500, 501}) + void nonRetryableError(int code) { + addHttpError(code); + + CloseableSpanHandler exporter = retryingExporter(); + + try { + assertThat( + exporter + .export(Collections.singletonList(generateFakeTelemetry())) + .join(10, TimeUnit.SECONDS) + .isSuccess()) + .isFalse(); + } finally { + exporter.shutdown(); + } + + assertThat(attempts).hasValue(1); + } + + @Test + void proxy() { + // configure mockserver to proxy to the local OTLP server + InetSocketAddress serverSocketAddress = server.httpSocketAddress(); + try (ClientAndServer clientAndServer = + ClientAndServer.startClientAndServer( + serverSocketAddress.getHostName(), serverSocketAddress.getPort())) { + CloseableSpanHandler exporter = + exporterBuilder() + // Configure exporter with server endpoint, and proxy options to route through + // mockserver proxy + .setEndpoint(server.httpUri() + path) + .setProxyOptions( + ProxyOptions.create( + InetSocketAddress.createUnresolved("localhost", clientAndServer.getPort()))) + .build(); + + try { + List telemetry = Collections.singletonList(generateFakeTelemetry()); + + assertThat(exporter.export(telemetry).join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); + // assert that mock server received request + assertThat(clientAndServer.retrieveRecordedRequests(new org.mockserver.model.HttpRequest())) + .hasSize(1); + // assert that server received telemetry from proxy, and is as expected + List expectedResourceTelemetry = toProto(telemetry); + assertThat(exportedResourceTelemetry).containsExactlyElementsOf(expectedResourceTelemetry); + } finally { + exporter.shutdown(); + } + } + } + + protected abstract TelemetryExporterBuilder exporterBuilder(); + + protected abstract MutableSpan generateFakeTelemetry(); + + private List toProto(List telemetry) { + OtelEncoder otelEncoder = new OtelEncoder(Tags.ERROR); + return + telemetry.stream() + .map(otelEncoder::encode) + .flatMap(bytes -> { + try { + return ExportTraceServiceRequest.parseFrom(bytes).getResourceSpansList().stream(); + } catch (InvalidProtocolBufferException e) { + throw new RuntimeException(e); + } + }) + .collect(Collectors.toList()); + } + + private CloseableSpanHandler retryingExporter() { + return exporterBuilder() + .setEndpoint(server.httpUri() + path) + .setRetryPolicy( + RetryPolicy.builder() + .setMaxAttempts(2) + // We don't validate backoff time itself in these tests, just that retries + // occur. Keep the tests fast by using minimal backoff. + .setInitialBackoff(Duration.ofMillis(1)) + .setMaxBackoff(Duration.ofMillis(1)) + .setBackoffMultiplier(1) + .build()) + .build(); + } + + private static void addHttpError(int code) { + httpErrors.add(HttpResponse.of(code)); + } +} \ No newline at end of file diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/Base64Compressor.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/Base64Compressor.java new file mode 100644 index 0000000..6c88324 --- /dev/null +++ b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/Base64Compressor.java @@ -0,0 +1,29 @@ +package zipkin2.reporter.otel.brave; + +import io.opentelemetry.exporter.internal.compression.Compressor; +import java.io.OutputStream; +import java.util.Base64; + +/** + * Taken from https://github.com/open-telemetry/opentelemetry-java/blob/v1.39.0/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/compressor/Base64Compressor.java + */ +class Base64Compressor implements Compressor { + + private static final Base64Compressor INSTANCE = new Base64Compressor(); + + private Base64Compressor() {} + + public static Base64Compressor getInstance() { + return INSTANCE; + } + + @Override + public String getEncoding() { + return "base64"; + } + + @Override + public OutputStream compress(OutputStream outputStream) { + return Base64.getEncoder().wrap(outputStream); + } +} \ No newline at end of file diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/CloseableSpanHandler.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/CloseableSpanHandler.java new file mode 100644 index 0000000..1c82e9f --- /dev/null +++ b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/CloseableSpanHandler.java @@ -0,0 +1,59 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package zipkin2.reporter.otel.brave; + +import brave.handler.MutableSpan; +import brave.handler.SpanHandler; +import brave.propagation.TraceContext; +import io.opentelemetry.sdk.common.CompletableResultCode; +import java.util.Collection; +import zipkin2.reporter.AsyncReporter; +import zipkin2.reporter.InMemoryReporterMetrics; + +public class CloseableSpanHandler extends SpanHandler { + + private final AsyncReporter reporter; + + private final InMemoryReporterMetrics reporterMetrics; + + CloseableSpanHandler(AsyncReporter reporter, InMemoryReporterMetrics reporterMetrics) { + this.reporter = reporter; + this.reporterMetrics = reporterMetrics; + } + + @Override + public boolean end(TraceContext context, MutableSpan span, Cause cause) { + long currentDropped = reporterMetrics.messagesDropped(); + this.reporter.report(span); + this.reporter.flush(); + long newDropped = reporterMetrics.messagesDropped(); + if (newDropped > currentDropped) { + throw new IllegalStateException("Dropped message"); + } + return true; + } + + public CompletableResultCode export(Collection spans) { + try { + for (MutableSpan span : spans) { + end(null, span, Cause.FINISHED); + } + } catch (Exception ex) { + return CompletableResultCode.ofFailure(); + } + return CompletableResultCode.ofSuccess(); + } + + public CompletableResultCode shutdown() { + try { + this.reporter.flush(); + this.reporter.close(); + } catch (Exception ex) { + return CompletableResultCode.ofFailure(); + } + return CompletableResultCode.ofSuccess(); + } +} diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/FakeTelemetryUtil.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/FakeTelemetryUtil.java new file mode 100644 index 0000000..fc21d8b --- /dev/null +++ b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/FakeTelemetryUtil.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package zipkin2.reporter.otel.brave; + +import brave.Span.Kind; +import brave.handler.MutableSpan; +import io.opentelemetry.sdk.trace.data.SpanData; +import java.util.concurrent.TimeUnit; + +/** + * Taken from https://github.com/open-telemetry/opentelemetry-java/blob/v1.39.0/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/FakeTelemetryUtil.java + */ +public class FakeTelemetryUtil { + + private static final String TRACE_ID = "00000000000000000000000000abc123"; + private static final String SPAN_ID = "0000000000def456"; + + + /** Generate a fake {@link SpanData}. */ + public static MutableSpan generateFakeSpanData() { + long duration = TimeUnit.MILLISECONDS.toNanos(900); + long startNs = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()); + long endNs = startNs + duration; + MutableSpan mutableSpan = new MutableSpan(); + mutableSpan.traceId(TRACE_ID); + mutableSpan.id(SPAN_ID); + mutableSpan.name("GET /api/endpoint"); + mutableSpan.startTimestamp(TimeUnit.NANOSECONDS.toMicros(startNs)); + mutableSpan.finishTimestamp(TimeUnit.NANOSECONDS.toMicros(endNs)); + mutableSpan.kind(Kind.SERVER); + return mutableSpan; + } + + private FakeTelemetryUtil() {} +} diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/HttpSpanExporterBuilderWrapper.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/HttpSpanExporterBuilderWrapper.java new file mode 100644 index 0000000..359c008 --- /dev/null +++ b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/HttpSpanExporterBuilderWrapper.java @@ -0,0 +1,131 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package zipkin2.reporter.otel.brave; + +import brave.Tags; +import brave.handler.MutableSpan; +import io.opentelemetry.sdk.common.export.ProxyOptions; +import io.opentelemetry.sdk.common.export.RetryPolicy; +import java.net.URI; +import java.time.Duration; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import okhttp3.Headers; +import okhttp3.Request; +import zipkin2.reporter.AsyncReporter; +import zipkin2.reporter.Encoding; +import zipkin2.reporter.InMemoryReporterMetrics; +import zipkin2.reporter.okhttp3.OkHttpSender; + +/** + * Taken from https://github.com/open-telemetry/opentelemetry-java/blob/v1.39.0/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/HttpSpanExporterBuilderWrapper.java + */ +public class HttpSpanExporterBuilderWrapper implements TelemetryExporterBuilder { + + private final OkHttpSender.Builder builder; + + public HttpSpanExporterBuilderWrapper(OkHttpSender.Builder builder) { + this.builder = builder.encoding(Encoding.PROTO3); + this.builder.clientBuilder() + .addInterceptor(chain -> { + // TODO: This should be added to the docs or somewhere to wrap OkHttp Sender + Request request = chain.request().newBuilder() + .addHeader("User-Agent", "OTel-OTLP-Exporter-Java/1.0.0").build(); + return chain.proceed(request); + }); + } + + @Override + public TelemetryExporterBuilder setEndpoint(String endpoint) { + builder.endpoint(endpoint); + return this; + } + + @Override + public TelemetryExporterBuilder setTimeout(long timeout, TimeUnit unit) { + builder.readTimeout(Math.toIntExact(unit.toMillis(timeout))); + return this; + } + + @Override + public TelemetryExporterBuilder setTimeout(Duration timeout) { + builder.readTimeout(Math.toIntExact(timeout.toMillis())); + return this; + } + + @Override + public TelemetryExporterBuilder setConnectTimeout(long timeout, TimeUnit unit) { + builder.connectTimeout(Math.toIntExact(unit.toMillis(timeout))); + return this; + } + + @Override + public TelemetryExporterBuilder setConnectTimeout(Duration timeout) { + builder.connectTimeout(Math.toIntExact(timeout.toMillis())); + return this; + } + + @Override + public TelemetryExporterBuilder setCompression(String compression) { + if ("gzip".equalsIgnoreCase(compression)) { + builder.compressionEnabled(true); + } else if ("none".equalsIgnoreCase(compression)) { + builder.compressionEnabled(false); + } else { + throw new UnsupportedOperationException("Only gzip is supported"); + } + return this; + } + + @Override + public TelemetryExporterBuilder addHeader(String key, String value) { + builder.clientBuilder().addInterceptor(chain -> { + Request request = chain.request(); + Request newRequest = request.newBuilder() + .addHeader(key, value) + .build(); + return chain.proceed(newRequest); + }); + return this; + } + + @Override + public TelemetryExporterBuilder setHeaders( + Supplier> headerSupplier) { + builder.clientBuilder().addInterceptor(chain -> { + Request request = chain.request(); + Request newRequest = request.newBuilder() + .headers(Headers.of(headerSupplier.get())) + .build(); + return chain.proceed(newRequest); + }); + return this; + } + + @Override + public TelemetryExporterBuilder setRetryPolicy(RetryPolicy retryPolicy) { + builder.clientBuilder().retryOnConnectionFailure(true); + return this; + } + + @Override + public TelemetryExporterBuilder setProxyOptions(ProxyOptions proxyOptions) { + builder.clientBuilder().proxy(proxyOptions.getProxySelector().select(URI.create("localhost")).get(0)); + return this; + } + + @Override + public CloseableSpanHandler build() { + OtelEncoder otelEncoder = new OtelEncoder(Tags.ERROR); + OkHttpSender sender = builder.build(); + InMemoryReporterMetrics reporterMetrics = new InMemoryReporterMetrics(); + AsyncReporter reporter = AsyncReporter.builder(sender) + .metrics(reporterMetrics) + .build(otelEncoder); + return new CloseableSpanHandler(reporter, reporterMetrics); + } +} diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/OtlpHttpSpanExporterOkHttpSenderTest.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/OtlpHttpSpanExporterOkHttpSenderTest.java new file mode 100644 index 0000000..a5da698 --- /dev/null +++ b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/OtlpHttpSpanExporterOkHttpSenderTest.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package zipkin2.reporter.otel.brave; + +import brave.handler.MutableSpan; +import io.opentelemetry.proto.trace.v1.ResourceSpans; +import zipkin2.reporter.okhttp3.OkHttpSender; + +/** + * Taken from https://github.com/open-telemetry/opentelemetry-java/blob/v1.39.0/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/OtlpHttpSpanExporterOkHttpSenderTest.java + */ +class OtlpHttpSpanExporterOkHttpSenderTest + extends AbstractHttpTelemetryExporterTest { + + protected OtlpHttpSpanExporterOkHttpSenderTest() { + super("span", "/v1/traces", ResourceSpans.getDefaultInstance()); + } + + @Override + protected TelemetryExporterBuilder exporterBuilder() { + return new HttpSpanExporterBuilderWrapper(OkHttpSender.newBuilder()); + } + + @Override + protected MutableSpan generateFakeTelemetry() { + return FakeTelemetryUtil.generateFakeSpanData(); + } +} diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/TelemetryExporterBuilder.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/TelemetryExporterBuilder.java new file mode 100644 index 0000000..de202e9 --- /dev/null +++ b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/TelemetryExporterBuilder.java @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package zipkin2.reporter.otel.brave; + +import io.opentelemetry.sdk.common.export.ProxyOptions; +import io.opentelemetry.sdk.common.export.RetryPolicy; +import java.time.Duration; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +/** + * Taken from https://github.com/open-telemetry/opentelemetry-java/blob/v1.39.0/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/TelemetryExporterBuilder.java + */ +public interface TelemetryExporterBuilder { + + TelemetryExporterBuilder setEndpoint(String endpoint); + + TelemetryExporterBuilder setTimeout(long timeout, TimeUnit unit); + + TelemetryExporterBuilder setTimeout(Duration timeout); + + TelemetryExporterBuilder setConnectTimeout(long timeout, TimeUnit unit); + + TelemetryExporterBuilder setConnectTimeout(Duration timeout); + + TelemetryExporterBuilder setCompression(String compression); + + TelemetryExporterBuilder addHeader(String key, String value); + + TelemetryExporterBuilder setHeaders(Supplier> headerSupplier); + + TelemetryExporterBuilder setRetryPolicy(RetryPolicy retryPolicy); + + TelemetryExporterBuilder setProxyOptions(ProxyOptions proxyOptions); + + CloseableSpanHandler build(); +} diff --git a/module/pom.xml b/module/pom.xml index 06b1313..e9317f8 100644 --- a/module/pom.xml +++ b/module/pom.xml @@ -46,7 +46,7 @@ ${armeria.groupId} - armeria-spring-boot2-autoconfigure + armeria-spring-boot3-autoconfigure ${armeria.version} provided diff --git a/pom.xml b/pom.xml index fb569c4..9ae1719 100644 --- a/pom.xml +++ b/pom.xml @@ -88,6 +88,7 @@ 5.10.2 5.12.0 1.19.8 + 4.12.0 diff --git a/tests/sender-tests/pom.xml b/tests/sender-tests/pom.xml index 46b9767..674b1fa 100644 --- a/tests/sender-tests/pom.xml +++ b/tests/sender-tests/pom.xml @@ -79,7 +79,7 @@ ${armeria.groupId} - armeria-spring-boot2-autoconfigure + armeria-spring-boot3-autoconfigure ${armeria.version} From 7738906f43503a1220288a34e9f6cb10984d873f Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 25 Jun 2024 08:20:00 +0200 Subject: [PATCH 13/30] Fixed tests --- .../AbstractHttpTelemetryExporterTest.java | 103 +----------------- .../reporter/otel/brave/Base64Compressor.java | 29 ----- .../otel/brave/CloseableSpanHandler.java | 24 +++- .../otel/brave/FakeTelemetryUtil.java | 14 ++- .../brave/HttpSpanExporterBuilderWrapper.java | 44 +++----- .../OtlpHttpSpanExporterOkHttpSenderTest.java | 16 ++- .../otel/brave/TelemetryExporterBuilder.java | 22 ++-- 7 files changed, 73 insertions(+), 179 deletions(-) delete mode 100644 encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/Base64Compressor.java diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/AbstractHttpTelemetryExporterTest.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/AbstractHttpTelemetryExporterTest.java index 103f8e4..d9d1ef6 100644 --- a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/AbstractHttpTelemetryExporterTest.java +++ b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/AbstractHttpTelemetryExporterTest.java @@ -33,7 +33,6 @@ import io.opentelemetry.proto.trace.v1.ResourceSpans; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.common.export.ProxyOptions; -import io.opentelemetry.sdk.common.export.RetryPolicy; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.UncheckedIOException; @@ -61,8 +60,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; import org.mockserver.integration.ClientAndServer; /** @@ -159,17 +156,12 @@ private static byte[] maybeInflate(RequestHeaders requestHeaders, byte[] content } } - private final String type; private final String path; - private final U resourceTelemetryInstance; - private CloseableSpanHandler exporter; // Brave OKHttp sender + private CloseableSpanHandler exporter; - protected AbstractHttpTelemetryExporterTest( - String type, String path, U resourceTelemetryInstance) { - this.type = type; + protected AbstractHttpTelemetryExporterTest(String path) { this.path = path; - this.resourceTelemetryInstance = resourceTelemetryInstance; } @BeforeAll @@ -336,82 +328,6 @@ void deadlineSetPerExport() throws InterruptedException { } } - @Test - void exportAfterShutdown() { - CloseableSpanHandler exporter = exporterBuilder().setEndpoint(server.httpUri() + path).build(); - exporter.shutdown(); - assertThat( - exporter - .export(Collections.singletonList(generateFakeTelemetry())) - .join(10, TimeUnit.SECONDS) - .isSuccess()) - .isFalse(); - assertThat(httpRequests).isEmpty(); - } - - @ParameterizedTest - @ValueSource(ints = {429, 502, 503, 504}) - void retryableError(int code) { - addHttpError(code); - - CloseableSpanHandler exporter = retryingExporter(); - - try { - assertThat( - exporter - .export(Collections.singletonList(generateFakeTelemetry())) - .join(10, TimeUnit.SECONDS) - .isSuccess()) - .isTrue(); - } finally { - exporter.shutdown(); - } - - assertThat(attempts).hasValue(2); - } - - @Test - void retryableError_tooManyAttempts() { - addHttpError(502); - addHttpError(502); - - CloseableSpanHandler exporter = retryingExporter(); - - try { - assertThat( - exporter - .export(Collections.singletonList(generateFakeTelemetry())) - .join(10, TimeUnit.SECONDS) - .isSuccess()) - .isFalse(); - } finally { - exporter.shutdown(); - } - - assertThat(attempts).hasValue(2); - } - - @ParameterizedTest - @ValueSource(ints = {400, 401, 403, 500, 501}) - void nonRetryableError(int code) { - addHttpError(code); - - CloseableSpanHandler exporter = retryingExporter(); - - try { - assertThat( - exporter - .export(Collections.singletonList(generateFakeTelemetry())) - .join(10, TimeUnit.SECONDS) - .isSuccess()) - .isFalse(); - } finally { - exporter.shutdown(); - } - - assertThat(attempts).hasValue(1); - } - @Test void proxy() { // configure mockserver to proxy to the local OTLP server @@ -464,21 +380,6 @@ private List toProto(List telemetry) { .collect(Collectors.toList()); } - private CloseableSpanHandler retryingExporter() { - return exporterBuilder() - .setEndpoint(server.httpUri() + path) - .setRetryPolicy( - RetryPolicy.builder() - .setMaxAttempts(2) - // We don't validate backoff time itself in these tests, just that retries - // occur. Keep the tests fast by using minimal backoff. - .setInitialBackoff(Duration.ofMillis(1)) - .setMaxBackoff(Duration.ofMillis(1)) - .setBackoffMultiplier(1) - .build()) - .build(); - } - private static void addHttpError(int code) { httpErrors.add(HttpResponse.of(code)); } diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/Base64Compressor.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/Base64Compressor.java deleted file mode 100644 index 6c88324..0000000 --- a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/Base64Compressor.java +++ /dev/null @@ -1,29 +0,0 @@ -package zipkin2.reporter.otel.brave; - -import io.opentelemetry.exporter.internal.compression.Compressor; -import java.io.OutputStream; -import java.util.Base64; - -/** - * Taken from https://github.com/open-telemetry/opentelemetry-java/blob/v1.39.0/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/compressor/Base64Compressor.java - */ -class Base64Compressor implements Compressor { - - private static final Base64Compressor INSTANCE = new Base64Compressor(); - - private Base64Compressor() {} - - public static Base64Compressor getInstance() { - return INSTANCE; - } - - @Override - public String getEncoding() { - return "base64"; - } - - @Override - public OutputStream compress(OutputStream outputStream) { - return Base64.getEncoder().wrap(outputStream); - } -} \ No newline at end of file diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/CloseableSpanHandler.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/CloseableSpanHandler.java index 1c82e9f..ab2a0bb 100644 --- a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/CloseableSpanHandler.java +++ b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/CloseableSpanHandler.java @@ -1,8 +1,16 @@ /* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 + * Copyright 2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. */ - package zipkin2.reporter.otel.brave; import brave.handler.MutableSpan; @@ -10,6 +18,7 @@ import brave.propagation.TraceContext; import io.opentelemetry.sdk.common.CompletableResultCode; import java.util.Collection; +import java.util.concurrent.atomic.AtomicBoolean; import zipkin2.reporter.AsyncReporter; import zipkin2.reporter.InMemoryReporterMetrics; @@ -19,6 +28,8 @@ public class CloseableSpanHandler extends SpanHandler { private final InMemoryReporterMetrics reporterMetrics; + private final AtomicBoolean shutdown = new AtomicBoolean(); + CloseableSpanHandler(AsyncReporter reporter, InMemoryReporterMetrics reporterMetrics) { this.reporter = reporter; this.reporterMetrics = reporterMetrics; @@ -37,6 +48,9 @@ public boolean end(TraceContext context, MutableSpan span, Cause cause) { } public CompletableResultCode export(Collection spans) { + if (shutdown.get()) { + return CompletableResultCode.ofFailure(); + } try { for (MutableSpan span : spans) { end(null, span, Cause.FINISHED); @@ -48,9 +62,13 @@ public CompletableResultCode export(Collection spans) { } public CompletableResultCode shutdown() { + if (shutdown.get()) { + return CompletableResultCode.ofSuccess(); + } try { this.reporter.flush(); this.reporter.close(); + shutdown.set(true); } catch (Exception ex) { return CompletableResultCode.ofFailure(); } diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/FakeTelemetryUtil.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/FakeTelemetryUtil.java index fc21d8b..0fe47fd 100644 --- a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/FakeTelemetryUtil.java +++ b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/FakeTelemetryUtil.java @@ -1,8 +1,16 @@ /* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 + * Copyright 2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. */ - package zipkin2.reporter.otel.brave; import brave.Span.Kind; diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/HttpSpanExporterBuilderWrapper.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/HttpSpanExporterBuilderWrapper.java index 359c008..3baf81a 100644 --- a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/HttpSpanExporterBuilderWrapper.java +++ b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/HttpSpanExporterBuilderWrapper.java @@ -1,21 +1,27 @@ /* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 + * Copyright 2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. */ - package zipkin2.reporter.otel.brave; import brave.Tags; import brave.handler.MutableSpan; import io.opentelemetry.sdk.common.export.ProxyOptions; -import io.opentelemetry.sdk.common.export.RetryPolicy; import java.net.URI; import java.time.Duration; import java.util.Map; -import java.util.concurrent.TimeUnit; import java.util.function.Supplier; -import okhttp3.Headers; import okhttp3.Request; +import okhttp3.Request.Builder; import zipkin2.reporter.AsyncReporter; import zipkin2.reporter.Encoding; import zipkin2.reporter.InMemoryReporterMetrics; @@ -29,7 +35,7 @@ public class HttpSpanExporterBuilderWrapper implements TelemetryExporterBuilder< private final OkHttpSender.Builder builder; public HttpSpanExporterBuilderWrapper(OkHttpSender.Builder builder) { - this.builder = builder.encoding(Encoding.PROTO3); + this.builder = builder.encoding(Encoding.PROTO3).compressionEnabled(false); this.builder.clientBuilder() .addInterceptor(chain -> { // TODO: This should be added to the docs or somewhere to wrap OkHttp Sender @@ -45,24 +51,12 @@ public TelemetryExporterBuilder setEndpoint(String endpoint) { return this; } - @Override - public TelemetryExporterBuilder setTimeout(long timeout, TimeUnit unit) { - builder.readTimeout(Math.toIntExact(unit.toMillis(timeout))); - return this; - } - @Override public TelemetryExporterBuilder setTimeout(Duration timeout) { builder.readTimeout(Math.toIntExact(timeout.toMillis())); return this; } - @Override - public TelemetryExporterBuilder setConnectTimeout(long timeout, TimeUnit unit) { - builder.connectTimeout(Math.toIntExact(unit.toMillis(timeout))); - return this; - } - @Override public TelemetryExporterBuilder setConnectTimeout(Duration timeout) { builder.connectTimeout(Math.toIntExact(timeout.toMillis())); @@ -98,20 +92,14 @@ public TelemetryExporterBuilder setHeaders( Supplier> headerSupplier) { builder.clientBuilder().addInterceptor(chain -> { Request request = chain.request(); - Request newRequest = request.newBuilder() - .headers(Headers.of(headerSupplier.get())) - .build(); + Builder newBuilder = request.newBuilder(); + headerSupplier.get().forEach(newBuilder::addHeader); + Request newRequest = newBuilder.build(); return chain.proceed(newRequest); }); return this; } - @Override - public TelemetryExporterBuilder setRetryPolicy(RetryPolicy retryPolicy) { - builder.clientBuilder().retryOnConnectionFailure(true); - return this; - } - @Override public TelemetryExporterBuilder setProxyOptions(ProxyOptions proxyOptions) { builder.clientBuilder().proxy(proxyOptions.getProxySelector().select(URI.create("localhost")).get(0)); diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/OtlpHttpSpanExporterOkHttpSenderTest.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/OtlpHttpSpanExporterOkHttpSenderTest.java index a5da698..db20911 100644 --- a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/OtlpHttpSpanExporterOkHttpSenderTest.java +++ b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/OtlpHttpSpanExporterOkHttpSenderTest.java @@ -1,8 +1,16 @@ /* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 + * Copyright 2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. */ - package zipkin2.reporter.otel.brave; import brave.handler.MutableSpan; @@ -16,7 +24,7 @@ class OtlpHttpSpanExporterOkHttpSenderTest extends AbstractHttpTelemetryExporterTest { protected OtlpHttpSpanExporterOkHttpSenderTest() { - super("span", "/v1/traces", ResourceSpans.getDefaultInstance()); + super("/v1/traces"); } @Override diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/TelemetryExporterBuilder.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/TelemetryExporterBuilder.java index de202e9..14c0381 100644 --- a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/TelemetryExporterBuilder.java +++ b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/TelemetryExporterBuilder.java @@ -1,15 +1,21 @@ /* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 + * Copyright 2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. */ - package zipkin2.reporter.otel.brave; import io.opentelemetry.sdk.common.export.ProxyOptions; -import io.opentelemetry.sdk.common.export.RetryPolicy; import java.time.Duration; import java.util.Map; -import java.util.concurrent.TimeUnit; import java.util.function.Supplier; /** @@ -19,12 +25,8 @@ public interface TelemetryExporterBuilder { TelemetryExporterBuilder setEndpoint(String endpoint); - TelemetryExporterBuilder setTimeout(long timeout, TimeUnit unit); - TelemetryExporterBuilder setTimeout(Duration timeout); - TelemetryExporterBuilder setConnectTimeout(long timeout, TimeUnit unit); - TelemetryExporterBuilder setConnectTimeout(Duration timeout); TelemetryExporterBuilder setCompression(String compression); @@ -33,8 +35,6 @@ public interface TelemetryExporterBuilder { TelemetryExporterBuilder setHeaders(Supplier> headerSupplier); - TelemetryExporterBuilder setRetryPolicy(RetryPolicy retryPolicy); - TelemetryExporterBuilder setProxyOptions(ProxyOptions proxyOptions); CloseableSpanHandler build(); From c8686008224f1e31e4a4e24b8e2e2886a9d4c5ad Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 25 Jun 2024 08:36:41 +0200 Subject: [PATCH 14/30] Trying to make the tests pass --- encoder-otel-brave/pom.xml | 6 ++++++ .../zipkin2/reporter/otel/brave/CloseableSpanHandler.java | 7 +++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/encoder-otel-brave/pom.xml b/encoder-otel-brave/pom.xml index d5be7ab..527bd28 100644 --- a/encoder-otel-brave/pom.xml +++ b/encoder-otel-brave/pom.xml @@ -116,6 +116,12 @@ ${armeria.version} test + + org.awaitility + awaitility + ${awaitility.version} + test + diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/CloseableSpanHandler.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/CloseableSpanHandler.java index ab2a0bb..f031172 100644 --- a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/CloseableSpanHandler.java +++ b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/CloseableSpanHandler.java @@ -19,6 +19,7 @@ import io.opentelemetry.sdk.common.CompletableResultCode; import java.util.Collection; import java.util.concurrent.atomic.AtomicBoolean; +import org.awaitility.Awaitility; import zipkin2.reporter.AsyncReporter; import zipkin2.reporter.InMemoryReporterMetrics; @@ -47,17 +48,19 @@ public boolean end(TraceContext context, MutableSpan span, Cause cause) { return true; } - public CompletableResultCode export(Collection spans) { + public CompletableResultCode export(Collection spansToExport) { if (shutdown.get()) { return CompletableResultCode.ofFailure(); } + long spansBeforeExport = reporterMetrics.spans(); try { - for (MutableSpan span : spans) { + for (MutableSpan span : spansToExport) { end(null, span, Cause.FINISHED); } } catch (Exception ex) { return CompletableResultCode.ofFailure(); } + Awaitility.await().untilTrue(new AtomicBoolean(reporterMetrics.spans() == spansBeforeExport + spansToExport.size())); return CompletableResultCode.ofSuccess(); } From 02c49f1ca76c1945b06f6f3b4fb87b215fdbbcfb Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 25 Jun 2024 08:55:30 +0200 Subject: [PATCH 15/30] Trying to make the tests pass --- .../otel/brave/AbstractHttpTelemetryExporterTest.java | 11 ++++++++--- .../reporter/otel/brave/CloseableSpanHandler.java | 7 ++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/AbstractHttpTelemetryExporterTest.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/AbstractHttpTelemetryExporterTest.java index d9d1ef6..4bb5f10 100644 --- a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/AbstractHttpTelemetryExporterTest.java +++ b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/AbstractHttpTelemetryExporterTest.java @@ -53,6 +53,7 @@ import okio.Okio; import okio.Source; import org.assertj.core.api.iterable.ThrowingExtractor; +import org.awaitility.Awaitility; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -63,7 +64,8 @@ import org.mockserver.integration.ClientAndServer; /** - * Taken from https://github.com/open-telemetry/opentelemetry-java/blob/v1.39.0/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractHttpTelemetryExporterTest.java + * Taken from + * https://github.com/open-telemetry/opentelemetry-java/blob/v1.39.0/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractHttpTelemetryExporterTest.java */ @TestInstance(TestInstance.Lifecycle.PER_CLASS) public abstract class AbstractHttpTelemetryExporterTest { @@ -97,6 +99,7 @@ protected void configure(ServerBuilder sb) { }; private static class CollectorService implements HttpService { + private final ThrowingExtractor parse; private final Function> getResourceTelemetry; private final byte[] successResponse; @@ -208,7 +211,9 @@ void multipleItems() { } assertThat(exporter.export(telemetry).join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); List expectedResourceTelemetry = toProto(telemetry); - assertThat(exportedResourceTelemetry).containsExactlyElementsOf(expectedResourceTelemetry); + Awaitility.await().untilAsserted( + () -> assertThat(exportedResourceTelemetry).containsExactlyElementsOf( + expectedResourceTelemetry)); } @Test @@ -377,7 +382,7 @@ private List toProto(List telemetry) { throw new RuntimeException(e); } }) - .collect(Collectors.toList()); + .collect(Collectors.toList()); } private static void addHttpError(int code) { diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/CloseableSpanHandler.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/CloseableSpanHandler.java index f031172..ab2a0bb 100644 --- a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/CloseableSpanHandler.java +++ b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/CloseableSpanHandler.java @@ -19,7 +19,6 @@ import io.opentelemetry.sdk.common.CompletableResultCode; import java.util.Collection; import java.util.concurrent.atomic.AtomicBoolean; -import org.awaitility.Awaitility; import zipkin2.reporter.AsyncReporter; import zipkin2.reporter.InMemoryReporterMetrics; @@ -48,19 +47,17 @@ public boolean end(TraceContext context, MutableSpan span, Cause cause) { return true; } - public CompletableResultCode export(Collection spansToExport) { + public CompletableResultCode export(Collection spans) { if (shutdown.get()) { return CompletableResultCode.ofFailure(); } - long spansBeforeExport = reporterMetrics.spans(); try { - for (MutableSpan span : spansToExport) { + for (MutableSpan span : spans) { end(null, span, Cause.FINISHED); } } catch (Exception ex) { return CompletableResultCode.ofFailure(); } - Awaitility.await().untilTrue(new AtomicBoolean(reporterMetrics.spans() == spansBeforeExport + spansToExport.size())); return CompletableResultCode.ofSuccess(); } From 437f9e988f98b8703c87c83feffd2028c83100a1 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 25 Jun 2024 15:02:05 +0200 Subject: [PATCH 16/30] SpanTranslator tests --- .../otel/brave/AttributesExtractor.java | 13 ++- .../reporter/otel/brave/SpanTranslator.java | 11 +- .../otel/brave/AttributesExtractorTest.java | 89 +++++++++++++++ .../otel/brave/SpanTranslatorTest.java | 107 ++++++++++++++++++ .../reporter/otel/brave/TestObjects.java | 38 +++++++ .../reporter/otel/zipkin/OtelEncoder.java | 23 +--- .../reporter/otel/zipkin/OtelEncoderTest.java | 45 ++++++++ 7 files changed, 294 insertions(+), 32 deletions(-) create mode 100644 encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/AttributesExtractorTest.java create mode 100644 encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/SpanTranslatorTest.java create mode 100644 encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/TestObjects.java create mode 100644 encoder-otel-zipkin/src/test/java/zipkin2/reporter/otel/zipkin/OtelEncoderTest.java diff --git a/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/AttributesExtractor.java b/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/AttributesExtractor.java index 6dea70d..3f3cc1f 100644 --- a/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/AttributesExtractor.java +++ b/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/AttributesExtractor.java @@ -17,6 +17,7 @@ import brave.handler.MutableSpan; import io.opentelemetry.proto.common.v1.AnyValue; import io.opentelemetry.proto.common.v1.KeyValue; +import io.opentelemetry.proto.trace.v1.Span; import java.util.Map; /** @@ -42,17 +43,17 @@ final class AttributesExtractor { this.renamedLabels = renamedLabels; } - void addErrorTag(KeyValue.Builder target, MutableSpan braveSpan) { + void addErrorTag(Span.Builder target, MutableSpan braveSpan) { String errorValue = errorTag.value(braveSpan.error(), null); if (errorValue != null) { - target.setKey(getLabelName("error")).setValue( - AnyValue.newBuilder().setStringValue(errorValue).build()); + target.addAttributes(KeyValue.newBuilder().setKey(getLabelName("error")).setValue( + AnyValue.newBuilder().setStringValue(errorValue).build()).build()); } } - void addTag(KeyValue.Builder target, String key, String value) { - target.setKey(getLabelName(key)).setValue( - AnyValue.newBuilder().setStringValue(value).build()); + void addTag(Span.Builder target, String key, String value) { + target.addAttributes(KeyValue.newBuilder().setKey(getLabelName(key)).setValue( + AnyValue.newBuilder().setStringValue(value).build()).build()); } private String getLabelName(String zipkinName) { diff --git a/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java b/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java index 9834061..c15e8bb 100644 --- a/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java +++ b/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java @@ -25,6 +25,7 @@ import io.opentelemetry.proto.trace.v1.ResourceSpans.Builder; import io.opentelemetry.proto.trace.v1.ScopeSpans; import io.opentelemetry.proto.trace.v1.Span; +import io.opentelemetry.proto.trace.v1.Span.Event; import io.opentelemetry.proto.trace.v1.Span.SpanKind; import io.opentelemetry.proto.trace.v1.TracesData; import io.opentelemetry.semconv.HttpAttributes; @@ -90,7 +91,7 @@ private Span.Builder builderForSingleSpan(MutableSpan span, Builder resourceSpan Span.Builder spanBuilder = Span.newBuilder() .setTraceId(ByteString.fromHex(span.traceId())) .setSpanId(ByteString.fromHex(span.id())) - .setName(span.name()); + .setName((span.name() == null || span.name().isEmpty()) ? "unknown" : span.name()); if (span.parentId() != null) { spanBuilder.setParentSpanId(ByteString.fromHex(span.parentId())); } @@ -166,17 +167,17 @@ class Consumer implements TagConsumer, AnnotationConsumer Date: Tue, 25 Jun 2024 16:47:29 +0200 Subject: [PATCH 17/30] Added moar tests --- encoder-otel-brave/pom.xml | 4 + .../otel/brave/AttributesExtractor.java | 5 + .../reporter/otel/brave/SpanTranslator.java | 36 +- .../AbstractHttpTelemetryExporterTest.java | 391 -------- .../otel/brave/CloseableSpanHandler.java | 77 -- .../otel/brave/FakeTelemetryUtil.java | 46 - .../brave/HttpSpanExporterBuilderWrapper.java | 119 --- .../OtelToZipkinSpanTransformerTest.java | 896 ++++++++++++++++++ .../OtlpHttpSpanExporterOkHttpSenderTest.java | 39 - .../otel/brave/SpanTranslatorTest.java | 13 + .../otel/brave/TelemetryExporterBuilder.java | 41 - 11 files changed, 951 insertions(+), 716 deletions(-) delete mode 100644 encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/AbstractHttpTelemetryExporterTest.java delete mode 100644 encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/CloseableSpanHandler.java delete mode 100644 encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/FakeTelemetryUtil.java delete mode 100644 encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/HttpSpanExporterBuilderWrapper.java create mode 100644 encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/OtelToZipkinSpanTransformerTest.java delete mode 100644 encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/OtlpHttpSpanExporterOkHttpSenderTest.java delete mode 100644 encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/TelemetryExporterBuilder.java diff --git a/encoder-otel-brave/pom.xml b/encoder-otel-brave/pom.xml index 527bd28..5ca69c6 100644 --- a/encoder-otel-brave/pom.xml +++ b/encoder-otel-brave/pom.xml @@ -35,6 +35,10 @@ io.opentelemetry opentelemetry-api + + io.opentelemetry + opentelemetry-sdk-common + io.opentelemetry.proto opentelemetry-proto diff --git a/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/AttributesExtractor.java b/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/AttributesExtractor.java index 3f3cc1f..6d3bfb6 100644 --- a/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/AttributesExtractor.java +++ b/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/AttributesExtractor.java @@ -18,6 +18,8 @@ import io.opentelemetry.proto.common.v1.AnyValue; import io.opentelemetry.proto.common.v1.KeyValue; import io.opentelemetry.proto.trace.v1.Span; +import io.opentelemetry.proto.trace.v1.Status; +import io.opentelemetry.proto.trace.v1.Status.StatusCode; import java.util.Map; /** @@ -48,6 +50,9 @@ void addErrorTag(Span.Builder target, MutableSpan braveSpan) { if (errorValue != null) { target.addAttributes(KeyValue.newBuilder().setKey(getLabelName("error")).setValue( AnyValue.newBuilder().setStringValue(errorValue).build()).build()); + target.setStatus(Status.newBuilder().setCode(StatusCode.STATUS_CODE_ERROR).build()); + } else { + target.setStatus(Status.newBuilder().setCode(StatusCode.STATUS_CODE_OK).build()); } } diff --git a/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java b/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java index c15e8bb..05b4983 100644 --- a/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java +++ b/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java @@ -13,6 +13,8 @@ */ package zipkin2.reporter.otel.brave; +import static io.opentelemetry.api.common.AttributeKey.stringKey; + import brave.Span.Kind; import brave.Tag; import brave.handler.MutableSpan; @@ -40,10 +42,16 @@ /** - * SpanTranslator converts a Zipkin Span to a OpenTelemetry Span. + * SpanTranslator converts a Brave Span to a OpenTelemetry Span. */ final class SpanTranslator { + static final String KEY_INSTRUMENTATION_SCOPE_NAME = "otel.scope.name"; + static final String KEY_INSTRUMENTATION_SCOPE_VERSION = "otel.scope.version"; + static final String KEY_INSTRUMENTATION_LIBRARY_NAME = "otel.library.name"; + static final String KEY_INSTRUMENTATION_LIBRARY_VERSION = "otel.library.version"; + static final String OTEL_STATUS_CODE = "otel.status_code"; + private static final Map RENAMED_LABELS; static { @@ -89,8 +97,8 @@ TracesData translate(MutableSpan braveSpan) { private Span.Builder builderForSingleSpan(MutableSpan span, Builder resourceSpansBuilder) { Span.Builder spanBuilder = Span.newBuilder() - .setTraceId(ByteString.fromHex(span.traceId())) - .setSpanId(ByteString.fromHex(span.id())) + .setTraceId(ByteString.fromHex(span.traceId() != null ? span.traceId() : io.opentelemetry.api.trace.SpanContext.getInvalid().getTraceId())) + .setSpanId(ByteString.fromHex(span.id() != null ? span.id() : io.opentelemetry.api.trace.SpanContext.getInvalid().getSpanId())) .setName((span.name() == null || span.name().isEmpty()) ? "unknown" : span.name()); if (span.parentId() != null) { spanBuilder.setParentSpanId(ByteString.fromHex(span.parentId())); @@ -119,12 +127,19 @@ private Span.Builder builderForSingleSpan(MutableSpan span, Builder resourceSpan default: spanBuilder.setKind(SpanKind.SPAN_KIND_INTERNAL); //TODO: Should it work like this? } + } else { + spanBuilder.setKind(SpanKind.SPAN_KIND_INTERNAL); //TODO: Should it work like this? } String localServiceName = span.localServiceName(); if (localServiceName != null) { resourceSpansBuilder.getResourceBuilder().addAttributes( KeyValue.newBuilder().setKey(ServiceAttributes.SERVICE_NAME.getKey()) .setValue(AnyValue.newBuilder().setStringValue(localServiceName).build()).build()); + } else { + resourceSpansBuilder.getResourceBuilder() + .addAttributes(KeyValue.newBuilder().setKey("service.name").setValue( + AnyValue.newBuilder().setStringValue( + io.opentelemetry.sdk.resources.Resource.getDefault().getAttribute(stringKey("service.name"))).build()).build()); } String localIp = span.localIp(); if (localIp != null) { @@ -151,6 +166,21 @@ private Span.Builder builderForSingleSpan(MutableSpan span, Builder resourceSpan spanBuilder.addAttributes(KeyValue.newBuilder().setKey(SemanticAttributes.NET_SOCK_PEER_PORT.getKey()) .setValue(AnyValue.newBuilder().setIntValue(peerPort).build()).build()); } + // TODO: What should we put here? + // tags taken from OtelToZipkinSpanTransformer + spanBuilder.addAttributes(KeyValue.newBuilder().setKey(KEY_INSTRUMENTATION_SCOPE_NAME).setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()).build()); + // TODO: Hardcoded library version + spanBuilder.addAttributes(KeyValue.newBuilder().setKey(KEY_INSTRUMENTATION_SCOPE_VERSION).setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()); + // Include instrumentation library name for backwards compatibility + spanBuilder.addAttributes(KeyValue.newBuilder().setKey(KEY_INSTRUMENTATION_LIBRARY_NAME).setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()).build()); + // TODO: Hardcoded library version + // Include instrumentation library name for backwards compatibility + spanBuilder.addAttributes(KeyValue.newBuilder().setKey(KEY_INSTRUMENTATION_LIBRARY_VERSION).setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()); + span.forEachTag(consumer, spanBuilder); span.forEachAnnotation(consumer, spanBuilder); consumer.addErrorTag(spanBuilder, span); diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/AbstractHttpTelemetryExporterTest.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/AbstractHttpTelemetryExporterTest.java deleted file mode 100644 index 4bb5f10..0000000 --- a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/AbstractHttpTelemetryExporterTest.java +++ /dev/null @@ -1,391 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin2.reporter.otel.brave; - -import static org.assertj.core.api.Assertions.assertThat; - -import brave.Tags; -import brave.handler.MutableSpan; -import com.google.protobuf.InvalidProtocolBufferException; -import com.google.protobuf.Message; -import com.linecorp.armeria.common.HttpRequest; -import com.linecorp.armeria.common.HttpResponse; -import com.linecorp.armeria.common.HttpStatus; -import com.linecorp.armeria.common.MediaType; -import com.linecorp.armeria.common.RequestHeaders; -import com.linecorp.armeria.server.HttpService; -import com.linecorp.armeria.server.ServerBuilder; -import com.linecorp.armeria.server.ServiceRequestContext; -import com.linecorp.armeria.testing.junit5.server.ServerExtension; -import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; -import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse; -import io.opentelemetry.proto.trace.v1.ResourceSpans; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.common.export.ProxyOptions; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.net.InetSocketAddress; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Base64; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; -import java.util.stream.Collectors; -import okio.Buffer; -import okio.GzipSource; -import okio.Okio; -import okio.Source; -import org.assertj.core.api.iterable.ThrowingExtractor; -import org.awaitility.Awaitility; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.mockserver.integration.ClientAndServer; - -/** - * Taken from - * https://github.com/open-telemetry/opentelemetry-java/blob/v1.39.0/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractHttpTelemetryExporterTest.java - */ -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -public abstract class AbstractHttpTelemetryExporterTest { - - private static final ConcurrentLinkedQueue exportedResourceTelemetry = - new ConcurrentLinkedQueue<>(); - - private static final ConcurrentLinkedQueue httpErrors = - new ConcurrentLinkedQueue<>(); - - private static final AtomicInteger attempts = new AtomicInteger(); - - private static final ConcurrentLinkedQueue httpRequests = - new ConcurrentLinkedQueue<>(); - - @RegisterExtension - @Order(3) - static final ServerExtension server = - new ServerExtension() { - @Override - protected void configure(ServerBuilder sb) { - sb.service( - "/v1/traces", - new CollectorService<>( - ExportTraceServiceRequest::parseFrom, - ExportTraceServiceRequest::getResourceSpansList, - ExportTraceServiceResponse.getDefaultInstance().toByteArray())); - - sb.http(0); - } - }; - - private static class CollectorService implements HttpService { - - private final ThrowingExtractor parse; - private final Function> getResourceTelemetry; - private final byte[] successResponse; - - private CollectorService( - ThrowingExtractor parse, - Function> getResourceTelemetry, - byte[] successResponse) { - this.parse = parse; - this.getResourceTelemetry = getResourceTelemetry; - this.successResponse = successResponse; - } - - @Override - public HttpResponse serve(ServiceRequestContext ctx, HttpRequest req) { - httpRequests.add(ctx.request()); - attempts.incrementAndGet(); - CompletableFuture responseFuture = - req.aggregate() - .thenApply( - aggReq -> { - T request; - try { - byte[] requestBody = maybeInflate(aggReq.headers(), aggReq.content().array()); - request = parse.extractThrows(requestBody); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - exportedResourceTelemetry.addAll(getResourceTelemetry.apply(request)); - HttpResponse errorResponse = httpErrors.poll(); - return errorResponse != null - ? errorResponse - : HttpResponse.of( - HttpStatus.OK, - MediaType.parse("application/x-protobuf"), - successResponse); - }); - return HttpResponse.of(responseFuture); - } - - private static byte[] maybeInflate(RequestHeaders requestHeaders, byte[] content) - throws IOException { - if (requestHeaders.contains("content-encoding", "gzip")) { - Buffer buffer = new Buffer(); - GzipSource gzipSource = new GzipSource(Okio.source(new ByteArrayInputStream(content))); - gzipSource.read(buffer, Integer.MAX_VALUE); - return buffer.readByteArray(); - } - if (requestHeaders.contains("content-encoding", "base64")) { - Buffer buffer = new Buffer(); - Source base64Source = - Okio.source(Base64.getDecoder().wrap(new ByteArrayInputStream(content))); - base64Source.read(buffer, Integer.MAX_VALUE); - return buffer.readByteArray(); - } - return content; - } - } - - private final String path; - - private CloseableSpanHandler exporter; - - protected AbstractHttpTelemetryExporterTest(String path) { - this.path = path; - } - - @BeforeAll - void setUp() { - exporter = exporterBuilder().setEndpoint(server.httpUri() + path).build(); - } - - @AfterAll - void shutdown() { - if (exporter != null) { - exporter.shutdown(); - } - } - - @AfterEach - void reset() { - exportedResourceTelemetry.clear(); - httpErrors.clear(); - attempts.set(0); - httpRequests.clear(); - } - - @Test - void export() { - List telemetry = Collections.singletonList(generateFakeTelemetry()); - assertThat(exporter.export(telemetry).join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); - List expectedResourceTelemetry = toProto(telemetry); - assertThat(exportedResourceTelemetry).containsExactlyElementsOf(expectedResourceTelemetry); - - // Assert request contains OTLP spec compliant User-Agent header - assertThat(httpRequests) - .singleElement() - .satisfies( - req -> - assertThat(req.headers().get("User-Agent")) - .matches("OTel-OTLP-Exporter-Java/1\\..*")); - } - - @Test - void multipleItems() { - List telemetry = new ArrayList<>(); - for (int i = 0; i < 10; i++) { - telemetry.add(generateFakeTelemetry()); - } - assertThat(exporter.export(telemetry).join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); - List expectedResourceTelemetry = toProto(telemetry); - Awaitility.await().untilAsserted( - () -> assertThat(exportedResourceTelemetry).containsExactlyElementsOf( - expectedResourceTelemetry)); - } - - @Test - void compressionWithNone() { - CloseableSpanHandler exporter = - exporterBuilder().setEndpoint(server.httpUri() + path).setCompression("none").build(); - try { - CompletableResultCode result = - exporter.export(Collections.singletonList(generateFakeTelemetry())); - assertThat(result.join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); - assertThat(httpRequests) - .singleElement() - .satisfies(req -> assertThat(req.headers().get("content-encoding")).isNull()); - } finally { - exporter.shutdown(); - } - } - - @Test - void compressionWithGzip() { - CloseableSpanHandler exporter = - exporterBuilder().setEndpoint(server.httpUri() + path).setCompression("gzip").build(); - try { - CompletableResultCode result = - exporter.export(Collections.singletonList(generateFakeTelemetry())); - assertThat(result.join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); - assertThat(httpRequests) - .singleElement() - .satisfies(req -> assertThat(req.headers().get("content-encoding")).isEqualTo("gzip")); - } finally { - exporter.shutdown(); - } - } - - @Test - void authorityWithAuth() { - CloseableSpanHandler exporter = - exporterBuilder() - .setEndpoint("http://foo:bar@localhost:" + server.httpPort() + path) - .build(); - try { - CompletableResultCode result = - exporter.export(Collections.singletonList(generateFakeTelemetry())); - assertThat(result.join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); - } finally { - exporter.shutdown(); - } - } - - @Test - void withHeaders() { - AtomicInteger count = new AtomicInteger(); - CloseableSpanHandler exporter = - exporterBuilder() - .setEndpoint(server.httpUri() + path) - .addHeader("key1", "value1") - .setHeaders(() -> Collections.singletonMap("key2", "value" + count.incrementAndGet())) - .build(); - try { - // Export twice to ensure header supplier gets invoked twice - CompletableResultCode result = - exporter.export(Collections.singletonList(generateFakeTelemetry())); - assertThat(result.join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); - result = exporter.export(Collections.singletonList(generateFakeTelemetry())); - assertThat(result.join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); - - assertThat(httpRequests) - .satisfiesExactly( - req -> { - assertThat(req.headers().get("key1")).isEqualTo("value1"); - assertThat(req.headers().get("key2")).isEqualTo("value" + (count.get() - 1)); - }, - req -> { - assertThat(req.headers().get("key1")).isEqualTo("value1"); - assertThat(req.headers().get("key2")).isEqualTo("value" + count.get()); - }); - } finally { - exporter.shutdown(); - } - } - - @Test - void connectTimeout() { - CloseableSpanHandler exporter = - exporterBuilder() - // Connecting to a non-routable IP address to trigger connection error - .setEndpoint("http://10.255.255.1") - .setConnectTimeout(Duration.ofMillis(1)) - .build(); - try { - long startTimeMillis = System.currentTimeMillis(); - CompletableResultCode result = - exporter.export(Collections.singletonList(generateFakeTelemetry())); - assertThat(result.join(10, TimeUnit.SECONDS).isSuccess()).isFalse(); - // Assert that the export request fails well before the default connect timeout of 10s - assertThat(System.currentTimeMillis() - startTimeMillis) - .isLessThan(TimeUnit.SECONDS.toMillis(1)); - } finally { - exporter.shutdown(); - } - } - - @Test - void deadlineSetPerExport() throws InterruptedException { - CloseableSpanHandler exporter = - exporterBuilder() - .setEndpoint(server.httpUri() + path) - .setTimeout(Duration.ofMillis(1500)) - .build(); - try { - TimeUnit.MILLISECONDS.sleep(2000); - CompletableResultCode result = - exporter.export(Collections.singletonList(generateFakeTelemetry())); - assertThat(result.join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); - } finally { - exporter.shutdown(); - } - } - - @Test - void proxy() { - // configure mockserver to proxy to the local OTLP server - InetSocketAddress serverSocketAddress = server.httpSocketAddress(); - try (ClientAndServer clientAndServer = - ClientAndServer.startClientAndServer( - serverSocketAddress.getHostName(), serverSocketAddress.getPort())) { - CloseableSpanHandler exporter = - exporterBuilder() - // Configure exporter with server endpoint, and proxy options to route through - // mockserver proxy - .setEndpoint(server.httpUri() + path) - .setProxyOptions( - ProxyOptions.create( - InetSocketAddress.createUnresolved("localhost", clientAndServer.getPort()))) - .build(); - - try { - List telemetry = Collections.singletonList(generateFakeTelemetry()); - - assertThat(exporter.export(telemetry).join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); - // assert that mock server received request - assertThat(clientAndServer.retrieveRecordedRequests(new org.mockserver.model.HttpRequest())) - .hasSize(1); - // assert that server received telemetry from proxy, and is as expected - List expectedResourceTelemetry = toProto(telemetry); - assertThat(exportedResourceTelemetry).containsExactlyElementsOf(expectedResourceTelemetry); - } finally { - exporter.shutdown(); - } - } - } - - protected abstract TelemetryExporterBuilder exporterBuilder(); - - protected abstract MutableSpan generateFakeTelemetry(); - - private List toProto(List telemetry) { - OtelEncoder otelEncoder = new OtelEncoder(Tags.ERROR); - return - telemetry.stream() - .map(otelEncoder::encode) - .flatMap(bytes -> { - try { - return ExportTraceServiceRequest.parseFrom(bytes).getResourceSpansList().stream(); - } catch (InvalidProtocolBufferException e) { - throw new RuntimeException(e); - } - }) - .collect(Collectors.toList()); - } - - private static void addHttpError(int code) { - httpErrors.add(HttpResponse.of(code)); - } -} \ No newline at end of file diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/CloseableSpanHandler.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/CloseableSpanHandler.java deleted file mode 100644 index ab2a0bb..0000000 --- a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/CloseableSpanHandler.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin2.reporter.otel.brave; - -import brave.handler.MutableSpan; -import brave.handler.SpanHandler; -import brave.propagation.TraceContext; -import io.opentelemetry.sdk.common.CompletableResultCode; -import java.util.Collection; -import java.util.concurrent.atomic.AtomicBoolean; -import zipkin2.reporter.AsyncReporter; -import zipkin2.reporter.InMemoryReporterMetrics; - -public class CloseableSpanHandler extends SpanHandler { - - private final AsyncReporter reporter; - - private final InMemoryReporterMetrics reporterMetrics; - - private final AtomicBoolean shutdown = new AtomicBoolean(); - - CloseableSpanHandler(AsyncReporter reporter, InMemoryReporterMetrics reporterMetrics) { - this.reporter = reporter; - this.reporterMetrics = reporterMetrics; - } - - @Override - public boolean end(TraceContext context, MutableSpan span, Cause cause) { - long currentDropped = reporterMetrics.messagesDropped(); - this.reporter.report(span); - this.reporter.flush(); - long newDropped = reporterMetrics.messagesDropped(); - if (newDropped > currentDropped) { - throw new IllegalStateException("Dropped message"); - } - return true; - } - - public CompletableResultCode export(Collection spans) { - if (shutdown.get()) { - return CompletableResultCode.ofFailure(); - } - try { - for (MutableSpan span : spans) { - end(null, span, Cause.FINISHED); - } - } catch (Exception ex) { - return CompletableResultCode.ofFailure(); - } - return CompletableResultCode.ofSuccess(); - } - - public CompletableResultCode shutdown() { - if (shutdown.get()) { - return CompletableResultCode.ofSuccess(); - } - try { - this.reporter.flush(); - this.reporter.close(); - shutdown.set(true); - } catch (Exception ex) { - return CompletableResultCode.ofFailure(); - } - return CompletableResultCode.ofSuccess(); - } -} diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/FakeTelemetryUtil.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/FakeTelemetryUtil.java deleted file mode 100644 index 0fe47fd..0000000 --- a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/FakeTelemetryUtil.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin2.reporter.otel.brave; - -import brave.Span.Kind; -import brave.handler.MutableSpan; -import io.opentelemetry.sdk.trace.data.SpanData; -import java.util.concurrent.TimeUnit; - -/** - * Taken from https://github.com/open-telemetry/opentelemetry-java/blob/v1.39.0/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/FakeTelemetryUtil.java - */ -public class FakeTelemetryUtil { - - private static final String TRACE_ID = "00000000000000000000000000abc123"; - private static final String SPAN_ID = "0000000000def456"; - - - /** Generate a fake {@link SpanData}. */ - public static MutableSpan generateFakeSpanData() { - long duration = TimeUnit.MILLISECONDS.toNanos(900); - long startNs = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()); - long endNs = startNs + duration; - MutableSpan mutableSpan = new MutableSpan(); - mutableSpan.traceId(TRACE_ID); - mutableSpan.id(SPAN_ID); - mutableSpan.name("GET /api/endpoint"); - mutableSpan.startTimestamp(TimeUnit.NANOSECONDS.toMicros(startNs)); - mutableSpan.finishTimestamp(TimeUnit.NANOSECONDS.toMicros(endNs)); - mutableSpan.kind(Kind.SERVER); - return mutableSpan; - } - - private FakeTelemetryUtil() {} -} diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/HttpSpanExporterBuilderWrapper.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/HttpSpanExporterBuilderWrapper.java deleted file mode 100644 index 3baf81a..0000000 --- a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/HttpSpanExporterBuilderWrapper.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin2.reporter.otel.brave; - -import brave.Tags; -import brave.handler.MutableSpan; -import io.opentelemetry.sdk.common.export.ProxyOptions; -import java.net.URI; -import java.time.Duration; -import java.util.Map; -import java.util.function.Supplier; -import okhttp3.Request; -import okhttp3.Request.Builder; -import zipkin2.reporter.AsyncReporter; -import zipkin2.reporter.Encoding; -import zipkin2.reporter.InMemoryReporterMetrics; -import zipkin2.reporter.okhttp3.OkHttpSender; - -/** - * Taken from https://github.com/open-telemetry/opentelemetry-java/blob/v1.39.0/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/HttpSpanExporterBuilderWrapper.java - */ -public class HttpSpanExporterBuilderWrapper implements TelemetryExporterBuilder { - - private final OkHttpSender.Builder builder; - - public HttpSpanExporterBuilderWrapper(OkHttpSender.Builder builder) { - this.builder = builder.encoding(Encoding.PROTO3).compressionEnabled(false); - this.builder.clientBuilder() - .addInterceptor(chain -> { - // TODO: This should be added to the docs or somewhere to wrap OkHttp Sender - Request request = chain.request().newBuilder() - .addHeader("User-Agent", "OTel-OTLP-Exporter-Java/1.0.0").build(); - return chain.proceed(request); - }); - } - - @Override - public TelemetryExporterBuilder setEndpoint(String endpoint) { - builder.endpoint(endpoint); - return this; - } - - @Override - public TelemetryExporterBuilder setTimeout(Duration timeout) { - builder.readTimeout(Math.toIntExact(timeout.toMillis())); - return this; - } - - @Override - public TelemetryExporterBuilder setConnectTimeout(Duration timeout) { - builder.connectTimeout(Math.toIntExact(timeout.toMillis())); - return this; - } - - @Override - public TelemetryExporterBuilder setCompression(String compression) { - if ("gzip".equalsIgnoreCase(compression)) { - builder.compressionEnabled(true); - } else if ("none".equalsIgnoreCase(compression)) { - builder.compressionEnabled(false); - } else { - throw new UnsupportedOperationException("Only gzip is supported"); - } - return this; - } - - @Override - public TelemetryExporterBuilder addHeader(String key, String value) { - builder.clientBuilder().addInterceptor(chain -> { - Request request = chain.request(); - Request newRequest = request.newBuilder() - .addHeader(key, value) - .build(); - return chain.proceed(newRequest); - }); - return this; - } - - @Override - public TelemetryExporterBuilder setHeaders( - Supplier> headerSupplier) { - builder.clientBuilder().addInterceptor(chain -> { - Request request = chain.request(); - Builder newBuilder = request.newBuilder(); - headerSupplier.get().forEach(newBuilder::addHeader); - Request newRequest = newBuilder.build(); - return chain.proceed(newRequest); - }); - return this; - } - - @Override - public TelemetryExporterBuilder setProxyOptions(ProxyOptions proxyOptions) { - builder.clientBuilder().proxy(proxyOptions.getProxySelector().select(URI.create("localhost")).get(0)); - return this; - } - - @Override - public CloseableSpanHandler build() { - OtelEncoder otelEncoder = new OtelEncoder(Tags.ERROR); - OkHttpSender sender = builder.build(); - InMemoryReporterMetrics reporterMetrics = new InMemoryReporterMetrics(); - AsyncReporter reporter = AsyncReporter.builder(sender) - .metrics(reporterMetrics) - .build(otelEncoder); - return new CloseableSpanHandler(reporter, reporterMetrics); - } -} diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/OtelToZipkinSpanTransformerTest.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/OtelToZipkinSpanTransformerTest.java new file mode 100644 index 0000000..c300354 --- /dev/null +++ b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/OtelToZipkinSpanTransformerTest.java @@ -0,0 +1,896 @@ +/* + * Copyright 2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package zipkin2.reporter.otel.brave; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static org.assertj.core.api.Assertions.assertThat; + +import brave.Span; +import brave.Span.Kind; +import brave.Tags; +import brave.handler.MutableSpan; +import com.google.protobuf.ByteString; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.proto.common.v1.AnyValue; +import io.opentelemetry.proto.common.v1.KeyValue; +import io.opentelemetry.proto.trace.v1.ResourceSpans; +import io.opentelemetry.proto.trace.v1.ScopeSpans; +import io.opentelemetry.proto.trace.v1.Span.Event; +import io.opentelemetry.proto.trace.v1.Span.SpanKind; +import io.opentelemetry.proto.trace.v1.Status; +import io.opentelemetry.proto.trace.v1.Status.StatusCode; +import io.opentelemetry.proto.trace.v1.TracesData; +import io.opentelemetry.sdk.resources.Resource; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +// Adopted from https://github.com/open-telemetry/opentelemetry-java/blob/v1.39.0/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/OtelToZipkinSpanTransformerTest.java +class OtelToZipkinSpanTransformerTest { + + static final String TRACE_ID = "d239036e7d5cec116b562147388b35bf"; + + static final String SPAN_ID = "9cc1e3049173be09"; + + static final String PARENT_SPAN_ID = "8b03ab423da481c5"; + + private OtelEncoder transformer; + + @BeforeEach + void setup() { + transformer = new OtelEncoder(Tags.ERROR); + } + + @Test + void generateSpan_remoteParent() { + MutableSpan data = span(Kind.SERVER); + + assertThat(transformer.translate(data)) + .isEqualTo( + TracesData.newBuilder().addResourceSpans(ResourceSpans + .newBuilder() + .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( + KeyValue.newBuilder().setKey("service.name").setValue( + AnyValue.newBuilder().setStringValue("tweetiebird").build()) + .build() + ).build()) + .addScopeSpans(ScopeSpans.newBuilder() + .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() + .setSpanId(ByteString.fromHex(data.id())) + .setTraceId(ByteString.fromHex(data.traceId())) + .setParentSpanId(ByteString.fromHex(data.parentId())) + .setName(data.name()) + .setStartTimeUnixNano(1505855794194009000L) + .setEndTimeUnixNano(1505855799465726000L) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes( + KeyValue.newBuilder().setKey("otel.library.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .setKind(io.opentelemetry.proto.trace.v1.Span.SpanKind.SPAN_KIND_SERVER) + .setStatus( + Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) + .addEvents(Event.newBuilder().setName("\"RECEIVED\":{}").setTimeUnixNano(1505855799433901000L).build()) + .addEvents(Event.newBuilder().setName("\"SENT\":{}").setTimeUnixNano(1505855799459486000L).build()) + .build()) + .build()) + .build()) + .build()); + } + + @Test + void generateSpan_subMicroDurations() { + MutableSpan data = span(Kind.SERVER); + data.startTimestamp(TimeUnit.NANOSECONDS.toMicros(1505855794_194009601L)); + data.finishTimestamp(TimeUnit.NANOSECONDS.toMicros(1505855794_194009999L)); + + assertThat(transformer.translate(data)) + .isEqualTo( + TracesData.newBuilder().addResourceSpans(ResourceSpans + .newBuilder() + .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( + KeyValue.newBuilder().setKey("service.name").setValue( + AnyValue.newBuilder().setStringValue("tweetiebird").build()) + .build() + ).build()) + .addScopeSpans(ScopeSpans.newBuilder() + .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() + .setSpanId(ByteString.fromHex(data.id())) + .setTraceId(ByteString.fromHex(data.traceId())) + .setParentSpanId(ByteString.fromHex(data.parentId())) + .setName(data.name()) + .setStartTimeUnixNano(1505855794_194009000L) // We lose precision + .setEndTimeUnixNano(1505855794_194009000L) // We lose precision + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes( + KeyValue.newBuilder().setKey("otel.library.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .setKind(io.opentelemetry.proto.trace.v1.Span.SpanKind.SPAN_KIND_SERVER) + .setStatus( + Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) + .addEvents(Event.newBuilder().setName("\"RECEIVED\":{}").setTimeUnixNano(1505855799433901000L).build()) + .addEvents(Event.newBuilder().setName("\"SENT\":{}").setTimeUnixNano(1505855799459486000L).build()) + .build()) + .build()) + .build()) + .build()); + } + + @Test + void generateSpan_ServerKind() { + MutableSpan data = span(Kind.SERVER); + + assertThat(transformer.translate(data)) + .isEqualTo( + TracesData.newBuilder().addResourceSpans(ResourceSpans + .newBuilder() + .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( + KeyValue.newBuilder().setKey("service.name").setValue( + AnyValue.newBuilder().setStringValue("tweetiebird").build()) + .build() + ).build()) + .addScopeSpans(ScopeSpans.newBuilder() + .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() + .setSpanId(ByteString.fromHex(data.id())) + .setTraceId(ByteString.fromHex(data.traceId())) + .setParentSpanId(ByteString.fromHex(data.parentId())) + .setName(data.name()) + .setStartTimeUnixNano(1505855794194009000L) + .setEndTimeUnixNano(1505855799465726000L) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes( + KeyValue.newBuilder().setKey("otel.library.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .setKind(io.opentelemetry.proto.trace.v1.Span.SpanKind.SPAN_KIND_SERVER) + .setStatus( + Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) + .addEvents(Event.newBuilder().setName("\"RECEIVED\":{}").setTimeUnixNano(1505855799433901000L).build()) + .addEvents(Event.newBuilder().setName("\"SENT\":{}").setTimeUnixNano(1505855799459486000L).build()) + .build()) + .build()) + .build()) + .build()); + } + + @Test + void generateSpan_ClientKind() { + MutableSpan data = span(Kind.CLIENT); + + assertThat(transformer.translate(data)) + .isEqualTo( + TracesData.newBuilder().addResourceSpans(ResourceSpans + .newBuilder() + .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( + KeyValue.newBuilder().setKey("service.name").setValue( + AnyValue.newBuilder().setStringValue("tweetiebird").build()) + .build() + ).build()) + .addScopeSpans(ScopeSpans.newBuilder() + .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() + .setSpanId(ByteString.fromHex(data.id())) + .setTraceId(ByteString.fromHex(data.traceId())) + .setParentSpanId(ByteString.fromHex(data.parentId())) + .setName(data.name()) + .setStartTimeUnixNano(1505855794194009000L) + .setEndTimeUnixNano(1505855799465726000L) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes( + KeyValue.newBuilder().setKey("otel.library.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .setKind(io.opentelemetry.proto.trace.v1.Span.SpanKind.SPAN_KIND_CLIENT) + .setStatus( + Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) + .addEvents(Event.newBuilder().setName("\"RECEIVED\":{}").setTimeUnixNano(1505855799433901000L).build()) + .addEvents(Event.newBuilder().setName("\"SENT\":{}").setTimeUnixNano(1505855799459486000L).build()) + .build()) + .build()) + .build()) + .build()); + } + + @Test + void generateSpan_InternalKind() { + MutableSpan data = span(null); + + assertThat(transformer.translate(data)) + .isEqualTo( + TracesData.newBuilder().addResourceSpans(ResourceSpans + .newBuilder() + .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( + KeyValue.newBuilder().setKey("service.name").setValue( + AnyValue.newBuilder().setStringValue("tweetiebird").build()) + .build() + ).build()) + .addScopeSpans(ScopeSpans.newBuilder() + .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() + .setSpanId(ByteString.fromHex(data.id())) + .setTraceId(ByteString.fromHex(data.traceId())) + .setParentSpanId(ByteString.fromHex(data.parentId())) + .setName(data.name()) + .setKind(SpanKind.SPAN_KIND_INTERNAL) + .setStartTimeUnixNano(1505855794194009000L) + .setEndTimeUnixNano(1505855799465726000L) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes( + KeyValue.newBuilder().setKey("otel.library.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .setKind(io.opentelemetry.proto.trace.v1.Span.SpanKind.SPAN_KIND_INTERNAL) + .setStatus( + Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) + .addEvents(Event.newBuilder().setName("\"RECEIVED\":{}").setTimeUnixNano(1505855799433901000L).build()) + .addEvents(Event.newBuilder().setName("\"SENT\":{}").setTimeUnixNano(1505855799459486000L).build()) + .build()) + .build()) + .build()) + .build()); + } + + @Test + void generateSpan_ConsumeKind() { + MutableSpan data = span(Kind.CONSUMER); + + assertThat(transformer.translate(data)) + .isEqualTo( + TracesData.newBuilder().addResourceSpans(ResourceSpans + .newBuilder() + .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( + KeyValue.newBuilder().setKey("service.name").setValue( + AnyValue.newBuilder().setStringValue("tweetiebird").build()) + .build() + ).build()) + .addScopeSpans(ScopeSpans.newBuilder() + .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() + .setSpanId(ByteString.fromHex(data.id())) + .setTraceId(ByteString.fromHex(data.traceId())) + .setParentSpanId(ByteString.fromHex(data.parentId())) + .setName(data.name()) + .setStartTimeUnixNano(1505855794194009000L) + .setEndTimeUnixNano(1505855799465726000L) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes( + KeyValue.newBuilder().setKey("otel.library.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .setKind(io.opentelemetry.proto.trace.v1.Span.SpanKind.SPAN_KIND_CONSUMER) + .setStatus( + Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) + .addEvents(Event.newBuilder().setName("\"RECEIVED\":{}").setTimeUnixNano(1505855799433901000L).build()) + .addEvents(Event.newBuilder().setName("\"SENT\":{}").setTimeUnixNano(1505855799459486000L).build()) + .build()) + .build()) + .build()) + .build()); + } + + @Test + void generateSpan_ProducerKind() { + MutableSpan data = span(Kind.PRODUCER); + + assertThat(transformer.translate(data)) + .isEqualTo( + TracesData.newBuilder().addResourceSpans(ResourceSpans + .newBuilder() + .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( + KeyValue.newBuilder().setKey("service.name").setValue( + AnyValue.newBuilder().setStringValue("tweetiebird").build()) + .build() + ).build()) + .addScopeSpans(ScopeSpans.newBuilder() + .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() + .setSpanId(ByteString.fromHex(data.id())) + .setTraceId(ByteString.fromHex(data.traceId())) + .setParentSpanId(ByteString.fromHex(data.parentId())) + .setName(data.name()) + .setStartTimeUnixNano(1505855794194009000L) + .setEndTimeUnixNano(1505855799465726000L) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes( + KeyValue.newBuilder().setKey("otel.library.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .setKind(io.opentelemetry.proto.trace.v1.Span.SpanKind.SPAN_KIND_PRODUCER) + .setStatus( + Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) + .addEvents(Event.newBuilder().setName("\"RECEIVED\":{}").setTimeUnixNano(1505855799433901000L).build()) + .addEvents(Event.newBuilder().setName("\"SENT\":{}").setTimeUnixNano(1505855799459486000L).build()) + .build()) + .build()) + .build()) + .build()); + } + + @Test + void generateSpan_ResourceServiceNameMapping() { + MutableSpan data = span(Kind.PRODUCER); + data.localServiceName("super-zipkin-service"); + + assertThat(transformer.translate(data)) + .isEqualTo( + TracesData.newBuilder().addResourceSpans(ResourceSpans + .newBuilder() + .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( + KeyValue.newBuilder().setKey("service.name").setValue( + AnyValue.newBuilder().setStringValue("super-zipkin-service").build()) + .build() + ).build()) + .addScopeSpans(ScopeSpans.newBuilder() + .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() + .setSpanId(ByteString.fromHex(data.id())) + .setTraceId(ByteString.fromHex(data.traceId())) + .setParentSpanId(ByteString.fromHex(data.parentId())) + .setName(data.name()) + .setStartTimeUnixNano(1505855794194009000L) + .setEndTimeUnixNano(1505855799465726000L) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes( + KeyValue.newBuilder().setKey("otel.library.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .setKind(SpanKind.SPAN_KIND_PRODUCER) + .setStatus( + Status.newBuilder().setCode(StatusCode.STATUS_CODE_OK).build()) + .addEvents(Event.newBuilder().setName("\"RECEIVED\":{}").setTimeUnixNano(1505855799433901000L).build()) + .addEvents(Event.newBuilder().setName("\"SENT\":{}").setTimeUnixNano(1505855799459486000L).build()) + .build()) + .build()) + .build()) + .build()); + } + + @Test + void generateSpan_defaultResourceServiceName() { + MutableSpan data = span(Kind.PRODUCER); + data.localServiceName(null); + + assertThat(transformer.translate(data)) + .isEqualTo( + TracesData.newBuilder().addResourceSpans(ResourceSpans + .newBuilder() + .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( + KeyValue.newBuilder().setKey("service.name").setValue(AnyValue.newBuilder() + .setStringValue( + Resource.getDefault().getAttribute(stringKey("service.name"))).build()) + .build() + ).build()) + .addScopeSpans(ScopeSpans.newBuilder() + .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() + .setSpanId(ByteString.fromHex(data.id())) + .setTraceId(ByteString.fromHex(data.traceId())) + .setParentSpanId(ByteString.fromHex(data.parentId())) + .setName(data.name()) + .setStartTimeUnixNano(1505855794194009000L) + .setEndTimeUnixNano(1505855799465726000L) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes( + KeyValue.newBuilder().setKey("otel.library.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .setKind(io.opentelemetry.proto.trace.v1.Span.SpanKind.SPAN_KIND_PRODUCER) + .setStatus( + Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) + .addEvents(Event.newBuilder().setName("\"RECEIVED\":{}").setTimeUnixNano(1505855799433901000L).build()) + .addEvents(Event.newBuilder().setName("\"SENT\":{}").setTimeUnixNano(1505855799459486000L).build()) + .build()) + .build()) + .build()) + .build()); + } + + @ParameterizedTest + @EnumSource( + value = Kind.class, + names = {"CLIENT", "PRODUCER"}) + void generateSpan_RemoteEndpointMapping(Kind spanKind) { + MutableSpan data = new MutableSpan(); + data.remoteServiceName("remote-test-service"); + data.remoteIp("8.8.8.8"); + data.remotePort(42); + data.kind(spanKind); + + assertThat(transformer.translate(data)) + .isEqualTo( + TracesData.newBuilder().addResourceSpans(ResourceSpans + .newBuilder() + .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( + KeyValue.newBuilder().setKey("service.name").setValue(AnyValue.newBuilder() + .setStringValue( + Resource.getDefault().getAttribute(stringKey("service.name"))).build()) + .build() + ).build()) + .addScopeSpans(ScopeSpans.newBuilder() + .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() + .setSpanId(ByteString.fromHex(SpanContext.getInvalid().getSpanId())) + .setTraceId(ByteString.fromHex(SpanContext.getInvalid().getTraceId())) + .setName("unknown") + .setKind( + toSpanKind(spanKind)) + .addAttributes(KeyValue.newBuilder().setKey("net.sock.peer.name").setValue( + AnyValue.newBuilder().setStringValue("remote-test-service").build()) + .build()) + .addAttributes(KeyValue.newBuilder().setKey("net.sock.peer.addr") + .setValue(AnyValue.newBuilder().setStringValue("8.8.8.8").build()) + .build()) + .addAttributes(KeyValue.newBuilder().setKey("net.sock.peer.port") + .setValue(AnyValue.newBuilder().setIntValue(42).build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes( + KeyValue.newBuilder().setKey("otel.library.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .setStatus( + Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) + .build()) + .build()) + .build()) + .build()); + } + + @ParameterizedTest + @EnumSource( + value = Kind.class, + names = {"SERVER", "CONSUMER"}) + void generateSpan_RemoteEndpointMappingWhenKindIsNotClientOrProducer(Kind spanKind) { + MutableSpan data = new MutableSpan(); + data.remoteServiceName("remote-test-service"); + data.remoteIp("8.8.8.8"); + data.remotePort(42); + data.kind(spanKind); + + assertThat(transformer.translate(data)) + .isEqualTo( + TracesData.newBuilder().addResourceSpans(ResourceSpans + .newBuilder() + .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( + KeyValue.newBuilder().setKey("service.name").setValue(AnyValue.newBuilder() + .setStringValue( + Resource.getDefault().getAttribute(stringKey("service.name"))).build()) + .build() + ).build()) + .addScopeSpans(ScopeSpans.newBuilder() + .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() + .setSpanId(ByteString.fromHex(SpanContext.getInvalid().getSpanId())) + .setTraceId(ByteString.fromHex(SpanContext.getInvalid().getTraceId())) + .setName("unknown") + .setKind( + toSpanKind(spanKind)) + .addAttributes(KeyValue.newBuilder().setKey("net.sock.peer.name").setValue( + AnyValue.newBuilder().setStringValue("remote-test-service").build()) + .build()) + .addAttributes(KeyValue.newBuilder().setKey("net.sock.peer.addr") + .setValue(AnyValue.newBuilder().setStringValue("8.8.8.8").build()) + .build()) + .addAttributes(KeyValue.newBuilder().setKey("net.sock.peer.port") + .setValue(AnyValue.newBuilder().setIntValue(42).build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes( + KeyValue.newBuilder().setKey("otel.library.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .setStatus( + Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) + .build()) + .build()) + .build()) + .build()); + } + + @ParameterizedTest + @EnumSource( + value = Kind.class, + names = {"CLIENT", "PRODUCER"}) + void generateSpan_RemoteEndpointMappingWhenServiceNameIsMissing(Kind spanKind) { + MutableSpan data = new MutableSpan(); + data.remoteIp("8.8.8.8"); + data.remotePort(42); + data.kind(spanKind); + + assertThat(transformer.translate(data)) + .isEqualTo( + TracesData.newBuilder().addResourceSpans(ResourceSpans + .newBuilder() + .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( + KeyValue.newBuilder().setKey("service.name").setValue(AnyValue.newBuilder() + .setStringValue( + Resource.getDefault().getAttribute(stringKey("service.name"))).build()) + .build() + ).build()) + .addScopeSpans(ScopeSpans.newBuilder() + .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() + .setSpanId(ByteString.fromHex(SpanContext.getInvalid().getSpanId())) + .setTraceId(ByteString.fromHex(SpanContext.getInvalid().getTraceId())) + .setName("unknown") + .setKind( + toSpanKind(spanKind)) + .addAttributes(KeyValue.newBuilder().setKey("net.sock.peer.addr") + .setValue(AnyValue.newBuilder().setStringValue("8.8.8.8").build()) + .build()) + .addAttributes(KeyValue.newBuilder().setKey("net.sock.peer.port") + .setValue(AnyValue.newBuilder().setIntValue(42).build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes( + KeyValue.newBuilder().setKey("otel.library.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .setStatus( + Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) + .build()) + .build()) + .build()) + .build()); + } + + @ParameterizedTest + @EnumSource( + value = Kind.class, + names = {"CLIENT", "PRODUCER"}) + void generateSpan_RemoteEndpointMappingWhenPortIsMissing(Kind spanKind) { + MutableSpan data = new MutableSpan(); + data.remoteServiceName("remote-test-service"); + data.remoteIp("8.8.8.8"); + data.kind(spanKind); + + assertThat(transformer.translate(data)) + .isEqualTo( + TracesData.newBuilder().addResourceSpans(ResourceSpans + .newBuilder() + .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( + KeyValue.newBuilder().setKey("service.name").setValue(AnyValue.newBuilder() + .setStringValue( + Resource.getDefault().getAttribute(stringKey("service.name"))).build()) + .build() + ).build()) + .addScopeSpans(ScopeSpans.newBuilder() + .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() + .setSpanId(ByteString.fromHex(SpanContext.getInvalid().getSpanId())) + .setTraceId(ByteString.fromHex(SpanContext.getInvalid().getTraceId())) + .setName("unknown") + .setKind( + toSpanKind(spanKind)) + .addAttributes(KeyValue.newBuilder().setKey("net.sock.peer.name").setValue( + AnyValue.newBuilder().setStringValue("remote-test-service").build()) + .build()) + .addAttributes(KeyValue.newBuilder().setKey("net.sock.peer.addr") + .setValue(AnyValue.newBuilder().setStringValue("8.8.8.8").build()) + .build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes( + KeyValue.newBuilder().setKey("otel.library.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .setStatus( + Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) + .build()) + .build()) + .build()) + .build()); + } + + @ParameterizedTest + @EnumSource( + value = Kind.class, + names = {"CLIENT", "PRODUCER"}) + void generateSpan_RemoteEndpointMappingWhenIpAndPortAreMissing(Kind spanKind) { + MutableSpan data = new MutableSpan(); + data.remoteServiceName("remote-test-service"); + data.kind(spanKind); + + assertThat(transformer.translate(data)) + .isEqualTo( + TracesData.newBuilder().addResourceSpans(ResourceSpans + .newBuilder() + .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( + KeyValue.newBuilder().setKey("service.name").setValue(AnyValue.newBuilder() + .setStringValue( + Resource.getDefault().getAttribute(stringKey("service.name"))).build()) + .build() + ).build()) + .addScopeSpans(ScopeSpans.newBuilder() + .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() + .setSpanId(ByteString.fromHex(SpanContext.getInvalid().getSpanId())) + .setTraceId(ByteString.fromHex(SpanContext.getInvalid().getTraceId())) + .setName("unknown") + .setKind( + toSpanKind(spanKind)) + .addAttributes(KeyValue.newBuilder().setKey("net.sock.peer.name").setValue( + AnyValue.newBuilder().setStringValue("remote-test-service").build()) + .build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes( + KeyValue.newBuilder().setKey("otel.library.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .setStatus( + Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) + .build()) + .build()) + .build()) + .build()); + } + + @Test + void generateSpan_WithAttributes() { + MutableSpan data = new MutableSpan(); + data.kind(Kind.CLIENT); + data.tag("string", "string value"); + data.tag("boolean", "false"); + data.tag("long", "9999"); + data.tag("double", "222.333"); + data.tag("booleanArray", "true,false"); + data.tag("stringArray", "Hello"); + data.tag("doubleArray", "32.33,-98.3"); + data.tag("longArray", "33,999"); + + assertThat(transformer.translate(data)) + .isEqualTo( + TracesData.newBuilder().addResourceSpans(ResourceSpans + .newBuilder() + .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( + KeyValue.newBuilder().setKey("service.name").setValue(AnyValue.newBuilder() + .setStringValue( + Resource.getDefault().getAttribute(stringKey("service.name"))).build()) + .build() + ).build()) + .addScopeSpans(ScopeSpans.newBuilder() + .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() + .setSpanId(ByteString.fromHex(SpanContext.getInvalid().getSpanId())) + .setTraceId(ByteString.fromHex(SpanContext.getInvalid().getTraceId())) + .setName("unknown") + .setKind( + toSpanKind(Kind.CLIENT)) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes( + KeyValue.newBuilder().setKey("otel.library.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("string").setValue( + AnyValue.newBuilder().setStringValue("string value").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("boolean").setValue( + AnyValue.newBuilder().setStringValue("false").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("long").setValue( + AnyValue.newBuilder().setStringValue("9999").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("double").setValue( + AnyValue.newBuilder().setStringValue("222.333").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("booleanArray").setValue( + AnyValue.newBuilder().setStringValue("true,false").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("stringArray").setValue( + AnyValue.newBuilder().setStringValue("Hello").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("doubleArray").setValue( + AnyValue.newBuilder().setStringValue("32.33,-98.3").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("longArray").setValue( + AnyValue.newBuilder().setStringValue("33,999").build()).build()) + .setStatus( + Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) + .build()) + .build()) + .build()) + .build()); + } + + @Test + void generateSpan_WithInstrumentationLibraryInfo() { + MutableSpan data = new MutableSpan(); + data.kind(Kind.CLIENT); + + assertThat(transformer.translate(data)) + .isEqualTo( + TracesData.newBuilder().addResourceSpans(ResourceSpans + .newBuilder() + .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( + KeyValue.newBuilder().setKey("service.name").setValue(AnyValue.newBuilder() + .setStringValue( + Resource.getDefault().getAttribute(stringKey("service.name"))).build()) + .build() + ).build()) + .addScopeSpans(ScopeSpans.newBuilder() + .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() + .setSpanId(ByteString.fromHex(SpanContext.getInvalid().getSpanId())) + .setTraceId(ByteString.fromHex(SpanContext.getInvalid().getTraceId())) + .setName("unknown") + .setKind( + toSpanKind(Kind.CLIENT)) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes( + KeyValue.newBuilder().setKey("otel.library.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .setStatus( + Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) + .build()) + .build()) + .build()) + .build()); + } + + @Test + void generateSpan_AlreadyHasHttpStatusInfo() { + MutableSpan data = new MutableSpan(); + data.kind(Kind.CLIENT); + data.error(new RuntimeException("A user provided error")); + data.tag("http.response.status.code", "404"); + + assertThat(transformer.translate(data)) + .isEqualTo( + TracesData.newBuilder().addResourceSpans(ResourceSpans + .newBuilder() + .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( + KeyValue.newBuilder().setKey("service.name").setValue(AnyValue.newBuilder() + .setStringValue( + Resource.getDefault().getAttribute(stringKey("service.name"))).build()) + .build() + ).build()) + .addScopeSpans(ScopeSpans.newBuilder() + .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() + .setSpanId(ByteString.fromHex(SpanContext.getInvalid().getSpanId())) + .setTraceId(ByteString.fromHex(SpanContext.getInvalid().getTraceId())) + .setName("unknown") + .setKind( + toSpanKind(Kind.CLIENT)) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build()) + .addAttributes( + KeyValue.newBuilder().setKey("otel.library.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) + .addAttributes( + KeyValue.newBuilder().setKey("http.response.status.code").setValue( + AnyValue.newBuilder().setStringValue("404").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("error").setValue( + AnyValue.newBuilder().setStringValue("A user provided error").build()) + .build()) + .setStatus( + Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_ERROR) + .build()) + .build()) + .build()) + .build()) + .build()); + } + + static MutableSpan span(@Nullable Span.Kind kind) { + MutableSpan mutableSpan = new MutableSpan(); + mutableSpan.traceId(TRACE_ID); + mutableSpan.parentId(PARENT_SPAN_ID); + mutableSpan.id(SPAN_ID); + mutableSpan.kind(kind); + mutableSpan.name("Recv.helloworld.Greeter.SayHello"); + mutableSpan.startTimestamp(1505855794000000L + 194009601L / 1000); + mutableSpan.finishTimestamp(1505855799000000L + 465726528L / 1000); + mutableSpan.localServiceName("tweetiebird"); + mutableSpan.annotate(1505855799000000L + 433901068L / 1000, "\"RECEIVED\":{}"); + mutableSpan.annotate(1505855799000000L + 459486280L / 1000, "\"SENT\":{}"); + return mutableSpan; + } + + static io.opentelemetry.proto.trace.v1.Span.SpanKind toSpanKind(Span.Kind kind) { + switch (kind) { + case CLIENT: + return SpanKind.SPAN_KIND_CLIENT; + case SERVER: + return SpanKind.SPAN_KIND_SERVER; + case PRODUCER: + return SpanKind.SPAN_KIND_PRODUCER; + case CONSUMER: + return SpanKind.SPAN_KIND_CONSUMER; + } + return SpanKind.SPAN_KIND_INTERNAL; + } +} diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/OtlpHttpSpanExporterOkHttpSenderTest.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/OtlpHttpSpanExporterOkHttpSenderTest.java deleted file mode 100644 index db20911..0000000 --- a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/OtlpHttpSpanExporterOkHttpSenderTest.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin2.reporter.otel.brave; - -import brave.handler.MutableSpan; -import io.opentelemetry.proto.trace.v1.ResourceSpans; -import zipkin2.reporter.okhttp3.OkHttpSender; - -/** - * Taken from https://github.com/open-telemetry/opentelemetry-java/blob/v1.39.0/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/OtlpHttpSpanExporterOkHttpSenderTest.java - */ -class OtlpHttpSpanExporterOkHttpSenderTest - extends AbstractHttpTelemetryExporterTest { - - protected OtlpHttpSpanExporterOkHttpSenderTest() { - super("/v1/traces"); - } - - @Override - protected TelemetryExporterBuilder exporterBuilder() { - return new HttpSpanExporterBuilderWrapper(OkHttpSender.newBuilder()); - } - - @Override - protected MutableSpan generateFakeTelemetry() { - return FakeTelemetryUtil.generateFakeSpanData(); - } -} diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/SpanTranslatorTest.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/SpanTranslatorTest.java index 307c34e..c4c67e1 100644 --- a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/SpanTranslatorTest.java +++ b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/SpanTranslatorTest.java @@ -25,6 +25,8 @@ import io.opentelemetry.proto.trace.v1.Span; import io.opentelemetry.proto.trace.v1.Span.Event; import io.opentelemetry.proto.trace.v1.Span.SpanKind; +import io.opentelemetry.proto.trace.v1.Status; +import io.opentelemetry.proto.trace.v1.Status.StatusCode; import io.opentelemetry.proto.trace.v1.TracesData; import java.time.Instant; import java.util.Arrays; @@ -66,6 +68,16 @@ void translate_clientSpan() { AnyValue.newBuilder().setStringValue("192.168.99.101").build()).build(), KeyValue.newBuilder().setKey("net.sock.peer.port").setValue( AnyValue.newBuilder().setIntValue(9000).build()).build(), + KeyValue.newBuilder().setKey("otel.scope.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build(), + KeyValue.newBuilder().setKey("otel.scope.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build(), + KeyValue.newBuilder().setKey("otel.library.name").setValue( + AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) + .build(), + KeyValue.newBuilder().setKey("otel.library.version").setValue( + AnyValue.newBuilder().setStringValue("0.0.1").build()).build(), KeyValue.newBuilder().setKey("clnt/finagle.version").setValue( AnyValue.newBuilder().setStringValue("6.45.0").build()).build(), KeyValue.newBuilder().setKey("http.path").setValue( @@ -79,6 +91,7 @@ void translate_clientSpan() { Event.newBuilder().setTimeUnixNano(TimeUnit.MILLISECONDS.toNanos( Instant.ofEpochSecond(1472470996, 403_000_000).toEpochMilli())) .setName("bar").build())) + .setStatus(Status.newBuilder().setCode(StatusCode.STATUS_CODE_OK).build()) .build()); } diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/TelemetryExporterBuilder.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/TelemetryExporterBuilder.java deleted file mode 100644 index 14c0381..0000000 --- a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/TelemetryExporterBuilder.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin2.reporter.otel.brave; - -import io.opentelemetry.sdk.common.export.ProxyOptions; -import java.time.Duration; -import java.util.Map; -import java.util.function.Supplier; - -/** - * Taken from https://github.com/open-telemetry/opentelemetry-java/blob/v1.39.0/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/TelemetryExporterBuilder.java - */ -public interface TelemetryExporterBuilder { - - TelemetryExporterBuilder setEndpoint(String endpoint); - - TelemetryExporterBuilder setTimeout(Duration timeout); - - TelemetryExporterBuilder setConnectTimeout(Duration timeout); - - TelemetryExporterBuilder setCompression(String compression); - - TelemetryExporterBuilder addHeader(String key, String value); - - TelemetryExporterBuilder setHeaders(Supplier> headerSupplier); - - TelemetryExporterBuilder setProxyOptions(ProxyOptions proxyOptions); - - CloseableSpanHandler build(); -} From d0d2fe2d1a5331f86c51db87efd9f689d9eab612 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 25 Jun 2024 17:58:05 +0200 Subject: [PATCH 18/30] Added Zipkin tests --- translation-otel/pom.xml | 4 + .../zipkin/AttributesExtractor.java | 6 +- .../translation/zipkin/SpanTranslator.java | 364 ++++++++++++++---- .../zipkin/SpanTranslatorTest.java | 161 ++++++++ 4 files changed, 455 insertions(+), 80 deletions(-) create mode 100644 translation-otel/src/test/java/zipkin2/translation/zipkin/SpanTranslatorTest.java diff --git a/translation-otel/pom.xml b/translation-otel/pom.xml index c273f17..347c7ea 100644 --- a/translation-otel/pom.xml +++ b/translation-otel/pom.xml @@ -39,6 +39,10 @@ io.opentelemetry.proto opentelemetry-proto + + io.opentelemetry + opentelemetry-sdk-common + io.opentelemetry.semconv opentelemetry-semconv diff --git a/translation-otel/src/main/java/zipkin2/translation/zipkin/AttributesExtractor.java b/translation-otel/src/main/java/zipkin2/translation/zipkin/AttributesExtractor.java index 9baa04e..b6af810 100644 --- a/translation-otel/src/main/java/zipkin2/translation/zipkin/AttributesExtractor.java +++ b/translation-otel/src/main/java/zipkin2/translation/zipkin/AttributesExtractor.java @@ -15,6 +15,7 @@ import io.opentelemetry.proto.common.v1.AnyValue; import io.opentelemetry.proto.common.v1.KeyValue; +import io.opentelemetry.proto.trace.v1.Span; import java.util.Map; /** @@ -38,9 +39,8 @@ final class AttributesExtractor { this.renamedLabels = renamedLabels; } - void addTag(KeyValue.Builder target, String key, String value) { - target.setKey(getLabelName(key)).setValue( - AnyValue.newBuilder().setStringValue(value).build()); + void addTag(Span.Builder target, String key, String value) { + target.addAttributes(KeyValue.newBuilder().setKey(getLabelName(key)).setValue(AnyValue.newBuilder().setStringValue(value).build()).build()).build(); } private String getLabelName(String zipkinName) { diff --git a/translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java b/translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java index bbe6677..b2988b1 100644 --- a/translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java +++ b/translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java @@ -13,7 +13,14 @@ */ package zipkin2.translation.zipkin; +import static io.opentelemetry.api.common.AttributeKey.longKey; +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static java.util.stream.Collectors.joining; + import com.google.protobuf.ByteString; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; import io.opentelemetry.proto.common.v1.AnyValue; import io.opentelemetry.proto.common.v1.KeyValue; @@ -21,21 +28,33 @@ import io.opentelemetry.proto.trace.v1.ResourceSpans.Builder; import io.opentelemetry.proto.trace.v1.ScopeSpans; import io.opentelemetry.proto.trace.v1.Span; +import io.opentelemetry.proto.trace.v1.Span.Event; import io.opentelemetry.proto.trace.v1.Span.SpanKind; +import io.opentelemetry.proto.trace.v1.Status; +import io.opentelemetry.proto.trace.v1.Status.StatusCode; import io.opentelemetry.proto.trace.v1.TracesData; +import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.semconv.HttpAttributes; import io.opentelemetry.semconv.NetworkAttributes; import io.opentelemetry.semconv.SemanticAttributes; import io.opentelemetry.semconv.ServerAttributes; import io.opentelemetry.semconv.ServiceAttributes; import io.opentelemetry.semconv.UrlAttributes; +import java.net.InetAddress; +import java.net.NetworkInterface; import java.util.ArrayList; +import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; import zipkin2.Endpoint; import zipkin2.Span.Kind; +import zipkin2.internal.Nullable; /** @@ -45,6 +64,22 @@ public final class SpanTranslator { static final AttributesExtractor ATTRIBUTES_EXTRACTOR; + + private static final AttributeKey SERVICE_NAME = AttributeKey.stringKey("service.name"); + private static final AttributeKey PEER_SERVICE = stringKey("peer.service"); + private static final AttributeKey SERVER_SOCKET_ADDRESS = + stringKey("server.socket.address"); + private static final AttributeKey SERVER_SOCKET_PORT = longKey("server.socket.port"); + + + static final String KEY_INSTRUMENTATION_SCOPE_NAME = "otel.scope.name"; + static final String KEY_INSTRUMENTATION_SCOPE_VERSION = "otel.scope.version"; + static final String KEY_INSTRUMENTATION_LIBRARY_NAME = "otel.library.name"; + static final String KEY_INSTRUMENTATION_LIBRARY_VERSION = "otel.library.version"; + static final String OTEL_DROPPED_ATTRIBUTES_COUNT = "otel.dropped_attributes_count"; + static final String OTEL_DROPPED_EVENTS_COUNT = "otel.dropped_events_count"; + static final String OTEL_STATUS_CODE = "otel.status_code"; + static { Map renamedLabels = new LinkedHashMap<>(); renamedLabels.put("http.host", ServerAttributes.SERVER_ADDRESS.getKey()); @@ -84,14 +119,16 @@ public static TracesData translate(zipkin2.Span zipkinSpan) { private static Span.Builder builderForSingleSpan(zipkin2.Span span, Builder resourceSpansBuilder) { Span.Builder spanBuilder = Span.newBuilder() - .setTraceId(ByteString.fromHex(span.traceId())) - .setSpanId(ByteString.fromHex(span.id())) - .setName(span.name()); + .setTraceId(ByteString.fromHex(span.traceId() != null ? span.traceId() + : io.opentelemetry.api.trace.SpanContext.getInvalid().getTraceId())) + .setSpanId(ByteString.fromHex(span.id() != null ? span.id() + : io.opentelemetry.api.trace.SpanContext.getInvalid().getSpanId())) + .setName((span.name() == null || span.name().isEmpty()) ? "unknown" : span.name()); if (span.parentId() != null) { spanBuilder.setParentSpanId(ByteString.fromHex(span.parentId())); } - long start = span.timestamp(); - long finish = span.timestampAsLong() + span.durationAsLong(); + Long start = span.timestamp() != null ? span.timestamp() : NANOSECONDS.toMicros(System.nanoTime()); + Long finish = span.timestampAsLong() + span.durationAsLong(); spanBuilder.setStartTimeUnixNano(TimeUnit.MICROSECONDS.toNanos(start)); if (start != 0 && finish != 0L) { spanBuilder.setEndTimeUnixNano(TimeUnit.MICROSECONDS.toNanos(finish)); @@ -120,6 +157,11 @@ private static Span.Builder builderForSingleSpan(zipkin2.Span span, resourceSpansBuilder.getResourceBuilder().addAttributes( KeyValue.newBuilder().setKey(ServiceAttributes.SERVICE_NAME.getKey()) .setValue(AnyValue.newBuilder().setStringValue(localServiceName).build()).build()); + } else { + resourceSpansBuilder.getResourceBuilder() + .addAttributes(KeyValue.newBuilder().setKey("service.name").setValue( + AnyValue.newBuilder().setStringValue( + Resource.getDefault().getAttribute(stringKey("service.name"))).build()).build()); } String localIp = span.localEndpoint() != null ? span.localEndpoint().ipv4() : null; if (localIp != null) { @@ -151,7 +193,7 @@ private static Span.Builder builderForSingleSpan(zipkin2.Span span, .setValue(AnyValue.newBuilder().setIntValue(peerPort).build()).build()); } span.tags() - .forEach((key, value) -> ATTRIBUTES_EXTRACTOR.addTag(KeyValue.newBuilder(), key, value)); + .forEach((key, value) -> ATTRIBUTES_EXTRACTOR.addTag(spanBuilder, key, value)); span.annotations().forEach(annotation -> spanBuilder.addEventsBuilder() .setTimeUnixNano(TimeUnit.MICROSECONDS.toNanos(annotation.timestamp())) .setName(annotation.value())); @@ -174,76 +216,9 @@ public static List translate(ExportTraceServiceRequest otelSpans) List spans = new ArrayList<>(); List spansList = otelSpans.getResourceSpansList(); for (ResourceSpans resourceSpans : spansList) { - // TODO: Use semantic attributes - KeyValue localServiceName = getValueFromAttributes("service.name", resourceSpans); - KeyValue localIp = getValueFromAttributes("net.host.ip", resourceSpans); - KeyValue localPort = getValueFromAttributes("net.host.port", resourceSpans); - KeyValue peerName = getValueFromAttributes("net.sock.peer.name", resourceSpans); - KeyValue peerIp = getValueFromAttributes("net.sock.peer.addr", resourceSpans); - KeyValue peerPort = getValueFromAttributes("net.sock.peer.port", resourceSpans); for (ScopeSpans scopeSpans : resourceSpans.getScopeSpansList()) { for (io.opentelemetry.proto.trace.v1.Span span : scopeSpans.getSpansList()) { - zipkin2.Span.Builder builder = zipkin2.Span.newBuilder(); - builder.name(span.getName()); - builder.traceId(OtelEncodingUtils.traceIdFromBytes(span.getTraceId().toByteArray())); - builder.id(OtelEncodingUtils.spanIdFromBytes(span.getSpanId().toByteArray())); - ByteString parent = span.getParentSpanId(); - if (parent != null) { - builder.parentId(OtelEncodingUtils.spanIdFromBytes(parent.toByteArray())); - } - long startMicros = TimeUnit.NANOSECONDS.toMicros(span.getStartTimeUnixNano()); - builder.timestamp(startMicros); - builder.duration(TimeUnit.NANOSECONDS.toMicros(span.getEndTimeUnixNano()) - startMicros); - SpanKind spanKind = span.getKind(); - switch (spanKind) { - case SPAN_KIND_UNSPECIFIED: - break; - case SPAN_KIND_INTERNAL: - break; - case SPAN_KIND_SERVER: - builder.kind(Kind.SERVER); - break; - case SPAN_KIND_CLIENT: - builder.kind(Kind.CLIENT); - break; - case SPAN_KIND_PRODUCER: - builder.kind(Kind.PRODUCER); - break; - case SPAN_KIND_CONSUMER: - builder.kind(Kind.CONSUMER); - break; - case UNRECOGNIZED: - break; - } - Endpoint.Builder localEndpointBuilder = Endpoint.newBuilder(); - if (localServiceName != null) { - localEndpointBuilder.serviceName(localServiceName.getValue().getStringValue()); - } - if (localPort != null) { - localEndpointBuilder.port((int) localPort.getValue().getIntValue()); - } - if (localIp != null) { - localEndpointBuilder.ip(localIp.getValue().getStringValue()); - } - builder.localEndpoint(localEndpointBuilder.build()); - Endpoint.Builder remoteEndpointBuilder = Endpoint.newBuilder(); - if (peerName != null) { - remoteEndpointBuilder.serviceName(peerName.getValue().getStringValue()); - } - if (peerPort != null) { - remoteEndpointBuilder.port((int) peerPort.getValue().getIntValue()); - } - if (peerIp != null) { - remoteEndpointBuilder.ip(peerIp.getValue().getStringValue()); - } - builder.remoteEndpoint(remoteEndpointBuilder.build()); - // TODO: Remove the ones from above - span.getAttributesList().forEach( - keyValue -> builder.putTag(keyValue.getKey(), keyValue.getValue().getStringValue())); - span.getEventsList().forEach( - event -> builder.addAnnotation(TimeUnit.NANOSECONDS.toMicros(event.getTimeUnixNano()), - event.getName())); - spans.add(builder.shared(false).build()); + spans.add(generateSpan(span, resourceSpans)); } } } @@ -251,11 +226,246 @@ public static List translate(ExportTraceServiceRequest otelSpans) } - private static KeyValue getValueFromAttributes(String key, ResourceSpans resourceSpans) { - return resourceSpans.getResource().getAttributesList().stream() - .filter(keyValue -> keyValue.getKey().equals(key)).findFirst().orElse(null); + /** + * Creates an instance of a Zipkin Span from an OpenTelemetry SpanData instance. + * + * @param spanData an OpenTelemetry spanData instance + * @return a new Zipkin Span + */ + private static zipkin2.Span generateSpan(io.opentelemetry.proto.trace.v1.Span spanData, ResourceSpans resourceSpans) { + long startTimestamp = toEpochMicros(spanData.getStartTimeUnixNano()); + long endTimestamp = toEpochMicros(spanData.getEndTimeUnixNano()); + + zipkin2.Span.Builder spanBuilder = + zipkin2.Span.newBuilder() + .traceId(OtelEncodingUtils.traceIdFromBytes(spanData.getTraceId().toByteArray())) + .id(OtelEncodingUtils.spanIdFromBytes(spanData.getSpanId().toByteArray())) + .kind(toSpanKind(spanData)) + .name(spanData.getName()) + .timestamp(toEpochMicros(spanData.getStartTimeUnixNano())) + .duration(Math.max(1, endTimestamp - startTimestamp)) + .localEndpoint(getLocalEndpoint(resourceSpans)) + .remoteEndpoint(getRemoteEndpoint(spanData)); + + if (spanData.getParentSpanId().isEmpty() || OtelEncodingUtils.spanIdFromBytes( + spanData.getParentSpanId().toByteArray()).equals(SpanContext.getInvalid().getSpanId())) { + spanBuilder.parentId( + OtelEncodingUtils.spanIdFromBytes(spanData.getParentSpanId().toByteArray())); + } + + List spanAttributes = spanData.getAttributesList(); + spanAttributes.forEach( + (kv) -> spanBuilder.putTag(kv.getKey(), valueToString(kv, kv.getValue()))); + int droppedAttributes = spanData.getAttributesCount() - spanAttributes.size(); + if (droppedAttributes > 0) { + spanBuilder.putTag(OTEL_DROPPED_ATTRIBUTES_COUNT, String.valueOf(droppedAttributes)); + } + + Status status = spanData.getStatus(); + + // include status code & error. + if (status.getCode() != Status.StatusCode.STATUS_CODE_UNSET) { + spanBuilder.putTag(OTEL_STATUS_CODE, status.getCode().toString()); + + // add the error tag, if it isn't already in the source span. + if (status.getCode() == StatusCode.STATUS_CODE_ERROR && spanAttributes.stream() + .anyMatch(keyValue -> keyValue.getKey().equals("error"))) { + spanBuilder.putTag("error", nullToEmpty(status.getMessage())); + } + } + + spanBuilder.putTag(KEY_INSTRUMENTATION_SCOPE_NAME, "zipkin2.reporter.otel"); + spanBuilder.putTag(KEY_INSTRUMENTATION_SCOPE_VERSION, "0.0.1"); + + // Include instrumentation library name for backwards compatibility + spanBuilder.putTag(KEY_INSTRUMENTATION_LIBRARY_NAME, "zipkin2.reporter.otel"); + // Include instrumentation library name for backwards compatibility + spanBuilder.putTag(KEY_INSTRUMENTATION_LIBRARY_VERSION, "0.0.1"); + + for (Event eventData : spanData.getEventsList()) { + String annotation = EventDataToAnnotation.apply(eventData); + spanBuilder.addAnnotation(toEpochMicros(eventData.getTimeUnixNano()), annotation); + } + int droppedEvents = spanData.getEventsCount() - spanData.getEventsList().size(); + if (droppedEvents > 0) { + spanBuilder.putTag(OTEL_DROPPED_EVENTS_COUNT, String.valueOf(droppedEvents)); + } + + return spanBuilder.shared(false).build(); + } + + private static String nullToEmpty(@Nullable String value) { + return value != null ? value : ""; + } + + private static Endpoint getLocalEndpoint(ResourceSpans spanData) { + List resourceAttributes = spanData.getResource().getAttributesList(); + + Endpoint.Builder endpoint = Endpoint.newBuilder(); + endpoint.ip(LocalInetAddressSupplier.findLocalIp()); + + // use the service.name from the Resource, if it's been set. + KeyValue serviceNameValue = resourceAttributes.stream().filter(keyValue -> SERVICE_NAME.getKey().equals( + keyValue.getKey())).findFirst().orElse(null); + String serviceName = null; + if (serviceNameValue == null) { + serviceName = Resource.getDefault().getAttribute(SERVICE_NAME); + } + // In practice should never be null unless the default Resource spec is changed. + if (serviceName != null) { + endpoint.serviceName(serviceName); + } + return endpoint.build(); + } + + @Nullable + private static Endpoint getRemoteEndpoint(Span spanData) { + if (spanData.getKind() == SpanKind.SPAN_KIND_CLIENT + || spanData.getKind() == SpanKind.SPAN_KIND_PRODUCER) { + // TODO: Implement fallback mechanism: + // https://opentelemetry.io/docs/reference/specification/trace/sdk_exporters/zipkin/#otlp---zipkin + List attributes = spanData.getAttributesList(); + String serviceName = attributes.stream().filter(keyValue -> PEER_SERVICE.getKey().equals(keyValue.getKey())).map(keyValue -> keyValue.getValue().getStringValue()).findFirst().orElse(null); + + if (serviceName != null) { + Endpoint.Builder endpoint = Endpoint.newBuilder(); + endpoint.serviceName(serviceName); + endpoint.ip(attributes.stream().filter(keyValue -> SERVER_SOCKET_ADDRESS.getKey().equals(keyValue.getKey())).map(keyValue -> keyValue.getValue().getStringValue()).findFirst().orElse(null)); + attributes.stream() + .filter(keyValue -> SERVER_SOCKET_PORT.getKey().equals(keyValue.getKey())) + .map(keyValue -> keyValue.getValue().getIntValue()).findFirst() + .ifPresent(port -> endpoint.port(port.intValue())); + + return endpoint.build(); + } + } + + return null; + } + + @Nullable + private static zipkin2.Span.Kind toSpanKind(io.opentelemetry.proto.trace.v1.Span spanData) { + switch (spanData.getKind()) { + case SPAN_KIND_UNSPECIFIED: + break; + case SPAN_KIND_INTERNAL: + break; + case SPAN_KIND_SERVER: + return zipkin2.Span.Kind.SERVER; + case SPAN_KIND_CLIENT: + return zipkin2.Span.Kind.CLIENT; + case SPAN_KIND_PRODUCER: + return zipkin2.Span.Kind.PRODUCER; + case SPAN_KIND_CONSUMER: + return zipkin2.Span.Kind.CONSUMER; + case UNRECOGNIZED: + break; + default: + return null; + } + return null; + } + + private static long toEpochMicros(long epochNanos) { + return NANOSECONDS.toMicros(epochNanos); + } + + private static String valueToString(KeyValue key, AnyValue attributeValue) { + if (attributeValue.hasArrayValue()) { + return commaSeparated(attributeValue.getArrayValue().getValuesList().stream().map( + AnyValue::getStringValue).collect( + Collectors.toList())); + } else if (attributeValue.hasStringValue()) { + return attributeValue.getStringValue(); + } + throw new IllegalStateException("Unknown attribute type"); + } + + private static String commaSeparated(List values) { + StringBuilder builder = new StringBuilder(); + for (Object value : values) { + if (builder.length() != 0) { + builder.append(','); + } + builder.append(value); + } + return builder.toString(); } + static final class EventDataToAnnotation { + + private EventDataToAnnotation() { + } + + static String apply(Event eventData) { + String name = eventData.getName(); + String value = toJson(eventData.getAttributesList()); + return "\"" + name + "\":" + value; + } + + private static String toJson(List attributes) { + return attributes.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); + } + } + + static class LocalInetAddressSupplier implements Supplier { + + private static final Logger logger = Logger.getLogger(LocalInetAddressSupplier.class.getName()); + private static final LocalInetAddressSupplier INSTANCE = + new LocalInetAddressSupplier(findLocalIp()); + @Nullable private final InetAddress inetAddress; + + private LocalInetAddressSupplier(@Nullable InetAddress inetAddress) { + this.inetAddress = inetAddress; + } + + @Nullable + @Override + public InetAddress get() { + return inetAddress; + } + + /** Logic borrowed from brave.internal.Platform.produceLocalEndpoint */ + @Nullable + private static InetAddress findLocalIp() { + try { + Enumeration nics = NetworkInterface.getNetworkInterfaces(); + while (nics.hasMoreElements()) { + NetworkInterface nic = nics.nextElement(); + Enumeration addresses = nic.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress address = addresses.nextElement(); + if (address.isSiteLocalAddress()) { + return address; + } + } + } + } catch (Exception e) { + // don't crash the caller if there was a problem reading nics. + logger.log(Level.FINE, "error reading nics", e); + } + return null; + } + + static LocalInetAddressSupplier getInstance() { + return INSTANCE; + } + } + + /** * Taken from OpenTelemetry codebase. */ diff --git a/translation-otel/src/test/java/zipkin2/translation/zipkin/SpanTranslatorTest.java b/translation-otel/src/test/java/zipkin2/translation/zipkin/SpanTranslatorTest.java new file mode 100644 index 0000000..489f186 --- /dev/null +++ b/translation-otel/src/test/java/zipkin2/translation/zipkin/SpanTranslatorTest.java @@ -0,0 +1,161 @@ +/* + * Copyright 2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package zipkin2.translation.zipkin; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; + +import com.google.protobuf.ByteString; +import io.opentelemetry.proto.common.v1.AnyValue; +import io.opentelemetry.proto.common.v1.KeyValue; +import io.opentelemetry.proto.trace.v1.ResourceSpans; +import io.opentelemetry.proto.trace.v1.ScopeSpans; +import io.opentelemetry.proto.trace.v1.Span.Event; +import io.opentelemetry.proto.trace.v1.Span.SpanKind; +import io.opentelemetry.proto.trace.v1.TracesData; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; +import zipkin2.Endpoint; +import zipkin2.Span; + +public class SpanTranslatorTest { + + /** + * This test is intentionally sensitive, so changing other parts makes obvious impact here + */ + @Test + void translate_clientSpan() { + Span zipkinSpan = + Span.newBuilder() + .traceId("7180c278b62e8f6a216a2aea45d08fc9") + .parentId("6b221d5bc9e6496c") + .id("5b4185666d50f68b") + .name("get") + .kind(Span.Kind.CLIENT) + .localEndpoint(Endpoint.newBuilder().serviceName("frontend").build()) + .remoteEndpoint( + Endpoint.newBuilder() + .serviceName("backend") + .ip("192.168.99.101") + .port(9000) + .build()) + .timestamp(1_000_000L) // 1 second after epoch + .duration(123_456L) + .addAnnotation(1_123_000L, "foo") + .putTag("http.path", "/api") + .putTag("clnt/finagle.version", "6.45.0") + .build(); + + TracesData translated = SpanTranslator.translate(zipkinSpan); + + assertThat(translated) + .isEqualTo( + TracesData.newBuilder() + .addResourceSpans(ResourceSpans.newBuilder() + .setResource( + io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( + KeyValue.newBuilder().setKey("service.name").setValue( + AnyValue.newBuilder().setStringValue("frontend").build()) + .build() + ).build()) + .addScopeSpans(ScopeSpans.newBuilder() + .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() + .setSpanId(ByteString.fromHex(zipkinSpan.id())) + .setTraceId(ByteString.fromHex(zipkinSpan.traceId())) + .setParentSpanId(ByteString.fromHex(zipkinSpan.parentId())) + .setName("get") + .setKind(SpanKind.SPAN_KIND_CLIENT) + .setStartTimeUnixNano(TimeUnit.SECONDS.toNanos(1)) + .setEndTimeUnixNano(TimeUnit.SECONDS.toNanos(1) + 123_456_000) + .addAttributes( + KeyValue.newBuilder().setKey("net.sock.peer.name").setValue( + AnyValue.newBuilder().setStringValue("backend").build()) + .build()) + .addAttributes( + KeyValue.newBuilder().setKey("net.sock.peer.addr").setValue( + AnyValue.newBuilder().setStringValue("192.168.99.101").build()) + .build()) + .addAttributes( + KeyValue.newBuilder().setKey("net.sock.peer.port").setValue( + AnyValue.newBuilder().setIntValue(9000).build()).build()) + .addAttributes( + KeyValue.newBuilder().setKey("clnt/finagle.version").setValue( + AnyValue.newBuilder().setStringValue("6.45.0").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("http.path").setValue( + AnyValue.newBuilder().setStringValue("/api").build()).build()) + .addEvents(Event.newBuilder() + .setTimeUnixNano(TimeUnit.MICROSECONDS.toNanos(1_123_000L)) + .setName("foo").build()) + .build()) + .build()) + .build()).build()); + } + + @Test + void translate_missingName() { + Span zipkinSpan = Span.newBuilder().traceId("3").id("2").build(); + TracesData translated = SpanTranslator.translate(zipkinSpan); + + assertThat(translated.getResourceSpans(0).getScopeSpans(0).getSpans(0).getName()).isNotEmpty(); + } + + @Test + void testTranslateSpans() { + Span span1 = + Span.newBuilder().id("1").traceId("1").name("/a").timestamp(1L).duration(1L).build(); + Span span2 = + Span.newBuilder().id("2").traceId("2").name("/b").timestamp(2L).duration(1L).build(); + Span span3 = + Span.newBuilder().id("3").traceId("1").name("/c").timestamp(3L).duration(1L).build(); + + List spans = asList(span1, span2, span3); + List translated = spans.stream().map(SpanTranslator::translate).collect( + Collectors.toList()); + + assertThat(translated).hasSize(3); + assertThat(translated).extracting( + tracesData -> tracesData.getResourceSpans(0).getScopeSpans(0).getSpans(0).getName()) + .containsExactlyInAnyOrder( + "/a", + "/b", + "/c"); + } + + @Test + void testTranslateSpanEmptyName() { + Span spanNullName = + Span.newBuilder().id("1").traceId("1").timestamp(1L).duration(1L).build(); + Span spanEmptyName = + Span.newBuilder().id("2").traceId("2").name("").timestamp(2L).duration(1L).build(); + Span spanNonEmptyName = + Span.newBuilder().id("2").traceId("2").name("somename").timestamp(2L).duration(1L).build(); + + List spans = asList(spanNullName, spanEmptyName, spanNonEmptyName); + List translated = spans.stream().map(SpanTranslator::translate).collect( + Collectors.toList()); + + assertThat(translated).hasSize(3); + assertThat( + translated.get(0).getResourceSpans(0).getScopeSpans(0).getSpans(0).getName()).isEqualTo( + "unknown"); + assertThat( + translated.get(1).getResourceSpans(0).getScopeSpans(0).getSpans(0).getName()).isEqualTo( + "unknown"); + assertThat( + translated.get(2).getResourceSpans(0).getScopeSpans(0).getSpans(0).getName()).isEqualTo( + "somename"); + } +} From dbbe43c9b5f865d9509bba905fb1c22fd97e908d Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 26 Jun 2024 14:13:29 +0200 Subject: [PATCH 19/30] Added links --- .../http/ITOpenTelemetryHttpCollector.java | 1 - .../reporter/otel/brave/SpanTranslator.java | 90 ++++++++++++- .../otel/brave/SpanTranslatorTest.java | 7 ++ .../reporter/otel/brave/TestObjects.java | 3 + .../zipkin/AttributesExtractor.java | 5 +- .../zipkin2/translation/zipkin/LinkUtils.java | 119 ++++++++++++++++++ .../translation/zipkin/SpanTranslator.java | 17 ++- .../zipkin/SpanTranslatorTest.java | 10 ++ 8 files changed, 247 insertions(+), 5 deletions(-) create mode 100644 translation-otel/src/main/java/zipkin2/translation/zipkin/LinkUtils.java diff --git a/collector-http/src/test/java/zipkin2/collector/otel/http/ITOpenTelemetryHttpCollector.java b/collector-http/src/test/java/zipkin2/collector/otel/http/ITOpenTelemetryHttpCollector.java index 9b4ee9e..109e245 100644 --- a/collector-http/src/test/java/zipkin2/collector/otel/http/ITOpenTelemetryHttpCollector.java +++ b/collector-http/src/test/java/zipkin2/collector/otel/http/ITOpenTelemetryHttpCollector.java @@ -22,7 +22,6 @@ import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; -import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; diff --git a/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java b/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java index 05b4983..1112630 100644 --- a/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java +++ b/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java @@ -28,6 +28,7 @@ import io.opentelemetry.proto.trace.v1.ScopeSpans; import io.opentelemetry.proto.trace.v1.Span; import io.opentelemetry.proto.trace.v1.Span.Event; +import io.opentelemetry.proto.trace.v1.Span.Link; import io.opentelemetry.proto.trace.v1.Span.SpanKind; import io.opentelemetry.proto.trace.v1.TracesData; import io.opentelemetry.semconv.HttpAttributes; @@ -36,9 +37,15 @@ import io.opentelemetry.semconv.ServerAttributes; import io.opentelemetry.semconv.ServiceAttributes; import io.opentelemetry.semconv.UrlAttributes; +import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; /** @@ -184,6 +191,10 @@ private Span.Builder builderForSingleSpan(MutableSpan span, Builder resourceSpan span.forEachTag(consumer, spanBuilder); span.forEachAnnotation(consumer, spanBuilder); consumer.addErrorTag(spanBuilder, span); + + List links = LinkUtils.toLinks(span.tags()); + spanBuilder.addAllLinks(links); + return spanBuilder; } @@ -197,7 +208,9 @@ class Consumer implements TagConsumer, AnnotationConsumer entry) { + return isApplicable(entry.getKey()); + } + + private static boolean isApplicable(String key) { + return key.startsWith(LINKS_PREFIX); + } + + private static int linkGroup(Map.Entry entry) { + Matcher matcher = LINKS_ID.matcher(entry.getKey()); + if (matcher.matches()) { + return Integer.parseInt(matcher.group(1)); + } + return -1; + } + + static List toLinks(Map tags) { + return tags.entrySet() + .stream() + .filter(LinkUtils::isApplicable) + .collect(Collectors.groupingBy(LinkUtils::linkGroup)) + .values() + .stream().map(LinkUtils::toLink) + .collect(Collectors.toList()); + } + + private static Link toLink(List> groupedTags) { + String traceId = ""; + String spanId = ""; + Map tags = new HashMap<>(); + for (Map.Entry groupedTag : groupedTags) { + if (groupedTag.getKey().endsWith(".traceId")) { + traceId = groupedTag.getValue(); + } + else if (groupedTag.getKey().endsWith(".spanId")) { + spanId = groupedTag.getValue(); + } + else if (groupedTag.getKey().contains("tags")) { + String tagKey = tagKeyNameFromString(groupedTag.getKey()); + if (tagKey != null) { + tags.put(tagKey, groupedTag.getValue()); + } + } + } + if (traceId != null && !traceId.isEmpty()) { + List keyValues = tags.entrySet().stream().map(e -> KeyValue.newBuilder().setKey(e.getKey()).setValue( + AnyValue.newBuilder().setStringValue(String.valueOf(e.getValue())).build()).build()).collect( + Collectors.toList()); + return Link.newBuilder() + .setSpanId(ByteString.fromHex(spanId)) + .setTraceId(ByteString.fromHex(traceId)) + .addAllAttributes(keyValues) + .build(); + } + return null; + } + + static String tagKeyNameFromString(String tag) { + Matcher matcher = TAG_KEY.matcher(tag); + if (matcher.matches()) { + return matcher.group(1); + } + return null; + } + + } + } diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/SpanTranslatorTest.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/SpanTranslatorTest.java index c4c67e1..4a05ebe 100644 --- a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/SpanTranslatorTest.java +++ b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/SpanTranslatorTest.java @@ -24,6 +24,7 @@ import io.opentelemetry.proto.common.v1.KeyValue; import io.opentelemetry.proto.trace.v1.Span; import io.opentelemetry.proto.trace.v1.Span.Event; +import io.opentelemetry.proto.trace.v1.Span.Link; import io.opentelemetry.proto.trace.v1.Span.SpanKind; import io.opentelemetry.proto.trace.v1.Status; import io.opentelemetry.proto.trace.v1.Status.StatusCode; @@ -92,6 +93,12 @@ void translate_clientSpan() { Instant.ofEpochSecond(1472470996, 403_000_000).toEpochMilli())) .setName("bar").build())) .setStatus(Status.newBuilder().setCode(StatusCode.STATUS_CODE_OK).build()) + .addLinks(Link.newBuilder() + .setSpanId(ByteString.fromHex("6c5295666d50f69c")) + .setTraceId(ByteString.fromHex("8291c278b62e8f6a216a2aea45d08fc8")) + .addAttributes(KeyValue.newBuilder().setKey("foo").setValue( + AnyValue.newBuilder().setStringValue("bar").build()).build()) + .build()) .build()); } diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/TestObjects.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/TestObjects.java index cc459d0..7e9e356 100644 --- a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/TestObjects.java +++ b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/TestObjects.java @@ -33,6 +33,9 @@ static MutableSpan clientSpan() { braveSpan.annotate(1472470996403000L, "bar"); braveSpan.tag("clnt/finagle.version", "6.45.0"); braveSpan.tag("http.path", "/api"); + braveSpan.tag("links[0].traceId", "8291c278b62e8f6a216a2aea45d08fc8"); + braveSpan.tag("links[0].spanId", "6c5295666d50f69c"); + braveSpan.tag("links[0].tags[foo]", "bar"); return braveSpan; } } diff --git a/translation-otel/src/main/java/zipkin2/translation/zipkin/AttributesExtractor.java b/translation-otel/src/main/java/zipkin2/translation/zipkin/AttributesExtractor.java index b6af810..9947030 100644 --- a/translation-otel/src/main/java/zipkin2/translation/zipkin/AttributesExtractor.java +++ b/translation-otel/src/main/java/zipkin2/translation/zipkin/AttributesExtractor.java @@ -40,12 +40,13 @@ final class AttributesExtractor { } void addTag(Span.Builder target, String key, String value) { - target.addAttributes(KeyValue.newBuilder().setKey(getLabelName(key)).setValue(AnyValue.newBuilder().setStringValue(value).build()).build()).build(); + if (!LinkUtils.isApplicable(key)) { + target.addAttributes(KeyValue.newBuilder().setKey(getLabelName(key)).setValue(AnyValue.newBuilder().setStringValue(value).build()).build()).build(); + } } private String getLabelName(String zipkinName) { String renamed = renamedLabels.get(zipkinName); return renamed != null ? renamed : zipkinName; } - } diff --git a/translation-otel/src/main/java/zipkin2/translation/zipkin/LinkUtils.java b/translation-otel/src/main/java/zipkin2/translation/zipkin/LinkUtils.java new file mode 100644 index 0000000..df3a950 --- /dev/null +++ b/translation-otel/src/main/java/zipkin2/translation/zipkin/LinkUtils.java @@ -0,0 +1,119 @@ +/* + * Copyright 2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package zipkin2.translation.zipkin; + +import com.google.protobuf.ByteString; +import io.opentelemetry.proto.common.v1.AnyValue; +import io.opentelemetry.proto.common.v1.KeyValue; +import io.opentelemetry.proto.trace.v1.Span.Link; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +class LinkUtils { + + private static final String LINKS_PREFIX = "links["; + + private static final String TRACE_ID = "links[%s].traceId"; + + private static final String SPAN_ID = "links[%s].spanId"; + + private static final String TAG = "links[%s].tags[%s]"; + + private static final Pattern LINKS_ID = Pattern.compile("^links\\[(.*)]\\..*$"); + + private static final Pattern TAG_KEY = Pattern.compile("^links\\[.*]\\.tags\\[(.*)]$"); + + static boolean isApplicable(Map.Entry entry) { + return isApplicable(entry.getKey()); + } + + static boolean isApplicable(String key) { + return key.startsWith(LINKS_PREFIX); + } + + private static int linkGroup(Map.Entry entry) { + Matcher matcher = LINKS_ID.matcher(entry.getKey()); + if (matcher.matches()) { + return Integer.parseInt(matcher.group(1)); + } + return -1; + } + + static List toLinks(Map tags) { + return tags.entrySet() + .stream() + .filter(LinkUtils::isApplicable) + .collect(Collectors.groupingBy(LinkUtils::linkGroup)) + .values() + .stream().map(LinkUtils::toLink) + .collect(Collectors.toList()); + } + + private static Link toLink(List> groupedTags) { + String traceId = ""; + String spanId = ""; + Map tags = new HashMap<>(); + for (Map.Entry groupedTag : groupedTags) { + if (groupedTag.getKey().endsWith(".traceId")) { + traceId = groupedTag.getValue(); + } + else if (groupedTag.getKey().endsWith(".spanId")) { + spanId = groupedTag.getValue(); + } + else if (groupedTag.getKey().contains("tags")) { + String tagKey = tagKeyNameFromString(groupedTag.getKey()); + if (tagKey != null) { + tags.put(tagKey, groupedTag.getValue()); + } + } + } + if (traceId != null && !traceId.isEmpty()) { + List keyValues = tags.entrySet().stream().map(e -> KeyValue.newBuilder().setKey(e.getKey()).setValue( + AnyValue.newBuilder().setStringValue(String.valueOf(e.getValue())).build()).build()).collect( + Collectors.toList()); + return Link.newBuilder() + .setSpanId(ByteString.fromHex(spanId)) + .setTraceId(ByteString.fromHex(traceId)) + .addAllAttributes(keyValues) + .build(); + } + return null; + } + + static String tagKeyNameFromString(String tag) { + Matcher matcher = TAG_KEY.matcher(tag); + if (matcher.matches()) { + return matcher.group(1); + } + return null; + } + + static String traceIdKey(long index) { + return String.format(TRACE_ID, index); + } + + static String spanIdKey(long index) { + return String.format(SPAN_ID, index); + } + + static String tagKey(long index, String tagKey) { + return String.format(TAG, index, tagKey); + } + +} diff --git a/translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java b/translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java index b2988b1..3a43fca 100644 --- a/translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java +++ b/translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java @@ -29,6 +29,7 @@ import io.opentelemetry.proto.trace.v1.ScopeSpans; import io.opentelemetry.proto.trace.v1.Span; import io.opentelemetry.proto.trace.v1.Span.Event; +import io.opentelemetry.proto.trace.v1.Span.Link; import io.opentelemetry.proto.trace.v1.Span.SpanKind; import io.opentelemetry.proto.trace.v1.Status; import io.opentelemetry.proto.trace.v1.Status.StatusCode; @@ -64,7 +65,6 @@ public final class SpanTranslator { static final AttributesExtractor ATTRIBUTES_EXTRACTOR; - private static final AttributeKey SERVICE_NAME = AttributeKey.stringKey("service.name"); private static final AttributeKey PEER_SERVICE = stringKey("peer.service"); private static final AttributeKey SERVER_SOCKET_ADDRESS = @@ -194,12 +194,17 @@ private static Span.Builder builderForSingleSpan(zipkin2.Span span, } span.tags() .forEach((key, value) -> ATTRIBUTES_EXTRACTOR.addTag(spanBuilder, key, value)); + + List links = LinkUtils.toLinks(span.tags()); + links.forEach(spanBuilder::addLinks); + span.annotations().forEach(annotation -> spanBuilder.addEventsBuilder() .setTimeUnixNano(TimeUnit.MICROSECONDS.toNanos(annotation.timestamp())) .setName(annotation.value())); return spanBuilder; } + // TODO: Write better tests /** * Converts OpenTelemetry Spans into Zipkin spans. * @@ -291,6 +296,16 @@ private static zipkin2.Span generateSpan(io.opentelemetry.proto.trace.v1.Span sp spanBuilder.putTag(OTEL_DROPPED_EVENTS_COUNT, String.valueOf(droppedEvents)); } + for (int i = 0; i < spanData.getLinksCount(); i++) { + Link link = spanData.getLinks(i); + spanBuilder.putTag(LinkUtils.spanIdKey(i), OtelEncodingUtils.spanIdFromBytes(link.getSpanId().toByteArray())); + spanBuilder.putTag(LinkUtils.traceIdKey(i), OtelEncodingUtils.traceIdFromBytes(link.getTraceId().toByteArray())); + for (int j = 0; j < link.getAttributesCount(); j++) { + KeyValue attributes = link.getAttributes(j); + spanBuilder.putTag(LinkUtils.tagKey(j, attributes.getKey()), attributes.getValue().getStringValue()); + } + } + return spanBuilder.shared(false).build(); } diff --git a/translation-otel/src/test/java/zipkin2/translation/zipkin/SpanTranslatorTest.java b/translation-otel/src/test/java/zipkin2/translation/zipkin/SpanTranslatorTest.java index 489f186..0591371 100644 --- a/translation-otel/src/test/java/zipkin2/translation/zipkin/SpanTranslatorTest.java +++ b/translation-otel/src/test/java/zipkin2/translation/zipkin/SpanTranslatorTest.java @@ -22,6 +22,7 @@ import io.opentelemetry.proto.trace.v1.ResourceSpans; import io.opentelemetry.proto.trace.v1.ScopeSpans; import io.opentelemetry.proto.trace.v1.Span.Event; +import io.opentelemetry.proto.trace.v1.Span.Link; import io.opentelemetry.proto.trace.v1.Span.SpanKind; import io.opentelemetry.proto.trace.v1.TracesData; import java.util.List; @@ -57,6 +58,9 @@ void translate_clientSpan() { .addAnnotation(1_123_000L, "foo") .putTag("http.path", "/api") .putTag("clnt/finagle.version", "6.45.0") + .putTag("links[0].traceId", "8291c278b62e8f6a216a2aea45d08fc8") + .putTag("links[0].spanId", "6c5295666d50f69c") + .putTag("links[0].tags[foo]", "bar") .build(); TracesData translated = SpanTranslator.translate(zipkinSpan); @@ -99,6 +103,12 @@ void translate_clientSpan() { .addEvents(Event.newBuilder() .setTimeUnixNano(TimeUnit.MICROSECONDS.toNanos(1_123_000L)) .setName("foo").build()) + .addLinks(Link.newBuilder() + .setSpanId(ByteString.fromHex("6c5295666d50f69c")) + .setTraceId(ByteString.fromHex("8291c278b62e8f6a216a2aea45d08fc8")) + .addAttributes(KeyValue.newBuilder().setKey("foo").setValue( + AnyValue.newBuilder().setStringValue("bar").build()).build()) + .build()) .build()) .build()) .build()).build()); From 791175f8753c7cc7f0e4a238646121ae70aa3456 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 26 Jun 2024 14:30:00 +0200 Subject: [PATCH 20/30] Removed tests module --- encoder-otel-brave/pom.xml | 18 + .../otel/brave/ITZipkinSenderTests.java | 224 +++++++++ .../reporter/otel/brave}/JaegerAllInOne.java | 3 +- module/pom.xml | 76 +++ .../module/otel/ZipkinCollectorTests.java | 160 +++++++ pom.xml | 1 - tests/pom.xml | 40 -- tests/sender-tests/pom.xml | 153 ------ .../zipkin2/reporter/otel/ITBasicUsage.java | 436 ------------------ 9 files changed, 479 insertions(+), 632 deletions(-) create mode 100644 encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/ITZipkinSenderTests.java rename {tests/sender-tests/src/test/java/zipkin2/reporter/otel => encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave}/JaegerAllInOne.java (98%) create mode 100644 module/src/test/java/zipkin/module/otel/ZipkinCollectorTests.java delete mode 100644 tests/pom.xml delete mode 100644 tests/sender-tests/pom.xml delete mode 100644 tests/sender-tests/src/test/java/zipkin2/reporter/otel/ITBasicUsage.java diff --git a/encoder-otel-brave/pom.xml b/encoder-otel-brave/pom.xml index 5ca69c6..a3f31e0 100644 --- a/encoder-otel-brave/pom.xml +++ b/encoder-otel-brave/pom.xml @@ -126,6 +126,24 @@ ${awaitility.version} test + + org.testcontainers + testcontainers + ${testcontainers.version} + test + + + org.slf4j + * + + + + + org.testcontainers + junit-jupiter + ${testcontainers.version} + test + diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/ITZipkinSenderTests.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/ITZipkinSenderTests.java new file mode 100644 index 0000000..192403e --- /dev/null +++ b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/ITZipkinSenderTests.java @@ -0,0 +1,224 @@ +/* + * Copyright 2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package zipkin2.reporter.otel.brave; + +import static org.assertj.core.api.BDDAssertions.then; + +import brave.Span; +import brave.Span.Kind; +import brave.Tags; +import brave.Tracer; +import brave.Tracer.SpanInScope; +import brave.Tracing; +import brave.handler.MutableSpan; +import brave.handler.SpanHandler; +import brave.propagation.ThreadLocalCurrentTraceContext; +import brave.propagation.TraceContext; +import brave.sampler.Sampler; +import java.io.Closeable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import okhttp3.OkHttpClient; +import okhttp3.OkHttpClient.Builder; +import okhttp3.Request; +import okhttp3.Response; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import zipkin2.reporter.AsyncReporter; +import zipkin2.reporter.Encoding; +import zipkin2.reporter.okhttp3.OkHttpSender; + +@Testcontainers(disabledWithoutDocker = true) +class ITZipkinSenderTests { + + private static final Logger log = LoggerFactory.getLogger(ITZipkinSenderTests.class); + + private static final int EXPECTED_TRACE_SIZE = 5; + + @Container + JaegerAllInOne jaegerAllInOne = new JaegerAllInOne(); + + JaegerTestingScenario testingScenario; + + @Test + void shouldSendBraveSpansToJaegerOtlpEndpoint() throws Exception { + testingScenario = new JaegerTestingScenario( + jaegerAllInOne.getHttpOtlpPort()); + + List traceIds = testingScenario.exportedTraceIds(); + + Awaitility.await().untilAsserted(() -> { + then(traceIds).hasSize(EXPECTED_TRACE_SIZE); + thenAllTraceIdsPresentInBackend(testingScenario.queryUrl(), traceIds); + }); + } + + @AfterEach + void shutdown() { + testingScenario.close(); + } + + private static void thenAllTraceIdsPresentInBackend(String queryUrl, List traceIds) { + OkHttpClient client = new Builder() + .build(); + + traceIds.forEach(traceId -> { + Request request = new Request.Builder().url(queryUrl + traceId).build(); + try (Response response = client.newCall(request).execute()) { + then(response.isSuccessful()).isTrue(); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + + /** + * Sender: Brave OKHttp with OTLP proto over HTTP ; Receiver: Jaeger OTLP + */ + class JaegerTestingScenario { + + private final BraveTraceIdGenerator braveTraceIdGenerator; + + JaegerTestingScenario(int otlpPort) { + this.braveTraceIdGenerator = new BraveTraceIdGenerator(otlpPort); + } + + public String queryUrl() { + return "http://localhost:" + jaegerAllInOne.getQueryPort() + "/api/traces/"; + } + + public List exportedTraceIds() throws Exception { + return braveTraceIdGenerator.traceIds(); + } + + public void close() { + braveTraceIdGenerator.close(); + } + } + + /** + * Actual testing logic that uses Brave to generate spans and send them to the backend. + */ + static class BraveTraceIdGenerator implements Closeable { + + private final BraveHttpSenderProvider braveHttpSenderProvider = new BraveHttpSenderProvider(); + + private final int port; + + Tracing tracing; + + BraveTraceIdGenerator(int port) { + this.port = port; + } + + List traceIds() throws Exception { + ThreadLocalCurrentTraceContext braveCurrentTraceContext = ThreadLocalCurrentTraceContext.newBuilder() + .build(); + List traceIds = new ArrayList<>(); + tracing = Tracing.newBuilder() + .currentTraceContext(braveCurrentTraceContext) + .supportsJoin(false) + .traceId128Bit(true) + .sampler(Sampler.ALWAYS_SAMPLE) + .addSpanHandler(braveHttpSenderProvider.apply(port)) + .localServiceName("my-service") + .build(); + Tracer braveTracer = tracing.tracer(); + + for (int i = 0; i < EXPECTED_TRACE_SIZE; i++) { + Span span = braveTracer.nextSpan().name("foo " + i) + .tag("foo tag", "foo value") + .kind(Kind.CONSUMER) + .error(new RuntimeException("BOOOOOM!")) + .remoteServiceName("remote service") + .start(); + try (SpanInScope scope = braveTracer.withSpanInScope(span)) { + String traceId = span.context().traceIdString(); + log.info("Trace Id <" + traceId + ">"); + span.remoteIpAndPort("http://localhost", 123456); + Thread.sleep(50); + span.annotate("boom!"); + Thread.sleep(50); + } finally { + span.finish(); + } + + traceIds.add(span.context().traceIdString()); + } + flush(); + return traceIds; + } + + void flush() { + braveHttpSenderProvider.flush(); + } + + @Override + public void close() { + braveHttpSenderProvider.close(); + tracing.close(); + } + + /** + * Provides a {@link SpanHandler} that uses OKHttp to send spans to a given port. + */ + static class BraveHttpSenderProvider implements Function, Closeable { + + OkHttpSender okHttpSender; + + AsyncReporter reporter; + + SpanHandler spanHandler; + + @Override + public void close() { + if (reporter != null) { + reporter.close(); + } + if (okHttpSender != null) { + okHttpSender.close(); + } + } + + @Override + public SpanHandler apply(Integer port) { + okHttpSender = OkHttpSender.newBuilder() + .encoding(Encoding.PROTO3) + .endpoint("http://localhost:" + port + "/v1/traces") + .build(); + OtelEncoder otelEncoder = new OtelEncoder(Tags.ERROR); + reporter = AsyncReporter.builder(okHttpSender).build(otelEncoder); + spanHandler = new SpanHandler() { + @Override + public boolean end(TraceContext context, MutableSpan span, Cause cause) { + reporter.report(span); + return true; + } + }; + return spanHandler; + } + + void flush() { + reporter.flush(); + } + } + } +} \ No newline at end of file diff --git a/tests/sender-tests/src/test/java/zipkin2/reporter/otel/JaegerAllInOne.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/JaegerAllInOne.java similarity index 98% rename from tests/sender-tests/src/test/java/zipkin2/reporter/otel/JaegerAllInOne.java rename to encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/JaegerAllInOne.java index 40d937e..292849d 100644 --- a/tests/sender-tests/src/test/java/zipkin2/reporter/otel/JaegerAllInOne.java +++ b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/JaegerAllInOne.java @@ -11,11 +11,10 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ -package zipkin2.reporter.otel; +package zipkin2.reporter.otel.brave; import java.util.Collections; import java.util.Set; - import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; diff --git a/module/pom.xml b/module/pom.xml index e9317f8..c2e7afc 100644 --- a/module/pom.xml +++ b/module/pom.xml @@ -48,6 +48,12 @@ ${armeria.groupId} armeria-spring-boot3-autoconfigure ${armeria.version} + + + org.slf4j + * + + provided @@ -62,12 +68,24 @@ armeria-junit5 ${armeria.version} test + + + org.slf4j + * + + ${armeria.groupId} armeria-grpc ${armeria.version} test + + + org.slf4j + * + + org.awaitility @@ -81,6 +99,64 @@ ${spring-boot.version} test + + org.springframework.boot + spring-boot-starter-web + ${spring-boot.version} + test + + + org.testcontainers + testcontainers + ${testcontainers.version} + test + + + org.slf4j + * + + + + + org.testcontainers + junit-jupiter + ${testcontainers.version} + test + + + org.slf4j + * + + + + + com.fasterxml.jackson.core + jackson-annotations + 2.17.0 + test + + + io.opentelemetry + opentelemetry-sdk + test + + + io.opentelemetry + opentelemetry-exporter-otlp + test + + + io.zipkin + zipkin-server + ${zipkin.version} + + + org.springframework.boot + spring-boot-starter-log4j2 + + + test + diff --git a/module/src/test/java/zipkin/module/otel/ZipkinCollectorTests.java b/module/src/test/java/zipkin/module/otel/ZipkinCollectorTests.java new file mode 100644 index 0000000..7117825 --- /dev/null +++ b/module/src/test/java/zipkin/module/otel/ZipkinCollectorTests.java @@ -0,0 +1,160 @@ +/* + * Copyright 2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package zipkin.module.otel; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import okhttp3.OkHttpClient; +import okhttp3.OkHttpClient.Builder; +import okhttp3.Request; +import okhttp3.Response; +import org.assertj.core.api.BDDAssertions; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.test.util.TestSocketUtils; +import zipkin.server.ZipkinServer; + +class ZipkinCollectorTests { + + private static final Logger log = LoggerFactory.getLogger( + ZipkinCollectorTests.class); + + private static final int EXPECTED_TRACE_SIZE = 5; + + private ConfigurableApplicationContext ctx; + + private final int port = TestSocketUtils.findAvailableTcpPort(); + + private final OpenTelemetry openTelemetry = initOpenTelemetry(port); + + /** + * Initialize OpenTelemetry. + * + * @return a ready-to-use {@link OpenTelemetry} instance. + */ + private OpenTelemetry initOpenTelemetry(int port) { + OpenTelemetrySdk openTelemetrySdk = + OpenTelemetrySdk.builder() + .setTracerProvider( + SdkTracerProvider.builder() + .addSpanProcessor( + BatchSpanProcessor.builder( + OtlpHttpSpanExporter.builder() + .setEndpoint("http://localhost:" + port + "/v1/traces") + .setTimeout(2, TimeUnit.SECONDS) + .build()) + .setScheduleDelay(100, TimeUnit.MILLISECONDS) + .build()) + .build()) + .buildAndRegisterGlobal(); + + Runtime.getRuntime().addShutdownHook(new Thread(openTelemetrySdk::close)); + + return openTelemetrySdk; + } + + @BeforeEach + void setup() { + ctx = new SpringApplicationBuilder(Config.class) + .web(WebApplicationType.NONE) + .run("--spring.main.allow-bean-definition-overriding=true", "--server.port=0", + "--armeria.ports[0].port=" + port, "--logging.level.zipkin2=trace", + "--logging.level.com.linecorp=debug"); + } + + + @Test + void shouldSendOtlpHttpSpansToOtlpEndpoint() throws Exception { + List traceIds = exportedTraceIds(); + + // Then + Awaitility.await().untilAsserted(() -> { + BDDAssertions.then(traceIds).hasSize(EXPECTED_TRACE_SIZE); + thenAllTraceIdsPresentInBackend(queryUrl(), traceIds); + }); + } + + private String queryUrl() { + return "http://localhost:" + port + "/api/v2/trace/"; + } + + private List exportedTraceIds() throws Exception { + io.opentelemetry.api.trace.Tracer tracer = openTelemetry.getTracer( + "io.opentelemetry.example"); + List traceIds = new ArrayList<>(); + for (int i = 0; i < EXPECTED_TRACE_SIZE; i++) { + io.opentelemetry.api.trace.Span span = tracer.spanBuilder("foo " + i) + .setAttribute("foo tag", "foo value") + .setSpanKind(SpanKind.CONSUMER) + .startSpan() + .recordException(new RuntimeException("BOOOOOM!")); + String traceId = span.getSpanContext().getTraceId(); + traceIds.add(traceId); + try (Scope scope = Context.current().with(span).makeCurrent()) { + log.info("Trace Id <" + traceId + ">"); + Thread.sleep(50); + span.addEvent("boom!"); + Thread.sleep(50); + } finally { + span.end(); + } + } + return traceIds; + } + + @AfterEach + void shutdown() throws IOException { + if (ctx != null) { + ctx.close(); + } + } + + private static void thenAllTraceIdsPresentInBackend(String queryUrl, List traceIds) { + OkHttpClient client = new Builder() + .build(); + + traceIds.forEach(traceId -> { + Request request = new Request.Builder().url(queryUrl + traceId).build(); + try (Response response = client.newCall(request).execute()) { + BDDAssertions.then(response.isSuccessful()).isTrue(); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + + @SpringBootApplication(scanBasePackageClasses = {ZipkinOpenTelemetryHttpCollectorProperties.class, + ZipkinServer.class}) + static class Config { + + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 9ae1719..c5a4ef4 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,6 @@ encoder-otel-brave encoder-otel-zipkin collector-http - tests diff --git a/tests/pom.xml b/tests/pom.xml deleted file mode 100644 index ff079e4..0000000 --- a/tests/pom.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - 4.0.0 - - - io.zipkin.contrib.otel - zipkin-otel-parent - 0.1.0-SNAPSHOT - ../pom.xml - - - zipkin-otel-tests - Zipkin OpenTelemetry Tests - pom - - - ${project.basedir}/.. - - - - sender-tests - - diff --git a/tests/sender-tests/pom.xml b/tests/sender-tests/pom.xml deleted file mode 100644 index 674b1fa..0000000 --- a/tests/sender-tests/pom.xml +++ /dev/null @@ -1,153 +0,0 @@ - - - - 4.0.0 - - - io.zipkin.contrib.otel - zipkin-otel-tests - 0.1.0-SNAPSHOT - ../pom.xml - - - zipkin-sender-otel-tests - Zipkin OpenTelemetry Sender Tests - - - ${project.basedir}/.. - - - - - io.zipkin.brave - brave - ${brave.version} - - - * - * - - - - provided - - - ${project.groupId} - brave-encoder-otel - ${project.version} - test - - - ${project.groupId} - zipkin-module-otel - ${project.version} - test - - - io.zipkin.reporter2 - zipkin-sender-okhttp3 - ${zipkin-reporter.version} - test - - - org.springframework.boot - spring-boot-autoconfigure - ${spring-boot.version} - test - - - org.springframework.boot - spring-boot-starter-web - ${spring-boot.version} - test - - - ${armeria.groupId} - armeria-spring-boot3-autoconfigure - ${armeria.version} - - - org.slf4j - * - - - test - - - io.zipkin - zipkin-server - ${zipkin.version} - - - org.springframework.boot - spring-boot-starter-log4j2 - - - test - - - io.opentelemetry - opentelemetry-sdk - test - - - - io.opentelemetry - opentelemetry-exporter-otlp - test - - - - org.testcontainers - testcontainers - ${testcontainers.version} - test - - - org.slf4j - * - - - - - org.testcontainers - junit-jupiter - ${testcontainers.version} - test - - - com.fasterxml.jackson.core - jackson-annotations - 2.17.0 - test - - - org.awaitility - awaitility - ${awaitility.version} - test - - - org.springframework.boot - spring-boot-test - ${spring-boot.version} - test - - - diff --git a/tests/sender-tests/src/test/java/zipkin2/reporter/otel/ITBasicUsage.java b/tests/sender-tests/src/test/java/zipkin2/reporter/otel/ITBasicUsage.java deleted file mode 100644 index 640c4df..0000000 --- a/tests/sender-tests/src/test/java/zipkin2/reporter/otel/ITBasicUsage.java +++ /dev/null @@ -1,436 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin2.reporter.otel; - -import brave.Span; -import brave.Span.Kind; -import brave.Tags; -import brave.Tracer; -import brave.Tracer.SpanInScope; -import brave.Tracing; -import brave.handler.MutableSpan; -import brave.handler.SpanHandler; -import brave.propagation.ThreadLocalCurrentTraceContext; -import brave.propagation.TraceContext; -import brave.sampler.Sampler; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.context.Context; -import io.opentelemetry.context.Scope; -import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.trace.SdkTracerProvider; -import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; -import java.io.Closeable; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; -import okhttp3.OkHttpClient; -import okhttp3.OkHttpClient.Builder; -import okhttp3.Request; -import okhttp3.Response; -import org.assertj.core.api.BDDAssertions; -import org.awaitility.Awaitility; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.WebApplicationType; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.test.util.TestSocketUtils; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; -import zipkin.module.otel.ZipkinOpenTelemetryHttpCollectorProperties; -import zipkin.server.ZipkinServer; -import zipkin2.reporter.AsyncReporter; -import zipkin2.reporter.Encoding; -import zipkin2.reporter.okhttp3.OkHttpSender; -import zipkin2.reporter.otel.brave.OtelEncoder; - -class ITBasicUsage { - - private static final Logger log = LoggerFactory.getLogger(ITBasicUsage.class); - - private static final int EXPECTED_TRACE_SIZE = 5; - - @Nested - class ZipkinCollectorTests { - - TestingScenario testingScenario; - - @ParameterizedTest - @EnumSource(TestingScenario.class) - void shouldSendOtlpHttpSpansToOtlpEndpoint(TestingScenario testingScenario) throws Exception { - // Setup - this.testingScenario = testingScenario; - testingScenario.setup(); - - List traceIds = testingScenario.exportedTraceIds(); - - // Then - Awaitility.await().untilAsserted(() -> { - BDDAssertions.then(traceIds).hasSize(EXPECTED_TRACE_SIZE); - thenAllTraceIdsPresentInBackend(testingScenario.queryUrl(), traceIds); - }); - } - - @AfterEach - void shutdown() throws IOException { - testingScenario.close(); - } - } - - @Nested - @Testcontainers(disabledWithoutDocker = true) - class ZipkinSenderTests { - - @Container - JaegerAllInOne jaegerAllInOne = new JaegerAllInOne(); - - JaegerTestingScenario testingScenario; - - @Test - void shouldSendBraveSpansToJaegerOtlpEndpoint() throws Exception { - // Setup - testingScenario = new JaegerTestingScenario(jaegerAllInOne.getHttpOtlpPort()); - testingScenario.setup(); - - List traceIds = testingScenario.exportedTraceIds(); - - // Then - Awaitility.await().untilAsserted(() -> { - BDDAssertions.then(traceIds).hasSize(EXPECTED_TRACE_SIZE); - thenAllTraceIdsPresentInBackend(testingScenario.queryUrl(), traceIds); - }); - } - - /** - * Sender: Brave OKHttp with OTLP proto over HTTP ; Receiver: Jaeger OTLP - */ - class JaegerTestingScenario implements ScenarioSetup { - - private final BraveTraceIdGenerator braveTraceIdGenerator; - - JaegerTestingScenario(int otlpPort) { - this.braveTraceIdGenerator = new BraveTraceIdGenerator(otlpPort); - } - - @Override - public String queryUrl() { - return "http://localhost:" + jaegerAllInOne.getQueryPort() + "/api/traces/"; - } - - @Override - public List exportedTraceIds() throws Exception { - return braveTraceIdGenerator.traceIds(); - } - - @Override - public void close() { - braveTraceIdGenerator.close(); - } - } - } - - private static void thenAllTraceIdsPresentInBackend(String queryUrl, List traceIds) { - OkHttpClient client = new Builder() - .build(); - - traceIds.forEach(traceId -> { - Request request = new Request.Builder().url(queryUrl + traceId).build(); - try (Response response = client.newCall(request).execute()) { - BDDAssertions.then(response.isSuccessful()).isTrue(); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - } - - - interface ScenarioSetup extends Closeable { - - /** - * Code to be run before tests are executed. - */ - default void setup() { - - } - - /** - * URL from which trace data can be collected. - * - * @return query URL - */ - String queryUrl(); - - /** - * Actual testing code that will create spans and send them to the backend. - * - * @return list of generated trace ids - * @throws Exception exception - */ - List exportedTraceIds() throws Exception; - - } - - enum TestingScenario implements ScenarioSetup { - - // TODO: Why is it so slow? - /** - * Sender: Brave OKHttp with OTLP proto over HTTP ; Receiver: Zipkin OTLP - */ - BRAVE_OK_HTTP_OTLP_SENDER_TO_ZIPKIN_OLTP { - - private ConfigurableApplicationContext ctx; - - private final int port = TestSocketUtils.findAvailableTcpPort(); - - private final BraveTraceIdGenerator braveTraceIdGenerator = new BraveTraceIdGenerator(port); - - @Override - public void setup() { - ctx = new SpringApplicationBuilder(Config.class) - .web(WebApplicationType.NONE) - .run("--spring.main.allow-bean-definition-overriding=true", "--server.port=0", - "--armeria.ports[0].port=" + port, "--logging.level.zipkin2=trace", - "--logging.level.com.linecorp=debug"); - } - - - @Override - public String queryUrl() { - return "http://localhost:" + port + "/api/v2/trace/"; - } - - @Override - public List exportedTraceIds() throws Exception { - return braveTraceIdGenerator.traceIds(); - } - - @Override - public void close() { - braveTraceIdGenerator.close(); - if (ctx != null) { - ctx.close(); - } - } - }, - - /** - * Sender: OpenTelemetry OTLP proto over HTTP Exporter ; Receiver: Zipkin OTLP - */ - OTEL_OTLP_EXPORTER_TO_ZIPKIN_OTLP { - - private ConfigurableApplicationContext ctx; - - private final int port = TestSocketUtils.findAvailableTcpPort(); - - private final OpenTelemetry openTelemetry = initOpenTelemetry(port); - - /** - * Initialize OpenTelemetry. - * - * @return a ready-to-use {@link OpenTelemetry} instance. - */ - OpenTelemetry initOpenTelemetry(int port) { - OpenTelemetrySdk openTelemetrySdk = - OpenTelemetrySdk.builder() - .setTracerProvider( - SdkTracerProvider.builder() - .addSpanProcessor( - BatchSpanProcessor.builder( - OtlpHttpSpanExporter.builder() - .setEndpoint("http://localhost:" + port + "/v1/traces") - .setTimeout(2, TimeUnit.SECONDS) - .build()) - .setScheduleDelay(100, TimeUnit.MILLISECONDS) - .build()) - .build()) - .buildAndRegisterGlobal(); - - Runtime.getRuntime().addShutdownHook(new Thread(openTelemetrySdk::close)); - - return openTelemetrySdk; - } - - @Override - public void setup() { - ctx = new SpringApplicationBuilder(Config.class) - .web(WebApplicationType.NONE) - .run("--spring.main.allow-bean-definition-overriding=true", "--server.port=0", - "--armeria.ports[0].port=" + port, "--logging.level.zipkin2=trace", - "--logging.level.com.linecorp=debug"); - } - - - @Override - public String queryUrl() { - return "http://localhost:" + port + "/api/v2/trace/"; - } - - @Override - public List exportedTraceIds() throws Exception { - io.opentelemetry.api.trace.Tracer tracer = openTelemetry.getTracer( - "io.opentelemetry.example"); - List traceIds = new ArrayList<>(); - for (int i = 0; i < EXPECTED_TRACE_SIZE; i++) { - io.opentelemetry.api.trace.Span span = tracer.spanBuilder("foo " + i) - .setAttribute("foo tag", "foo value") - .setSpanKind(SpanKind.CONSUMER) - .startSpan() - .recordException(new RuntimeException("BOOOOOM!")); - String traceId = span.getSpanContext().getTraceId(); - traceIds.add(traceId); - try (Scope scope = Context.current().with(span).makeCurrent()) { - log.info("Trace Id <" + traceId + ">"); - Thread.sleep(50); - span.addEvent("boom!"); - Thread.sleep(50); - } finally { - span.end(); - } - } - return traceIds; - } - - @Override - public void close() { - if (ctx != null) { - ctx.close(); - } - } - } - } - - @SpringBootApplication(scanBasePackageClasses = {ZipkinOpenTelemetryHttpCollectorProperties.class, - ZipkinServer.class}) - static class Config { - - } - - /** - * Actual testing logic that uses Brave to generate spans and send them to the backend. - */ - static class BraveTraceIdGenerator implements Closeable { - - private final BraveHttpSenderProvider braveHttpSenderProvider = new BraveHttpSenderProvider(); - - private final int port; - - Tracing tracing; - - BraveTraceIdGenerator(int port) { - this.port = port; - } - - List traceIds() throws Exception { - ThreadLocalCurrentTraceContext braveCurrentTraceContext = ThreadLocalCurrentTraceContext.newBuilder() - .build(); - List traceIds = new ArrayList<>(); - tracing = Tracing.newBuilder() - .currentTraceContext(braveCurrentTraceContext) - .supportsJoin(false) - .traceId128Bit(true) - .sampler(Sampler.ALWAYS_SAMPLE) - .addSpanHandler(braveHttpSenderProvider.apply(port)) - .localServiceName("my-service") - .build(); - Tracer braveTracer = tracing.tracer(); - - for (int i = 0; i < EXPECTED_TRACE_SIZE; i++) { - Span span = braveTracer.nextSpan().name("foo " + i) - .tag("foo tag", "foo value") - .kind(Kind.CONSUMER) - .error(new RuntimeException("BOOOOOM!")) - .remoteServiceName("remote service") - .start(); - try (SpanInScope scope = braveTracer.withSpanInScope(span)) { - String traceId = span.context().traceIdString(); - log.info("Trace Id <" + traceId + ">"); - span.remoteIpAndPort("http://localhost", 123456); - Thread.sleep(50); - span.annotate("boom!"); - Thread.sleep(50); - } finally { - span.finish(); - } - - traceIds.add(span.context().traceIdString()); - } - flush(); - return traceIds; - } - - void flush() { - braveHttpSenderProvider.flush(); - } - - @Override - public void close() { - braveHttpSenderProvider.close(); - tracing.close(); - } - - /** - * Provides a {@link SpanHandler} that uses OKHttp to send spans to a given port. - */ - static class BraveHttpSenderProvider implements Function, Closeable { - - OkHttpSender okHttpSender; - - AsyncReporter reporter; - - SpanHandler spanHandler; - - @Override - public void close() { - if (reporter != null) { - reporter.close(); - } - if (okHttpSender != null) { - okHttpSender.close(); - } - } - - @Override - public SpanHandler apply(Integer port) { - okHttpSender = OkHttpSender.newBuilder() - .encoding(Encoding.PROTO3) - .endpoint("http://localhost:" + port + "/v1/traces") - .build(); - OtelEncoder otelEncoder = new OtelEncoder(Tags.ERROR); - reporter = AsyncReporter.builder(okHttpSender).build(otelEncoder); - spanHandler = new SpanHandler() { - @Override - public boolean end(TraceContext context, MutableSpan span, Cause cause) { - reporter.report(span); - return true; - } - }; - return spanHandler; - } - - void flush() { - reporter.flush(); - } - } - } -} \ No newline at end of file From dc7b76296ee2e318cf7a2df39aa821f2b41c2256 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 26 Jun 2024 15:22:30 +0200 Subject: [PATCH 21/30] Fixed translation from OTel to Zipkin --- .../otel/brave/ITZipkinSenderTests.java | 11 +-- .../reporter/otel/brave/JaegerAllInOne.java | 3 - module/README.md | 5 +- .../module/otel/ZipkinCollectorTests.java | 1 - .../translation/zipkin/SpanTranslator.java | 45 ++++++--- .../zipkin/SpanTranslatorTest.java | 94 +++++++++++++++++++ 6 files changed, 131 insertions(+), 28 deletions(-) diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/ITZipkinSenderTests.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/ITZipkinSenderTests.java index 192403e..0741386 100644 --- a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/ITZipkinSenderTests.java +++ b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/ITZipkinSenderTests.java @@ -101,11 +101,11 @@ class JaegerTestingScenario { this.braveTraceIdGenerator = new BraveTraceIdGenerator(otlpPort); } - public String queryUrl() { + String queryUrl() { return "http://localhost:" + jaegerAllInOne.getQueryPort() + "/api/traces/"; } - public List exportedTraceIds() throws Exception { + List exportedTraceIds() throws Exception { return braveTraceIdGenerator.traceIds(); } @@ -117,7 +117,7 @@ public void close() { /** * Actual testing logic that uses Brave to generate spans and send them to the backend. */ - static class BraveTraceIdGenerator implements Closeable { + static class BraveTraceIdGenerator { private final BraveHttpSenderProvider braveHttpSenderProvider = new BraveHttpSenderProvider(); @@ -167,11 +167,10 @@ List traceIds() throws Exception { return traceIds; } - void flush() { + private void flush() { braveHttpSenderProvider.flush(); } - @Override public void close() { braveHttpSenderProvider.close(); tracing.close(); @@ -216,7 +215,7 @@ public boolean end(TraceContext context, MutableSpan span, Cause cause) { return spanHandler; } - void flush() { + private void flush() { reporter.flush(); } } diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/JaegerAllInOne.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/JaegerAllInOne.java index 292849d..3f65ea6 100644 --- a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/JaegerAllInOne.java +++ b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/JaegerAllInOne.java @@ -24,8 +24,6 @@ class JaegerAllInOne extends GenericContainer { static final int JAEGER_ADMIN_PORT = 14269; - static final int GRPC_OTLP_PORT = 4317; - static final int HTTP_OTLP_PORT = 4318; JaegerAllInOne() { @@ -37,7 +35,6 @@ private void init() { waitingFor(new BoundPortHttpWaitStrategy(JAEGER_ADMIN_PORT)); withExposedPorts(JAEGER_ADMIN_PORT, JAEGER_QUERY_PORT, - GRPC_OTLP_PORT, HTTP_OTLP_PORT); } diff --git a/module/README.md b/module/README.md index 641348c..59cb82d 100644 --- a/module/README.md +++ b/module/README.md @@ -4,7 +4,7 @@ This is a module that can be added to a [Zipkin Server](https://github.com/openzipkin/zipkin/tree/master/zipkin-server) -deployment to receive Spans to OpenTelemetry's OLTP/GRPC and OLTP/HTTP protocols. +deployment to receive Spans to OpenTelemetry's OLTP/HTTP protocols. ## Experimental @@ -21,7 +21,7 @@ Fetch the latest released [executable jar for Zipkin server](https://search.maven.org/remote_content?g=io.zipkin&a=zipkin-server&v=LATEST&c=exec) and [module jar for otel](https://search.maven.org/remote_content?g=io.zipkin.contrib.otel&a=zipkin-module-otel&v=LATEST&c=module). -Run Zipkin server with the StackDriver Storage enabled. +Run Zipkin server with the Zipkin OTel module enabled. For example: @@ -49,7 +49,6 @@ for users that prefer a file based approach. | Property | Environment Variable | Description | |--------------------------------------|-------------------------------|----------------------------------------------------------| -| `zipkin.collector.otel.grpc.enabled` | `COLLECTOR_GRPC_OTEL_ENABLED` | `false` disables the GRPC collector. Defaults to `true`. | | `zipkin.collector.otel.http.enabled` | `COLLECTOR_HTTP_OTEL_ENABLED` | `false` disables the HTTP collector. Defaults to `true`. | ### Running diff --git a/module/src/test/java/zipkin/module/otel/ZipkinCollectorTests.java b/module/src/test/java/zipkin/module/otel/ZipkinCollectorTests.java index 7117825..68d13f6 100644 --- a/module/src/test/java/zipkin/module/otel/ZipkinCollectorTests.java +++ b/module/src/test/java/zipkin/module/otel/ZipkinCollectorTests.java @@ -96,7 +96,6 @@ void setup() { void shouldSendOtlpHttpSpansToOtlpEndpoint() throws Exception { List traceIds = exportedTraceIds(); - // Then Awaitility.await().untilAsserted(() -> { BDDAssertions.then(traceIds).hasSize(EXPECTED_TRACE_SIZE); thenAllTraceIdsPresentInBackend(queryUrl(), traceIds); diff --git a/translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java b/translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java index 3a43fca..a9af20d 100644 --- a/translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java +++ b/translation-otel/src/main/java/zipkin2/translation/zipkin/SpanTranslator.java @@ -15,6 +15,9 @@ import static io.opentelemetry.api.common.AttributeKey.longKey; import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.semconv.SemanticAttributes.NET_SOCK_PEER_ADDR; +import static io.opentelemetry.semconv.SemanticAttributes.NET_SOCK_PEER_NAME; +import static io.opentelemetry.semconv.SemanticAttributes.NET_SOCK_PEER_PORT; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.stream.Collectors.joining; @@ -71,7 +74,6 @@ public final class SpanTranslator { stringKey("server.socket.address"); private static final AttributeKey SERVER_SOCKET_PORT = longKey("server.socket.port"); - static final String KEY_INSTRUMENTATION_SCOPE_NAME = "otel.scope.name"; static final String KEY_INSTRUMENTATION_SCOPE_VERSION = "otel.scope.version"; static final String KEY_INSTRUMENTATION_LIBRARY_NAME = "otel.library.name"; @@ -177,7 +179,7 @@ private static Span.Builder builderForSingleSpan(zipkin2.Span span, String peerName = span.remoteServiceName(); if (peerName != null) { spanBuilder.addAttributes( - KeyValue.newBuilder().setKey(SemanticAttributes.NET_SOCK_PEER_NAME.getKey()) + KeyValue.newBuilder().setKey(NET_SOCK_PEER_NAME.getKey()) .setValue(AnyValue.newBuilder().setStringValue(peerName).build()).build()); } String peerIp = span.remoteEndpoint() != null ? span.remoteEndpoint().ipv4() : null; @@ -204,7 +206,6 @@ private static Span.Builder builderForSingleSpan(zipkin2.Span span, return spanBuilder; } - // TODO: Write better tests /** * Converts OpenTelemetry Spans into Zipkin spans. * @@ -252,7 +253,7 @@ private static zipkin2.Span generateSpan(io.opentelemetry.proto.trace.v1.Span sp .localEndpoint(getLocalEndpoint(resourceSpans)) .remoteEndpoint(getRemoteEndpoint(spanData)); - if (spanData.getParentSpanId().isEmpty() || OtelEncodingUtils.spanIdFromBytes( + if (!spanData.getParentSpanId().isEmpty() && !OtelEncodingUtils.spanIdFromBytes( spanData.getParentSpanId().toByteArray()).equals(SpanContext.getInvalid().getSpanId())) { spanBuilder.parentId( OtelEncodingUtils.spanIdFromBytes(spanData.getParentSpanId().toByteArray())); @@ -260,7 +261,7 @@ private static zipkin2.Span generateSpan(io.opentelemetry.proto.trace.v1.Span sp List spanAttributes = spanData.getAttributesList(); spanAttributes.forEach( - (kv) -> spanBuilder.putTag(kv.getKey(), valueToString(kv, kv.getValue()))); + (kv) -> spanBuilder.putTag(kv.getKey(), valueToString(kv.getValue()))); int droppedAttributes = spanData.getAttributesCount() - spanAttributes.size(); if (droppedAttributes > 0) { spanBuilder.putTag(OTEL_DROPPED_ATTRIBUTES_COUNT, String.valueOf(droppedAttributes)); @@ -317,7 +318,7 @@ private static Endpoint getLocalEndpoint(ResourceSpans spanData) { List resourceAttributes = spanData.getResource().getAttributesList(); Endpoint.Builder endpoint = Endpoint.newBuilder(); - endpoint.ip(LocalInetAddressSupplier.findLocalIp()); + endpoint.ip(LocalInetAddressSupplier.getInstance().get()); // use the service.name from the Resource, if it's been set. KeyValue serviceNameValue = resourceAttributes.stream().filter(keyValue -> SERVICE_NAME.getKey().equals( @@ -325,6 +326,8 @@ private static Endpoint getLocalEndpoint(ResourceSpans spanData) { String serviceName = null; if (serviceNameValue == null) { serviceName = Resource.getDefault().getAttribute(SERVICE_NAME); + } else { + serviceName = serviceNameValue.getValue().getStringValue(); } // In practice should never be null unless the default Resource spec is changed. if (serviceName != null) { @@ -332,6 +335,9 @@ private static Endpoint getLocalEndpoint(ResourceSpans spanData) { } return endpoint.build(); } + /* + localEndpoint":{"serviceName":"frontend"},"remoteEndpoint":{"serviceName":"backend","ipv4":"192.168.99.101","port":9000} + */ @Nullable private static Endpoint getRemoteEndpoint(Span spanData) { @@ -340,14 +346,14 @@ private static Endpoint getRemoteEndpoint(Span spanData) { // TODO: Implement fallback mechanism: // https://opentelemetry.io/docs/reference/specification/trace/sdk_exporters/zipkin/#otlp---zipkin List attributes = spanData.getAttributesList(); - String serviceName = attributes.stream().filter(keyValue -> PEER_SERVICE.getKey().equals(keyValue.getKey())).map(keyValue -> keyValue.getValue().getStringValue()).findFirst().orElse(null); + String serviceName = attributes.stream().filter(keyValue -> PEER_SERVICE.getKey().equals(keyValue.getKey()) || NET_SOCK_PEER_NAME.getKey().equals(keyValue.getKey())).map(keyValue -> keyValue.getValue().getStringValue()).findFirst().orElse(null); if (serviceName != null) { Endpoint.Builder endpoint = Endpoint.newBuilder(); endpoint.serviceName(serviceName); - endpoint.ip(attributes.stream().filter(keyValue -> SERVER_SOCKET_ADDRESS.getKey().equals(keyValue.getKey())).map(keyValue -> keyValue.getValue().getStringValue()).findFirst().orElse(null)); + endpoint.ip(attributes.stream().filter(keyValue -> SERVER_SOCKET_ADDRESS.getKey().equals(keyValue.getKey()) || NET_SOCK_PEER_ADDR.getKey().equals(keyValue.getKey())).map(keyValue -> keyValue.getValue().getStringValue()).findFirst().orElse(null)); attributes.stream() - .filter(keyValue -> SERVER_SOCKET_PORT.getKey().equals(keyValue.getKey())) + .filter(keyValue -> SERVER_SOCKET_PORT.getKey().equals(keyValue.getKey()) || NET_SOCK_PEER_PORT.getKey().equals(keyValue.getKey())) .map(keyValue -> keyValue.getValue().getIntValue()).findFirst() .ifPresent(port -> endpoint.port(port.intValue())); @@ -385,15 +391,20 @@ private static long toEpochMicros(long epochNanos) { return NANOSECONDS.toMicros(epochNanos); } - private static String valueToString(KeyValue key, AnyValue attributeValue) { + private static String valueToString(AnyValue attributeValue) { if (attributeValue.hasArrayValue()) { return commaSeparated(attributeValue.getArrayValue().getValuesList().stream().map( AnyValue::getStringValue).collect( Collectors.toList())); - } else if (attributeValue.hasStringValue()) { - return attributeValue.getStringValue(); } - throw new IllegalStateException("Unknown attribute type"); + if (attributeValue.hasBoolValue()) { + return String.valueOf(attributeValue.getBoolValue()); + } else if (attributeValue.hasDoubleValue()) { + return String.valueOf(attributeValue.getDoubleValue()); + } else if (attributeValue.hasIntValue()) { + return String.valueOf(attributeValue.getIntValue()); + } + return attributeValue.getStringValue(); } private static String commaSeparated(List values) { @@ -439,11 +450,11 @@ private static String toValue(Object o) { static class LocalInetAddressSupplier implements Supplier { private static final Logger logger = Logger.getLogger(LocalInetAddressSupplier.class.getName()); - private static final LocalInetAddressSupplier INSTANCE = + private static LocalInetAddressSupplier INSTANCE = new LocalInetAddressSupplier(findLocalIp()); @Nullable private final InetAddress inetAddress; - private LocalInetAddressSupplier(@Nullable InetAddress inetAddress) { + LocalInetAddressSupplier(@Nullable InetAddress inetAddress) { this.inetAddress = inetAddress; } @@ -478,6 +489,10 @@ private static InetAddress findLocalIp() { static LocalInetAddressSupplier getInstance() { return INSTANCE; } + + static void setInstance(LocalInetAddressSupplier supplier) { + INSTANCE = supplier; + } } diff --git a/translation-otel/src/test/java/zipkin2/translation/zipkin/SpanTranslatorTest.java b/translation-otel/src/test/java/zipkin2/translation/zipkin/SpanTranslatorTest.java index 0591371..101e1cd 100644 --- a/translation-otel/src/test/java/zipkin2/translation/zipkin/SpanTranslatorTest.java +++ b/translation-otel/src/test/java/zipkin2/translation/zipkin/SpanTranslatorTest.java @@ -17,20 +17,25 @@ import static org.assertj.core.api.Assertions.assertThat; import com.google.protobuf.ByteString; +import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; import io.opentelemetry.proto.common.v1.AnyValue; import io.opentelemetry.proto.common.v1.KeyValue; +import io.opentelemetry.proto.resource.v1.Resource; import io.opentelemetry.proto.trace.v1.ResourceSpans; import io.opentelemetry.proto.trace.v1.ScopeSpans; import io.opentelemetry.proto.trace.v1.Span.Event; import io.opentelemetry.proto.trace.v1.Span.Link; import io.opentelemetry.proto.trace.v1.Span.SpanKind; import io.opentelemetry.proto.trace.v1.TracesData; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import org.junit.jupiter.api.Test; import zipkin2.Endpoint; import zipkin2.Span; +import zipkin2.translation.zipkin.SpanTranslator.LocalInetAddressSupplier; public class SpanTranslatorTest { @@ -113,6 +118,95 @@ void translate_clientSpan() { .build()) .build()).build()); } + /** + * This test is intentionally sensitive, so changing other parts makes obvious impact here + */ + @Test + void translate_clientOtelSpan() throws UnknownHostException { + SpanTranslator.LocalInetAddressSupplier.setInstance(new LocalInetAddressSupplier(InetAddress.getByName("172.28.0.100"))); + + ExportTraceServiceRequest request = ExportTraceServiceRequest.newBuilder() + .addResourceSpans(ResourceSpans.newBuilder() + .setResource( + Resource.newBuilder().addAttributes( + KeyValue.newBuilder().setKey("service.name").setValue( + AnyValue.newBuilder().setStringValue("frontend").build()) + .build() + ).build()) + .addScopeSpans(ScopeSpans.newBuilder() + .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() + .setSpanId(ByteString.fromHex("5b4185666d50f68b")) + .setTraceId(ByteString.fromHex("7180c278b62e8f6a216a2aea45d08fc9")) + .setParentSpanId(ByteString.fromHex("6b221d5bc9e6496c")) + .setName("get") + .setKind(SpanKind.SPAN_KIND_CLIENT) + .setStartTimeUnixNano(TimeUnit.SECONDS.toNanos(1)) + .setEndTimeUnixNano(TimeUnit.SECONDS.toNanos(1) + 123_456_000) + .addAttributes( + KeyValue.newBuilder().setKey("net.sock.peer.name").setValue( + AnyValue.newBuilder().setStringValue("backend").build()) + .build()) + .addAttributes( + KeyValue.newBuilder().setKey("net.sock.peer.addr").setValue( + AnyValue.newBuilder().setStringValue("192.168.99.101").build()) + .build()) + .addAttributes( + KeyValue.newBuilder().setKey("net.sock.peer.port").setValue( + AnyValue.newBuilder().setIntValue(9000).build()).build()) + .addAttributes( + KeyValue.newBuilder().setKey("clnt/finagle.version").setValue( + AnyValue.newBuilder().setStringValue("6.45.0").build()).build()) + .addAttributes(KeyValue.newBuilder().setKey("http.path").setValue( + AnyValue.newBuilder().setStringValue("/api").build()).build()) + .addEvents(Event.newBuilder() + .setTimeUnixNano(TimeUnit.MICROSECONDS.toNanos(1_123_000L)) + .setName("foo").build()) + .addLinks(Link.newBuilder() + .setSpanId(ByteString.fromHex("6c5295666d50f69c")) + .setTraceId(ByteString.fromHex("8291c278b62e8f6a216a2aea45d08fc8")) + .addAttributes(KeyValue.newBuilder().setKey("foo").setValue( + AnyValue.newBuilder().setStringValue("bar").build()).build()) + .build()) + .build()) + .build()) + .build()).build(); + + List translated = SpanTranslator.translate(request); + + assertThat(translated).hasSize(1); + assertThat(translated.get(0)) + .isEqualTo( + Span.newBuilder() + .traceId("7180c278b62e8f6a216a2aea45d08fc9") + .parentId("6b221d5bc9e6496c") + .id("5b4185666d50f68b") + .name("get") + .kind(Span.Kind.CLIENT) + .localEndpoint(Endpoint.newBuilder().serviceName("frontend").ip("172.28.0.100").build()) + .remoteEndpoint( + Endpoint.newBuilder() + .serviceName("backend") + .ip("192.168.99.101") + .port(9000) + .build()) + .timestamp(1_000_000L) // 1 second after epoch + .duration(123_456L) + .addAnnotation(1_123_000L, "\"foo\":{}") + .putTag("http.path", "/api") + .putTag("clnt/finagle.version", "6.45.0") + .putTag("links[0].traceId", "8291c278b62e8f6a216a2aea45d08fc8") + .putTag("links[0].spanId", "6c5295666d50f69c") + .putTag("links[0].tags[foo]", "bar") + .putTag("net.sock.peer.addr", "192.168.99.101") + .putTag("net.sock.peer.name", "backend") + .putTag("net.sock.peer.port", "9000") + .putTag("otel.library.name", "zipkin2.reporter.otel") + .putTag("otel.library.version", "0.0.1") + .putTag("otel.scope.name", "zipkin2.reporter.otel") + .putTag("otel.scope.version", "0.0.1") + .shared(false) + .build()); + } @Test void translate_missingName() { From f30ade5cfefe3d6c3e7c901c2784896f470e8b78 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 26 Jun 2024 15:29:53 +0200 Subject: [PATCH 22/30] Removing GRPC --- .dockerignore | 1 - README.md | 1 - benchmarks/pom.xml | 5 - collector-grpc/README.md | 5 - collector-grpc/pom.xml | 70 ------------- .../otel/grpc/OpenTelemetryGrpcCollector.java | 97 ------------------- .../grpc/ITOpenTelemetryGrpcCollector.java | 50 ---------- .../grpc/OpenTelemetryGrpcCollectorTest.java | 38 -------- module/README.md | 3 +- module/pom.xml | 11 --- ...ipkinOpenTelemetryGrpcCollectorModule.java | 43 -------- ...nOpenTelemetryGrpcCollectorProperties.java | 20 ---- ...ipkinOpenTelemetryHttpCollectorModule.java | 1 - .../src/main/resources/zipkin-server-otel.yml | 4 - ...nOpenTelemetryGrpcCollectorModuleTest.java | 80 --------------- pom.xml | 1 - 16 files changed, 1 insertion(+), 429 deletions(-) delete mode 100644 collector-grpc/README.md delete mode 100644 collector-grpc/pom.xml delete mode 100644 collector-grpc/src/main/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollector.java delete mode 100644 collector-grpc/src/test/java/zipkin2/collector/otel/grpc/ITOpenTelemetryGrpcCollector.java delete mode 100644 collector-grpc/src/test/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollectorTest.java delete mode 100644 module/src/main/java/zipkin/module/otel/ZipkinOpenTelemetryGrpcCollectorModule.java delete mode 100644 module/src/main/java/zipkin/module/otel/ZipkinOpenTelemetryGrpcCollectorProperties.java delete mode 100644 module/src/test/java/zipkin/module/otel/ZipkinOpenTelemetryGrpcCollectorModuleTest.java diff --git a/.dockerignore b/.dockerignore index 08d807a..805d603 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,7 +8,6 @@ !build-bin/maven/maven_unjar # Allow on-demand "mvn package". referenced in pom.xml must be added even if not built -!collector-grpc/src/main/** !collector-http/src/main/** !module/src/main/** !**/pom.xml diff --git a/README.md b/README.md index 4b80a0d..850c0d0 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,6 @@ persists them to a configured collector component. | Collector | Description | |------------------------------------|-----------------------------------------------------------------------------------------| -| [collector-grpc](./collector-grpc) | Implements the [OTLP/gRPC protocol](https://opentelemetry.io/docs/specs/otlp/#otlpgrpc) | | [collector-http](./collector-http) | Implements the [OTLP/HTTP protocol](https://opentelemetry.io/docs/specs/otlp/#otlphttp) | ## Server integration diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index b6d2a32..e0a1287 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -34,11 +34,6 @@ - - ${project.groupId} - zipkin-collector-otel-grpc - ${project.version} - ${project.groupId} zipkin-collector-otel-http diff --git a/collector-grpc/README.md b/collector-grpc/README.md deleted file mode 100644 index e8b13b2..0000000 --- a/collector-grpc/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# zipkin-collector-otel-grpc - -This component implements -the [OTLP/gRPC protocol](https://opentelemetry.io/docs/specs/otlp/#otlpgrpc) -with [Armeria](https://armeria.dev/). diff --git a/collector-grpc/pom.xml b/collector-grpc/pom.xml deleted file mode 100644 index 146ca56..0000000 --- a/collector-grpc/pom.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - 4.0.0 - - - io.zipkin.contrib.otel - zipkin-otel-parent - 0.1.0-SNAPSHOT - ../pom.xml - - - zipkin-collector-otel-grpc - Zipkin Collector: OpenTelemetry gRPC - - - ${project.basedir}/.. - - - - - ${zipkin.groupId} - zipkin-collector - ${zipkin.version} - - - - ${armeria.groupId} - armeria-grpc-protocol - ${armeria.version} - - - - ${zipkin.groupId} - zipkin-tests - ${zipkin.version} - test - - - - ${armeria.groupId} - armeria-grpc - ${armeria.version} - test - - - - ${armeria.groupId} - armeria-junit5 - ${armeria.version} - test - - - diff --git a/collector-grpc/src/main/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollector.java b/collector-grpc/src/main/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollector.java deleted file mode 100644 index 17bb6f0..0000000 --- a/collector-grpc/src/main/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollector.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin2.collector.otel.grpc; - -import com.linecorp.armeria.server.ServerBuilder; -import com.linecorp.armeria.server.ServerConfigurator; -import com.linecorp.armeria.server.ServiceRequestContext; -import com.linecorp.armeria.server.grpc.protocol.AbstractUnsafeUnaryGrpcService; -import io.netty.buffer.ByteBuf; -import java.util.concurrent.CompletionStage; -import zipkin2.collector.Collector; -import zipkin2.collector.CollectorComponent; -import zipkin2.collector.CollectorMetrics; -import zipkin2.collector.CollectorSampler; -import zipkin2.storage.StorageComponent; - -public final class OpenTelemetryGrpcCollector extends CollectorComponent - implements ServerConfigurator { - public static Builder newBuilder() { - return new Builder(); - } - - public static final class Builder extends CollectorComponent.Builder { - - Collector.Builder delegate = Collector.newBuilder(OpenTelemetryGrpcCollector.class); - CollectorMetrics metrics = CollectorMetrics.NOOP_METRICS; - - @Override public Builder storage(StorageComponent storageComponent) { - delegate.storage(storageComponent); - return this; - } - - @Override public Builder metrics(CollectorMetrics metrics) { - if (metrics == null) throw new NullPointerException("metrics == null"); - delegate.metrics(this.metrics = metrics.forTransport("otel/grpc")); - return this; - } - - @Override public Builder sampler(CollectorSampler sampler) { - delegate.sampler(sampler); - return this; - } - - @Override public OpenTelemetryGrpcCollector build() { - return new OpenTelemetryGrpcCollector(this); - } - - Builder() { - } - } - - final Collector collector; - final CollectorMetrics metrics; - - OpenTelemetryGrpcCollector(Builder builder) { - collector = builder.delegate.build(); - metrics = builder.metrics; - } - - @Override public OpenTelemetryGrpcCollector start() { - return this; - } - @Override public String toString() { - return "OpenTelemetryGrpcCollector{}"; - } - - /** - * Reconfigures the service per https://github.com/open-telemetry/opentelemetry-proto/blob/v1.0.0/opentelemetry/proto/collector/trace/v1/trace_service.proto - */ - @Override public void reconfigure(ServerBuilder sb) { - sb.service("/opentelemetry.proto.collector.trace.v1.TraceService/Export", new HttpService(this)); - } - - static final class HttpService extends AbstractUnsafeUnaryGrpcService { - final OpenTelemetryGrpcCollector collector; - - HttpService(OpenTelemetryGrpcCollector collector) { - this.collector = collector; - } - - @Override - protected CompletionStage handleMessage(ServiceRequestContext ctx, ByteBuf message) { - throw new RuntimeException("Implement me!"); - } - } -} diff --git a/collector-grpc/src/test/java/zipkin2/collector/otel/grpc/ITOpenTelemetryGrpcCollector.java b/collector-grpc/src/test/java/zipkin2/collector/otel/grpc/ITOpenTelemetryGrpcCollector.java deleted file mode 100644 index 99d7e1b..0000000 --- a/collector-grpc/src/test/java/zipkin2/collector/otel/grpc/ITOpenTelemetryGrpcCollector.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin2.collector.otel.grpc; - -import java.io.IOException; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.TestInstance; -import zipkin2.collector.CollectorComponent; -import zipkin2.collector.CollectorSampler; -import zipkin2.collector.InMemoryCollectorMetrics; -import zipkin2.storage.InMemoryStorage; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class ITOpenTelemetryGrpcCollector { - InMemoryStorage store; - InMemoryCollectorMetrics metrics; - CollectorComponent collector; - - @BeforeEach public void setup() { - store = InMemoryStorage.newBuilder().build(); - metrics = new InMemoryCollectorMetrics(); - - collector = OpenTelemetryGrpcCollector.newBuilder() - .metrics(metrics) - .sampler(CollectorSampler.ALWAYS_SAMPLE) - .storage(store) - .build() - .start(); - metrics = metrics.forTransport("otel/grpc"); - } - - @AfterEach void teardown() throws IOException { - store.close(); - collector.close(); - } - - // TODO: integration test -} diff --git a/collector-grpc/src/test/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollectorTest.java b/collector-grpc/src/test/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollectorTest.java deleted file mode 100644 index 53b33fa..0000000 --- a/collector-grpc/src/test/java/zipkin2/collector/otel/grpc/OpenTelemetryGrpcCollectorTest.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin2.collector.otel.grpc; - -import org.junit.jupiter.api.Test; -import zipkin2.storage.InMemoryStorage; - -import static org.assertj.core.api.Assertions.assertThat; - -class OpenTelemetryGrpcCollectorTest { - OpenTelemetryGrpcCollector collector = OpenTelemetryGrpcCollector.newBuilder() - .storage(InMemoryStorage.newBuilder().build()) - .build(); - - @Test void check_ok() { - assertThat(collector.check().ok()).isTrue(); - } - - /** - * The output of toString() on {@link zipkin2.collector.Collector} implementations appear in the - * /health endpoint. Make sure it is minimal and human-readable. - */ - @Test void toStringContainsOnlyConfigurableFields() { - assertThat(collector.toString()) - .isEqualTo("OpenTelemetryGrpcCollector{}"); - } -} diff --git a/module/README.md b/module/README.md index 641348c..730f70a 100644 --- a/module/README.md +++ b/module/README.md @@ -4,7 +4,7 @@ This is a module that can be added to a [Zipkin Server](https://github.com/openzipkin/zipkin/tree/master/zipkin-server) -deployment to receive Spans to OpenTelemetry's OLTP/GRPC and OLTP/HTTP protocols. +deployment to receive Spans to OpenTelemetry's OLTP/HTTP protocols. ## Experimental @@ -49,7 +49,6 @@ for users that prefer a file based approach. | Property | Environment Variable | Description | |--------------------------------------|-------------------------------|----------------------------------------------------------| -| `zipkin.collector.otel.grpc.enabled` | `COLLECTOR_GRPC_OTEL_ENABLED` | `false` disables the GRPC collector. Defaults to `true`. | | `zipkin.collector.otel.http.enabled` | `COLLECTOR_HTTP_OTEL_ENABLED` | `false` disables the HTTP collector. Defaults to `true`. | ### Running diff --git a/module/pom.xml b/module/pom.xml index caf72b5..93008ab 100644 --- a/module/pom.xml +++ b/module/pom.xml @@ -32,11 +32,6 @@ - - ${project.groupId} - zipkin-collector-otel-grpc - ${project.version} - ${project.groupId} zipkin-collector-otel-http @@ -68,12 +63,6 @@ ${armeria.version} test - - ${armeria.groupId} - armeria-grpc - ${armeria.version} - test - org.awaitility awaitility diff --git a/module/src/main/java/zipkin/module/otel/ZipkinOpenTelemetryGrpcCollectorModule.java b/module/src/main/java/zipkin/module/otel/ZipkinOpenTelemetryGrpcCollectorModule.java deleted file mode 100644 index ae9e950..0000000 --- a/module/src/main/java/zipkin/module/otel/ZipkinOpenTelemetryGrpcCollectorModule.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin.module.otel; - -import com.linecorp.armeria.spring.ArmeriaServerConfigurator; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import zipkin2.collector.CollectorMetrics; -import zipkin2.collector.CollectorSampler; -import zipkin2.collector.otel.grpc.OpenTelemetryGrpcCollector; -import zipkin2.storage.StorageComponent; - -@Configuration -@ConditionalOnProperty(name = "zipkin.collector.otel.grpc.enabled", matchIfMissing = true) -@EnableConfigurationProperties(ZipkinOpenTelemetryGrpcCollectorProperties.class) -class ZipkinOpenTelemetryGrpcCollectorModule { - @Bean OpenTelemetryGrpcCollector otelGrpcCollector(StorageComponent storage, - CollectorSampler sampler, CollectorMetrics metrics) { - return OpenTelemetryGrpcCollector.newBuilder() - .storage(storage) - .sampler(sampler) - .metrics(metrics) - .build(); - } - - @Bean ArmeriaServerConfigurator otelGrpcCollectorConfigurator( - OpenTelemetryGrpcCollector collector) { - return collector::reconfigure; - } -} diff --git a/module/src/main/java/zipkin/module/otel/ZipkinOpenTelemetryGrpcCollectorProperties.java b/module/src/main/java/zipkin/module/otel/ZipkinOpenTelemetryGrpcCollectorProperties.java deleted file mode 100644 index 31a9c4a..0000000 --- a/module/src/main/java/zipkin/module/otel/ZipkinOpenTelemetryGrpcCollectorProperties.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin.module.otel; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties("zipkin.collector.otel.grpc") -public class ZipkinOpenTelemetryGrpcCollectorProperties { -} diff --git a/module/src/main/java/zipkin/module/otel/ZipkinOpenTelemetryHttpCollectorModule.java b/module/src/main/java/zipkin/module/otel/ZipkinOpenTelemetryHttpCollectorModule.java index 49b51d8..6f55d1e 100644 --- a/module/src/main/java/zipkin/module/otel/ZipkinOpenTelemetryHttpCollectorModule.java +++ b/module/src/main/java/zipkin/module/otel/ZipkinOpenTelemetryHttpCollectorModule.java @@ -20,7 +20,6 @@ import org.springframework.context.annotation.Configuration; import zipkin2.collector.CollectorMetrics; import zipkin2.collector.CollectorSampler; -import zipkin2.collector.otel.grpc.OpenTelemetryGrpcCollector; import zipkin2.collector.otel.http.OpenTelemetryHttpCollector; import zipkin2.storage.StorageComponent; diff --git a/module/src/main/resources/zipkin-server-otel.yml b/module/src/main/resources/zipkin-server-otel.yml index bcf71b3..0095ceb 100644 --- a/module/src/main/resources/zipkin-server-otel.yml +++ b/module/src/main/resources/zipkin-server-otel.yml @@ -2,13 +2,9 @@ zipkin: internal: module: - grpc: zipkin.module.otel.ZipkinOpenTelemetryGrpcCollectorModule http: zipkin.module.otel.ZipkinOpenTelemetryHttpCollectorModule collector: otel: - grpc: - # Set to false to disable creation of spans via OLTP/GRPC protocol - enabled: ${COLLECTOR_GRPC_ENABLED:${COLLECTOR_OTEL_GRPC_ENABLED:true}} http: # Set to false to disable creation of spans via OLTP/HTTP protocol enabled: ${COLLECTOR_HTTP_ENABLED:${COLLECTOR_OTEL_HTTP_ENABLED:true}} diff --git a/module/src/test/java/zipkin/module/otel/ZipkinOpenTelemetryGrpcCollectorModuleTest.java b/module/src/test/java/zipkin/module/otel/ZipkinOpenTelemetryGrpcCollectorModuleTest.java deleted file mode 100644 index bc9a07b..0000000 --- a/module/src/test/java/zipkin/module/otel/ZipkinOpenTelemetryGrpcCollectorModuleTest.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin.module.otel; - -import com.linecorp.armeria.spring.ArmeriaServerConfigurator; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import zipkin2.collector.CollectorMetrics; -import zipkin2.collector.CollectorSampler; -import zipkin2.collector.otel.grpc.OpenTelemetryGrpcCollector; -import zipkin2.storage.InMemoryStorage; -import zipkin2.storage.StorageComponent; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; - -class ZipkinOpenTelemetryGrpcCollectorModuleTest { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - - @AfterEach void close() { - context.close(); - } - - @Test void grpcCollector_enabledByDefault() { - context.register( - ZipkinOpenTelemetryGrpcCollectorProperties.class, - ZipkinOpenTelemetryGrpcCollectorModule.class, - InMemoryConfiguration.class - ); - context.refresh(); - - assertThat(context.getBean(OpenTelemetryGrpcCollector.class)).isNotNull(); - assertThat(context.getBean(ArmeriaServerConfigurator.class)).isNotNull(); - } - - @Test void grpcCollector_canDisable() { - assertThatExceptionOfType(NoSuchBeanDefinitionException.class).isThrownBy(() -> { - TestPropertyValues.of("zipkin.collector.otel.grpc.enabled:false").applyTo(context); - context.register( - ZipkinOpenTelemetryGrpcCollectorProperties.class, - ZipkinOpenTelemetryGrpcCollectorModule.class, - InMemoryConfiguration.class - ); - context.refresh(); - - context.getBean(OpenTelemetryGrpcCollector.class); - }); - } - - @Configuration - static class InMemoryConfiguration { - @Bean CollectorSampler sampler() { - return CollectorSampler.ALWAYS_SAMPLE; - } - - @Bean CollectorMetrics metrics() { - return CollectorMetrics.NOOP_METRICS; - } - - @Bean StorageComponent storage() { - return InMemoryStorage.newBuilder().build(); - } - } -} diff --git a/pom.xml b/pom.xml index 915f434..1f51392 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,6 @@ module - collector-grpc collector-http From d64ff9b0ec4938853963004b331aafefc194b15c Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 26 Jun 2024 15:39:18 +0200 Subject: [PATCH 23/30] Left only Zipkin OTLP collector --- encoder-otel-brave/README.md | 8 - encoder-otel-brave/pom.xml | 160 ---- .../otel/brave/AttributesExtractor.java | 69 -- .../reporter/otel/brave/OtelEncoder.java | 49 - .../reporter/otel/brave/SpanTranslator.java | 302 ------ .../otel/brave/AttributesExtractorTest.java | 89 -- .../otel/brave/ITZipkinSenderTests.java | 223 ----- .../reporter/otel/brave/JaegerAllInOne.java | 62 -- .../OtelToZipkinSpanTransformerTest.java | 896 ------------------ .../otel/brave/SpanTranslatorTest.java | 127 --- .../reporter/otel/brave/TestObjects.java | 41 - pom.xml | 1 - 12 files changed, 2027 deletions(-) delete mode 100644 encoder-otel-brave/README.md delete mode 100644 encoder-otel-brave/pom.xml delete mode 100644 encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/AttributesExtractor.java delete mode 100644 encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/OtelEncoder.java delete mode 100644 encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java delete mode 100644 encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/AttributesExtractorTest.java delete mode 100644 encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/ITZipkinSenderTests.java delete mode 100644 encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/JaegerAllInOne.java delete mode 100644 encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/OtelToZipkinSpanTransformerTest.java delete mode 100644 encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/SpanTranslatorTest.java delete mode 100644 encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/TestObjects.java diff --git a/encoder-otel-brave/README.md b/encoder-otel-brave/README.md deleted file mode 100644 index 68f1ff1..0000000 --- a/encoder-otel-brave/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# encoder-otel-brave - -This encodes brave spans into OTLP proto format. - -```java -// connect the sender to the correct encoding -spanHandler = AsyncZipkinSpanHandler.newBuilder(sender).build(new OtelEncoder(Tags.ERROR)); -``` diff --git a/encoder-otel-brave/pom.xml b/encoder-otel-brave/pom.xml deleted file mode 100644 index a3f31e0..0000000 --- a/encoder-otel-brave/pom.xml +++ /dev/null @@ -1,160 +0,0 @@ - - - - - io.zipkin.contrib.otel - zipkin-otel-parent - 0.1.0-SNAPSHOT - ../pom.xml - - 4.0.0 - - brave-encoder-otel - Brave Encoder: OpenTelemetry Trace - - - ${project.basedir}/.. - - - - - io.opentelemetry - opentelemetry-api - - - io.opentelemetry - opentelemetry-sdk-common - - - io.opentelemetry.proto - opentelemetry-proto - - - io.opentelemetry.semconv - opentelemetry-semconv - - - com.google.protobuf - protobuf-java - ${protobuf.version} - - provided - - - - - io.zipkin.reporter2 - zipkin-reporter-brave - ${zipkin-reporter.version} - - - ${brave.groupId} - brave - ${brave.version} - - provided - - - - org.mock-server - mockserver-netty - 5.15.0 - test - shaded - - - io.opentelemetry - opentelemetry-sdk - test - - - io.opentelemetry - opentelemetry-sdk-testing - test - - - io.opentelemetry - opentelemetry-exporter-common - test - - - io.opentelemetry - opentelemetry-exporter-otlp-common - test - - - com.squareup.okhttp3 - okhttp - ${okhttp.version} - test - - - io.zipkin.reporter2 - zipkin-sender-okhttp3 - ${zipkin-reporter.version} - test - - - com.squareup.okhttp3 - mockwebserver - ${okhttp.version} - test - - - ${armeria.groupId} - armeria-junit5 - ${armeria.version} - test - - - org.awaitility - awaitility - ${awaitility.version} - test - - - org.testcontainers - testcontainers - ${testcontainers.version} - test - - - org.slf4j - * - - - - - org.testcontainers - junit-jupiter - ${testcontainers.version} - test - - - - - - - io.netty - netty-bom - 4.1.108.Final - pom - import - - - - \ No newline at end of file diff --git a/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/AttributesExtractor.java b/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/AttributesExtractor.java deleted file mode 100644 index 6d3bfb6..0000000 --- a/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/AttributesExtractor.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin2.reporter.otel.brave; - -import brave.Tag; -import brave.handler.MutableSpan; -import io.opentelemetry.proto.common.v1.AnyValue; -import io.opentelemetry.proto.common.v1.KeyValue; -import io.opentelemetry.proto.trace.v1.Span; -import io.opentelemetry.proto.trace.v1.Status; -import io.opentelemetry.proto.trace.v1.Status.StatusCode; -import java.util.Map; - -/** - * LabelExtractor extracts the set of OTel Span labels equivalent to the annotations in a - * given Zipkin Span. - * - *

Zipkin annotations are converted to OTel Span labels by using annotation.value as the - * key and annotation.timestamp as the value. - * - *

Zipkin tags are converted to OTel Span labels by using annotation.key as the key and - * the String value of annotation.value as the value. - * - *

Zipkin annotations with equivalent OTel labels will be renamed to the Stackdriver - * Trace name. - */ -final class AttributesExtractor { - - private final Tag errorTag; - private final Map renamedLabels; - - AttributesExtractor(Tag errorTag, Map renamedLabels) { - this.errorTag = errorTag; - this.renamedLabels = renamedLabels; - } - - void addErrorTag(Span.Builder target, MutableSpan braveSpan) { - String errorValue = errorTag.value(braveSpan.error(), null); - if (errorValue != null) { - target.addAttributes(KeyValue.newBuilder().setKey(getLabelName("error")).setValue( - AnyValue.newBuilder().setStringValue(errorValue).build()).build()); - target.setStatus(Status.newBuilder().setCode(StatusCode.STATUS_CODE_ERROR).build()); - } else { - target.setStatus(Status.newBuilder().setCode(StatusCode.STATUS_CODE_OK).build()); - } - } - - void addTag(Span.Builder target, String key, String value) { - target.addAttributes(KeyValue.newBuilder().setKey(getLabelName(key)).setValue( - AnyValue.newBuilder().setStringValue(value).build()).build()); - } - - private String getLabelName(String zipkinName) { - String renamed = renamedLabels.get(zipkinName); - return renamed != null ? renamed : zipkinName; - } - -} diff --git a/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/OtelEncoder.java b/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/OtelEncoder.java deleted file mode 100644 index 57643db..0000000 --- a/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/OtelEncoder.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin2.reporter.otel.brave; - -import brave.Tag; -import brave.handler.MutableSpan; -import io.opentelemetry.proto.trace.v1.TracesData; -import zipkin2.reporter.BytesEncoder; -import zipkin2.reporter.Encoding; - -@SuppressWarnings("ImmutableEnumChecker") // because span is immutable -public class OtelEncoder implements BytesEncoder { - final SpanTranslator spanTranslator; - - public OtelEncoder(Tag errorTag) { - if (errorTag == null) throw new NullPointerException("errorTag == null"); - this.spanTranslator = new SpanTranslator(errorTag); - } - - @Override - public Encoding encoding() { - return Encoding.PROTO3; - } - - @Override public int sizeInBytes(MutableSpan span) { - // TODO: Optimize this by caching? - TracesData convert = translate(span); - return encoding().listSizeInBytes(convert.getSerializedSize()); - } - - @Override public byte[] encode(MutableSpan span) { - return translate(span).toByteArray(); - } - - TracesData translate(MutableSpan span) { - return spanTranslator.translate(span); - } -} diff --git a/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java b/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java deleted file mode 100644 index 1112630..0000000 --- a/encoder-otel-brave/src/main/java/zipkin2/reporter/otel/brave/SpanTranslator.java +++ /dev/null @@ -1,302 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin2.reporter.otel.brave; - -import static io.opentelemetry.api.common.AttributeKey.stringKey; - -import brave.Span.Kind; -import brave.Tag; -import brave.handler.MutableSpan; -import brave.handler.MutableSpan.AnnotationConsumer; -import brave.handler.MutableSpan.TagConsumer; -import com.google.protobuf.ByteString; -import io.opentelemetry.proto.common.v1.AnyValue; -import io.opentelemetry.proto.common.v1.KeyValue; -import io.opentelemetry.proto.trace.v1.ResourceSpans; -import io.opentelemetry.proto.trace.v1.ResourceSpans.Builder; -import io.opentelemetry.proto.trace.v1.ScopeSpans; -import io.opentelemetry.proto.trace.v1.Span; -import io.opentelemetry.proto.trace.v1.Span.Event; -import io.opentelemetry.proto.trace.v1.Span.Link; -import io.opentelemetry.proto.trace.v1.Span.SpanKind; -import io.opentelemetry.proto.trace.v1.TracesData; -import io.opentelemetry.semconv.HttpAttributes; -import io.opentelemetry.semconv.NetworkAttributes; -import io.opentelemetry.semconv.SemanticAttributes; -import io.opentelemetry.semconv.ServerAttributes; -import io.opentelemetry.semconv.ServiceAttributes; -import io.opentelemetry.semconv.UrlAttributes; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - - -/** - * SpanTranslator converts a Brave Span to a OpenTelemetry Span. - */ -final class SpanTranslator { - - static final String KEY_INSTRUMENTATION_SCOPE_NAME = "otel.scope.name"; - static final String KEY_INSTRUMENTATION_SCOPE_VERSION = "otel.scope.version"; - static final String KEY_INSTRUMENTATION_LIBRARY_NAME = "otel.library.name"; - static final String KEY_INSTRUMENTATION_LIBRARY_VERSION = "otel.library.version"; - static final String OTEL_STATUS_CODE = "otel.status_code"; - - private static final Map RENAMED_LABELS; - - static { - RENAMED_LABELS = new LinkedHashMap<>(); - RENAMED_LABELS.put("http.host", ServerAttributes.SERVER_ADDRESS.getKey()); - RENAMED_LABELS.put("http.method", HttpAttributes.HTTP_REQUEST_METHOD.getKey()); - RENAMED_LABELS.put("http.status_code", HttpAttributes.HTTP_RESPONSE_STATUS_CODE.getKey()); - RENAMED_LABELS.put("http.request.size", "http.request.body.size"); - RENAMED_LABELS.put("http.response.size", "http.response.body.size"); - RENAMED_LABELS.put("http.url", UrlAttributes.URL_FULL.getKey()); - } - - private final Consumer consumer; - - SpanTranslator(Tag errorTag) { - this.consumer = new Consumer(new AttributesExtractor(errorTag, RENAMED_LABELS)); - } - - /** - * Converts a Zipkin Span into a OpenTelemetry Span. - * - *

Ex. - * - *

{@code
-   * tracesData = SpanTranslator.translate(braveSpan);
-   * }
- * - * @param braveSpan The Zipkin Span. - * @return A OpenTelemetry Span. - */ - TracesData translate(MutableSpan braveSpan) { - TracesData.Builder tracesDataBuilder = TracesData.newBuilder(); - Builder resourceSpansBuilder = ResourceSpans.newBuilder(); - ScopeSpans.Builder scopeSpanBuilder = ScopeSpans.newBuilder(); - Span.Builder spanBuilder = builderForSingleSpan(braveSpan, resourceSpansBuilder); - scopeSpanBuilder.addSpans(spanBuilder - .build()); - resourceSpansBuilder.addScopeSpans(scopeSpanBuilder - .build()); - tracesDataBuilder.addResourceSpans(resourceSpansBuilder.build()); - return tracesDataBuilder.build(); - } - - private Span.Builder builderForSingleSpan(MutableSpan span, Builder resourceSpansBuilder) { - Span.Builder spanBuilder = Span.newBuilder() - .setTraceId(ByteString.fromHex(span.traceId() != null ? span.traceId() : io.opentelemetry.api.trace.SpanContext.getInvalid().getTraceId())) - .setSpanId(ByteString.fromHex(span.id() != null ? span.id() : io.opentelemetry.api.trace.SpanContext.getInvalid().getSpanId())) - .setName((span.name() == null || span.name().isEmpty()) ? "unknown" : span.name()); - if (span.parentId() != null) { - spanBuilder.setParentSpanId(ByteString.fromHex(span.parentId())); - } - long start = span.startTimestamp(); - long finish = span.finishTimestamp(); - spanBuilder.setStartTimeUnixNano(TimeUnit.MICROSECONDS.toNanos(start)); - if (start != 0 && finish != 0L) { - spanBuilder.setEndTimeUnixNano(TimeUnit.MICROSECONDS.toNanos(finish)); - } - Kind kind = span.kind(); - if (kind != null) { - switch (kind) { - case CLIENT: - spanBuilder.setKind(SpanKind.SPAN_KIND_CLIENT); - break; - case SERVER: - spanBuilder.setKind(SpanKind.SPAN_KIND_SERVER); - break; - case PRODUCER: - spanBuilder.setKind(SpanKind.SPAN_KIND_PRODUCER); - break; - case CONSUMER: - spanBuilder.setKind(SpanKind.SPAN_KIND_CONSUMER); - break; - default: - spanBuilder.setKind(SpanKind.SPAN_KIND_INTERNAL); //TODO: Should it work like this? - } - } else { - spanBuilder.setKind(SpanKind.SPAN_KIND_INTERNAL); //TODO: Should it work like this? - } - String localServiceName = span.localServiceName(); - if (localServiceName != null) { - resourceSpansBuilder.getResourceBuilder().addAttributes( - KeyValue.newBuilder().setKey(ServiceAttributes.SERVICE_NAME.getKey()) - .setValue(AnyValue.newBuilder().setStringValue(localServiceName).build()).build()); - } else { - resourceSpansBuilder.getResourceBuilder() - .addAttributes(KeyValue.newBuilder().setKey("service.name").setValue( - AnyValue.newBuilder().setStringValue( - io.opentelemetry.sdk.resources.Resource.getDefault().getAttribute(stringKey("service.name"))).build()).build()); - } - String localIp = span.localIp(); - if (localIp != null) { - spanBuilder.addAttributes(KeyValue.newBuilder().setKey(NetworkAttributes.NETWORK_LOCAL_ADDRESS.getKey()) - .setValue(AnyValue.newBuilder().setStringValue(localIp).build()).build()); - } - int localPort = span.localPort(); - if (localPort != 0) { - spanBuilder.addAttributes(KeyValue.newBuilder().setKey(ServerAttributes.SERVER_PORT.getKey()) - .setValue(AnyValue.newBuilder().setIntValue(localPort).build()).build()); - } - String peerName = span.remoteServiceName(); - if (peerName != null) { - spanBuilder.addAttributes(KeyValue.newBuilder().setKey(SemanticAttributes.NET_SOCK_PEER_NAME.getKey()) - .setValue(AnyValue.newBuilder().setStringValue(peerName).build()).build()); - } - String peerIp = span.remoteIp(); - if (peerIp != null) { - spanBuilder.addAttributes(KeyValue.newBuilder().setKey(SemanticAttributes.NET_SOCK_PEER_ADDR.getKey()) - .setValue(AnyValue.newBuilder().setStringValue(peerIp).build()).build()); - } - int peerPort = span.remotePort(); - if (peerPort != 0) { - spanBuilder.addAttributes(KeyValue.newBuilder().setKey(SemanticAttributes.NET_SOCK_PEER_PORT.getKey()) - .setValue(AnyValue.newBuilder().setIntValue(peerPort).build()).build()); - } - // TODO: What should we put here? - // tags taken from OtelToZipkinSpanTransformer - spanBuilder.addAttributes(KeyValue.newBuilder().setKey(KEY_INSTRUMENTATION_SCOPE_NAME).setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()).build()); - // TODO: Hardcoded library version - spanBuilder.addAttributes(KeyValue.newBuilder().setKey(KEY_INSTRUMENTATION_SCOPE_VERSION).setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()); - // Include instrumentation library name for backwards compatibility - spanBuilder.addAttributes(KeyValue.newBuilder().setKey(KEY_INSTRUMENTATION_LIBRARY_NAME).setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()).build()); - // TODO: Hardcoded library version - // Include instrumentation library name for backwards compatibility - spanBuilder.addAttributes(KeyValue.newBuilder().setKey(KEY_INSTRUMENTATION_LIBRARY_VERSION).setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()); - - span.forEachTag(consumer, spanBuilder); - span.forEachAnnotation(consumer, spanBuilder); - consumer.addErrorTag(spanBuilder, span); - - List links = LinkUtils.toLinks(span.tags()); - spanBuilder.addAllLinks(links); - - return spanBuilder; - } - - class Consumer implements TagConsumer, AnnotationConsumer { - - private final AttributesExtractor attributesExtractor; - - Consumer(AttributesExtractor attributesExtractor) { - this.attributesExtractor = attributesExtractor; - } - - @Override - public void accept(Span.Builder target, String key, String value) { - if (!LinkUtils.isApplicable(key)) { - attributesExtractor.addTag(target, key, value); - } - } - - void addErrorTag(Span.Builder target, MutableSpan span) { - attributesExtractor.addErrorTag(target, span); - } - - @Override - public void accept(Span.Builder target, long timestamp, String value) { - target.addEvents(Event.newBuilder().setTimeUnixNano(TimeUnit.MICROSECONDS.toNanos(timestamp)) - .setName(value).build()); - } - } - - static class LinkUtils { - - private static final String LINKS_PREFIX = "links["; - - private static final Pattern LINKS_ID = Pattern.compile("^links\\[(.*)]\\..*$"); - - private static final Pattern TAG_KEY = Pattern.compile("^links\\[.*]\\.tags\\[(.*)]$"); - - static boolean isApplicable(Map.Entry entry) { - return isApplicable(entry.getKey()); - } - - private static boolean isApplicable(String key) { - return key.startsWith(LINKS_PREFIX); - } - - private static int linkGroup(Map.Entry entry) { - Matcher matcher = LINKS_ID.matcher(entry.getKey()); - if (matcher.matches()) { - return Integer.parseInt(matcher.group(1)); - } - return -1; - } - - static List toLinks(Map tags) { - return tags.entrySet() - .stream() - .filter(LinkUtils::isApplicable) - .collect(Collectors.groupingBy(LinkUtils::linkGroup)) - .values() - .stream().map(LinkUtils::toLink) - .collect(Collectors.toList()); - } - - private static Link toLink(List> groupedTags) { - String traceId = ""; - String spanId = ""; - Map tags = new HashMap<>(); - for (Map.Entry groupedTag : groupedTags) { - if (groupedTag.getKey().endsWith(".traceId")) { - traceId = groupedTag.getValue(); - } - else if (groupedTag.getKey().endsWith(".spanId")) { - spanId = groupedTag.getValue(); - } - else if (groupedTag.getKey().contains("tags")) { - String tagKey = tagKeyNameFromString(groupedTag.getKey()); - if (tagKey != null) { - tags.put(tagKey, groupedTag.getValue()); - } - } - } - if (traceId != null && !traceId.isEmpty()) { - List keyValues = tags.entrySet().stream().map(e -> KeyValue.newBuilder().setKey(e.getKey()).setValue( - AnyValue.newBuilder().setStringValue(String.valueOf(e.getValue())).build()).build()).collect( - Collectors.toList()); - return Link.newBuilder() - .setSpanId(ByteString.fromHex(spanId)) - .setTraceId(ByteString.fromHex(traceId)) - .addAllAttributes(keyValues) - .build(); - } - return null; - } - - static String tagKeyNameFromString(String tag) { - Matcher matcher = TAG_KEY.matcher(tag); - if (matcher.matches()) { - return matcher.group(1); - } - return null; - } - - } - -} diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/AttributesExtractorTest.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/AttributesExtractorTest.java deleted file mode 100644 index af59409..0000000 --- a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/AttributesExtractorTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin2.reporter.otel.brave; - -import static org.assertj.core.api.Assertions.assertThat; - -import brave.Tags; -import brave.handler.MutableSpan; -import brave.propagation.TraceContext; -import io.opentelemetry.proto.common.v1.AnyValue; -import io.opentelemetry.proto.common.v1.KeyValue; -import io.opentelemetry.proto.trace.v1.Span; -import java.util.Collections; -import org.assertj.core.util.Maps; -import org.junit.jupiter.api.Test; - -class AttributesExtractorTest { - @Test void testTag() { - AttributesExtractor extractor = new AttributesExtractor(Tags.ERROR, Collections.emptyMap()); - Span.Builder builder = Span.newBuilder(); - - extractor.addTag(builder, "tag", "value"); - - Span span = builder.build(); - KeyValue keyValue = span.getAttributes(0); - assertThat(keyValue.getKey()).isEqualTo("tag"); - assertThat(keyValue.getValue()).isEqualTo(AnyValue.newBuilder().setStringValue("value").build()); - } - - @Test void testTagWithRename() { - AttributesExtractor extractor = new AttributesExtractor(Tags.ERROR, Maps.newHashMap("tag", "tag2")); - Span.Builder builder = Span.newBuilder(); - - extractor.addTag(builder, "tag", "value"); - - Span span = builder.build(); - KeyValue keyValue = span.getAttributes(0); - assertThat(keyValue.getKey()).isEqualTo("tag2"); - assertThat(keyValue.getValue()).isEqualTo(AnyValue.newBuilder().setStringValue("value").build()); - } - - @Test void testErrorTag() { - AttributesExtractor extractor = new AttributesExtractor(Tags.ERROR, Collections.emptyMap()); - Span.Builder builder = Span.newBuilder(); - - MutableSpan serverSpan = spanWithError(); - - extractor.addErrorTag(builder, serverSpan); - - Span span = builder.build(); - KeyValue keyValue = span.getAttributes(0); - assertThat(keyValue.getKey()).isEqualTo("error"); - assertThat(keyValue.getValue()).isEqualTo(AnyValue.newBuilder().setStringValue("this cake is a lie").build()); - } - - @Test void testErrorTagWithRename() { - AttributesExtractor extractor = new AttributesExtractor(Tags.ERROR, Maps.newHashMap("error", "error2")); - Span.Builder builder = Span.newBuilder(); - - MutableSpan serverSpan = spanWithError(); - - extractor.addErrorTag(builder, serverSpan); - - Span span = builder.build(); - KeyValue keyValue = span.getAttributes(0); - assertThat(keyValue.getKey()).isEqualTo("error2"); - assertThat(keyValue.getValue()).isEqualTo(AnyValue.newBuilder().setStringValue("this cake is a lie").build()); - } - - private static MutableSpan spanWithError() { - MutableSpan serverSpan = - new MutableSpan(TraceContext.newBuilder().traceId(4).spanId(5).build(), null); - serverSpan.name("test-span"); - serverSpan.error(new RuntimeException("this cake is a lie")); - return serverSpan; - } - -} diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/ITZipkinSenderTests.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/ITZipkinSenderTests.java deleted file mode 100644 index 0741386..0000000 --- a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/ITZipkinSenderTests.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin2.reporter.otel.brave; - -import static org.assertj.core.api.BDDAssertions.then; - -import brave.Span; -import brave.Span.Kind; -import brave.Tags; -import brave.Tracer; -import brave.Tracer.SpanInScope; -import brave.Tracing; -import brave.handler.MutableSpan; -import brave.handler.SpanHandler; -import brave.propagation.ThreadLocalCurrentTraceContext; -import brave.propagation.TraceContext; -import brave.sampler.Sampler; -import java.io.Closeable; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Function; -import okhttp3.OkHttpClient; -import okhttp3.OkHttpClient.Builder; -import okhttp3.Request; -import okhttp3.Response; -import org.awaitility.Awaitility; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; -import zipkin2.reporter.AsyncReporter; -import zipkin2.reporter.Encoding; -import zipkin2.reporter.okhttp3.OkHttpSender; - -@Testcontainers(disabledWithoutDocker = true) -class ITZipkinSenderTests { - - private static final Logger log = LoggerFactory.getLogger(ITZipkinSenderTests.class); - - private static final int EXPECTED_TRACE_SIZE = 5; - - @Container - JaegerAllInOne jaegerAllInOne = new JaegerAllInOne(); - - JaegerTestingScenario testingScenario; - - @Test - void shouldSendBraveSpansToJaegerOtlpEndpoint() throws Exception { - testingScenario = new JaegerTestingScenario( - jaegerAllInOne.getHttpOtlpPort()); - - List traceIds = testingScenario.exportedTraceIds(); - - Awaitility.await().untilAsserted(() -> { - then(traceIds).hasSize(EXPECTED_TRACE_SIZE); - thenAllTraceIdsPresentInBackend(testingScenario.queryUrl(), traceIds); - }); - } - - @AfterEach - void shutdown() { - testingScenario.close(); - } - - private static void thenAllTraceIdsPresentInBackend(String queryUrl, List traceIds) { - OkHttpClient client = new Builder() - .build(); - - traceIds.forEach(traceId -> { - Request request = new Request.Builder().url(queryUrl + traceId).build(); - try (Response response = client.newCall(request).execute()) { - then(response.isSuccessful()).isTrue(); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - } - - /** - * Sender: Brave OKHttp with OTLP proto over HTTP ; Receiver: Jaeger OTLP - */ - class JaegerTestingScenario { - - private final BraveTraceIdGenerator braveTraceIdGenerator; - - JaegerTestingScenario(int otlpPort) { - this.braveTraceIdGenerator = new BraveTraceIdGenerator(otlpPort); - } - - String queryUrl() { - return "http://localhost:" + jaegerAllInOne.getQueryPort() + "/api/traces/"; - } - - List exportedTraceIds() throws Exception { - return braveTraceIdGenerator.traceIds(); - } - - public void close() { - braveTraceIdGenerator.close(); - } - } - - /** - * Actual testing logic that uses Brave to generate spans and send them to the backend. - */ - static class BraveTraceIdGenerator { - - private final BraveHttpSenderProvider braveHttpSenderProvider = new BraveHttpSenderProvider(); - - private final int port; - - Tracing tracing; - - BraveTraceIdGenerator(int port) { - this.port = port; - } - - List traceIds() throws Exception { - ThreadLocalCurrentTraceContext braveCurrentTraceContext = ThreadLocalCurrentTraceContext.newBuilder() - .build(); - List traceIds = new ArrayList<>(); - tracing = Tracing.newBuilder() - .currentTraceContext(braveCurrentTraceContext) - .supportsJoin(false) - .traceId128Bit(true) - .sampler(Sampler.ALWAYS_SAMPLE) - .addSpanHandler(braveHttpSenderProvider.apply(port)) - .localServiceName("my-service") - .build(); - Tracer braveTracer = tracing.tracer(); - - for (int i = 0; i < EXPECTED_TRACE_SIZE; i++) { - Span span = braveTracer.nextSpan().name("foo " + i) - .tag("foo tag", "foo value") - .kind(Kind.CONSUMER) - .error(new RuntimeException("BOOOOOM!")) - .remoteServiceName("remote service") - .start(); - try (SpanInScope scope = braveTracer.withSpanInScope(span)) { - String traceId = span.context().traceIdString(); - log.info("Trace Id <" + traceId + ">"); - span.remoteIpAndPort("http://localhost", 123456); - Thread.sleep(50); - span.annotate("boom!"); - Thread.sleep(50); - } finally { - span.finish(); - } - - traceIds.add(span.context().traceIdString()); - } - flush(); - return traceIds; - } - - private void flush() { - braveHttpSenderProvider.flush(); - } - - public void close() { - braveHttpSenderProvider.close(); - tracing.close(); - } - - /** - * Provides a {@link SpanHandler} that uses OKHttp to send spans to a given port. - */ - static class BraveHttpSenderProvider implements Function, Closeable { - - OkHttpSender okHttpSender; - - AsyncReporter reporter; - - SpanHandler spanHandler; - - @Override - public void close() { - if (reporter != null) { - reporter.close(); - } - if (okHttpSender != null) { - okHttpSender.close(); - } - } - - @Override - public SpanHandler apply(Integer port) { - okHttpSender = OkHttpSender.newBuilder() - .encoding(Encoding.PROTO3) - .endpoint("http://localhost:" + port + "/v1/traces") - .build(); - OtelEncoder otelEncoder = new OtelEncoder(Tags.ERROR); - reporter = AsyncReporter.builder(okHttpSender).build(otelEncoder); - spanHandler = new SpanHandler() { - @Override - public boolean end(TraceContext context, MutableSpan span, Cause cause) { - reporter.report(span); - return true; - } - }; - return spanHandler; - } - - private void flush() { - reporter.flush(); - } - } - } -} \ No newline at end of file diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/JaegerAllInOne.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/JaegerAllInOne.java deleted file mode 100644 index 3f65ea6..0000000 --- a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/JaegerAllInOne.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin2.reporter.otel.brave; - -import java.util.Collections; -import java.util.Set; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; - -class JaegerAllInOne extends GenericContainer { - - static final int JAEGER_QUERY_PORT = 16686; - - static final int JAEGER_ADMIN_PORT = 14269; - - static final int HTTP_OTLP_PORT = 4318; - - JaegerAllInOne() { - super("jaegertracing/all-in-one:1.57"); - init(); - } - - private void init() { - waitingFor(new BoundPortHttpWaitStrategy(JAEGER_ADMIN_PORT)); - withExposedPorts(JAEGER_ADMIN_PORT, - JAEGER_QUERY_PORT, - HTTP_OTLP_PORT); - } - - int getHttpOtlpPort() { - return getMappedPort(HTTP_OTLP_PORT); - } - - int getQueryPort() { - return getMappedPort(JAEGER_QUERY_PORT); - } - - private static class BoundPortHttpWaitStrategy extends HttpWaitStrategy { - private final int port; - - public BoundPortHttpWaitStrategy(int port) { - this.port = port; - } - - @Override - protected Set getLivenessCheckPorts() { - int mappedPort = this.waitStrategyTarget.getMappedPort(port); - return Collections.singleton(mappedPort); - } - } -} \ No newline at end of file diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/OtelToZipkinSpanTransformerTest.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/OtelToZipkinSpanTransformerTest.java deleted file mode 100644 index c300354..0000000 --- a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/OtelToZipkinSpanTransformerTest.java +++ /dev/null @@ -1,896 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin2.reporter.otel.brave; - -import static io.opentelemetry.api.common.AttributeKey.stringKey; -import static org.assertj.core.api.Assertions.assertThat; - -import brave.Span; -import brave.Span.Kind; -import brave.Tags; -import brave.handler.MutableSpan; -import com.google.protobuf.ByteString; -import io.opentelemetry.api.trace.SpanContext; -import io.opentelemetry.proto.common.v1.AnyValue; -import io.opentelemetry.proto.common.v1.KeyValue; -import io.opentelemetry.proto.trace.v1.ResourceSpans; -import io.opentelemetry.proto.trace.v1.ScopeSpans; -import io.opentelemetry.proto.trace.v1.Span.Event; -import io.opentelemetry.proto.trace.v1.Span.SpanKind; -import io.opentelemetry.proto.trace.v1.Status; -import io.opentelemetry.proto.trace.v1.Status.StatusCode; -import io.opentelemetry.proto.trace.v1.TracesData; -import io.opentelemetry.sdk.resources.Resource; -import java.util.concurrent.TimeUnit; -import javax.annotation.Nullable; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; - -// Adopted from https://github.com/open-telemetry/opentelemetry-java/blob/v1.39.0/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/OtelToZipkinSpanTransformerTest.java -class OtelToZipkinSpanTransformerTest { - - static final String TRACE_ID = "d239036e7d5cec116b562147388b35bf"; - - static final String SPAN_ID = "9cc1e3049173be09"; - - static final String PARENT_SPAN_ID = "8b03ab423da481c5"; - - private OtelEncoder transformer; - - @BeforeEach - void setup() { - transformer = new OtelEncoder(Tags.ERROR); - } - - @Test - void generateSpan_remoteParent() { - MutableSpan data = span(Kind.SERVER); - - assertThat(transformer.translate(data)) - .isEqualTo( - TracesData.newBuilder().addResourceSpans(ResourceSpans - .newBuilder() - .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( - KeyValue.newBuilder().setKey("service.name").setValue( - AnyValue.newBuilder().setStringValue("tweetiebird").build()) - .build() - ).build()) - .addScopeSpans(ScopeSpans.newBuilder() - .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() - .setSpanId(ByteString.fromHex(data.id())) - .setTraceId(ByteString.fromHex(data.traceId())) - .setParentSpanId(ByteString.fromHex(data.parentId())) - .setName(data.name()) - .setStartTimeUnixNano(1505855794194009000L) - .setEndTimeUnixNano(1505855799465726000L) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes( - KeyValue.newBuilder().setKey("otel.library.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .setKind(io.opentelemetry.proto.trace.v1.Span.SpanKind.SPAN_KIND_SERVER) - .setStatus( - Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) - .addEvents(Event.newBuilder().setName("\"RECEIVED\":{}").setTimeUnixNano(1505855799433901000L).build()) - .addEvents(Event.newBuilder().setName("\"SENT\":{}").setTimeUnixNano(1505855799459486000L).build()) - .build()) - .build()) - .build()) - .build()); - } - - @Test - void generateSpan_subMicroDurations() { - MutableSpan data = span(Kind.SERVER); - data.startTimestamp(TimeUnit.NANOSECONDS.toMicros(1505855794_194009601L)); - data.finishTimestamp(TimeUnit.NANOSECONDS.toMicros(1505855794_194009999L)); - - assertThat(transformer.translate(data)) - .isEqualTo( - TracesData.newBuilder().addResourceSpans(ResourceSpans - .newBuilder() - .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( - KeyValue.newBuilder().setKey("service.name").setValue( - AnyValue.newBuilder().setStringValue("tweetiebird").build()) - .build() - ).build()) - .addScopeSpans(ScopeSpans.newBuilder() - .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() - .setSpanId(ByteString.fromHex(data.id())) - .setTraceId(ByteString.fromHex(data.traceId())) - .setParentSpanId(ByteString.fromHex(data.parentId())) - .setName(data.name()) - .setStartTimeUnixNano(1505855794_194009000L) // We lose precision - .setEndTimeUnixNano(1505855794_194009000L) // We lose precision - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes( - KeyValue.newBuilder().setKey("otel.library.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .setKind(io.opentelemetry.proto.trace.v1.Span.SpanKind.SPAN_KIND_SERVER) - .setStatus( - Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) - .addEvents(Event.newBuilder().setName("\"RECEIVED\":{}").setTimeUnixNano(1505855799433901000L).build()) - .addEvents(Event.newBuilder().setName("\"SENT\":{}").setTimeUnixNano(1505855799459486000L).build()) - .build()) - .build()) - .build()) - .build()); - } - - @Test - void generateSpan_ServerKind() { - MutableSpan data = span(Kind.SERVER); - - assertThat(transformer.translate(data)) - .isEqualTo( - TracesData.newBuilder().addResourceSpans(ResourceSpans - .newBuilder() - .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( - KeyValue.newBuilder().setKey("service.name").setValue( - AnyValue.newBuilder().setStringValue("tweetiebird").build()) - .build() - ).build()) - .addScopeSpans(ScopeSpans.newBuilder() - .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() - .setSpanId(ByteString.fromHex(data.id())) - .setTraceId(ByteString.fromHex(data.traceId())) - .setParentSpanId(ByteString.fromHex(data.parentId())) - .setName(data.name()) - .setStartTimeUnixNano(1505855794194009000L) - .setEndTimeUnixNano(1505855799465726000L) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes( - KeyValue.newBuilder().setKey("otel.library.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .setKind(io.opentelemetry.proto.trace.v1.Span.SpanKind.SPAN_KIND_SERVER) - .setStatus( - Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) - .addEvents(Event.newBuilder().setName("\"RECEIVED\":{}").setTimeUnixNano(1505855799433901000L).build()) - .addEvents(Event.newBuilder().setName("\"SENT\":{}").setTimeUnixNano(1505855799459486000L).build()) - .build()) - .build()) - .build()) - .build()); - } - - @Test - void generateSpan_ClientKind() { - MutableSpan data = span(Kind.CLIENT); - - assertThat(transformer.translate(data)) - .isEqualTo( - TracesData.newBuilder().addResourceSpans(ResourceSpans - .newBuilder() - .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( - KeyValue.newBuilder().setKey("service.name").setValue( - AnyValue.newBuilder().setStringValue("tweetiebird").build()) - .build() - ).build()) - .addScopeSpans(ScopeSpans.newBuilder() - .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() - .setSpanId(ByteString.fromHex(data.id())) - .setTraceId(ByteString.fromHex(data.traceId())) - .setParentSpanId(ByteString.fromHex(data.parentId())) - .setName(data.name()) - .setStartTimeUnixNano(1505855794194009000L) - .setEndTimeUnixNano(1505855799465726000L) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes( - KeyValue.newBuilder().setKey("otel.library.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .setKind(io.opentelemetry.proto.trace.v1.Span.SpanKind.SPAN_KIND_CLIENT) - .setStatus( - Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) - .addEvents(Event.newBuilder().setName("\"RECEIVED\":{}").setTimeUnixNano(1505855799433901000L).build()) - .addEvents(Event.newBuilder().setName("\"SENT\":{}").setTimeUnixNano(1505855799459486000L).build()) - .build()) - .build()) - .build()) - .build()); - } - - @Test - void generateSpan_InternalKind() { - MutableSpan data = span(null); - - assertThat(transformer.translate(data)) - .isEqualTo( - TracesData.newBuilder().addResourceSpans(ResourceSpans - .newBuilder() - .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( - KeyValue.newBuilder().setKey("service.name").setValue( - AnyValue.newBuilder().setStringValue("tweetiebird").build()) - .build() - ).build()) - .addScopeSpans(ScopeSpans.newBuilder() - .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() - .setSpanId(ByteString.fromHex(data.id())) - .setTraceId(ByteString.fromHex(data.traceId())) - .setParentSpanId(ByteString.fromHex(data.parentId())) - .setName(data.name()) - .setKind(SpanKind.SPAN_KIND_INTERNAL) - .setStartTimeUnixNano(1505855794194009000L) - .setEndTimeUnixNano(1505855799465726000L) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes( - KeyValue.newBuilder().setKey("otel.library.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .setKind(io.opentelemetry.proto.trace.v1.Span.SpanKind.SPAN_KIND_INTERNAL) - .setStatus( - Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) - .addEvents(Event.newBuilder().setName("\"RECEIVED\":{}").setTimeUnixNano(1505855799433901000L).build()) - .addEvents(Event.newBuilder().setName("\"SENT\":{}").setTimeUnixNano(1505855799459486000L).build()) - .build()) - .build()) - .build()) - .build()); - } - - @Test - void generateSpan_ConsumeKind() { - MutableSpan data = span(Kind.CONSUMER); - - assertThat(transformer.translate(data)) - .isEqualTo( - TracesData.newBuilder().addResourceSpans(ResourceSpans - .newBuilder() - .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( - KeyValue.newBuilder().setKey("service.name").setValue( - AnyValue.newBuilder().setStringValue("tweetiebird").build()) - .build() - ).build()) - .addScopeSpans(ScopeSpans.newBuilder() - .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() - .setSpanId(ByteString.fromHex(data.id())) - .setTraceId(ByteString.fromHex(data.traceId())) - .setParentSpanId(ByteString.fromHex(data.parentId())) - .setName(data.name()) - .setStartTimeUnixNano(1505855794194009000L) - .setEndTimeUnixNano(1505855799465726000L) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes( - KeyValue.newBuilder().setKey("otel.library.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .setKind(io.opentelemetry.proto.trace.v1.Span.SpanKind.SPAN_KIND_CONSUMER) - .setStatus( - Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) - .addEvents(Event.newBuilder().setName("\"RECEIVED\":{}").setTimeUnixNano(1505855799433901000L).build()) - .addEvents(Event.newBuilder().setName("\"SENT\":{}").setTimeUnixNano(1505855799459486000L).build()) - .build()) - .build()) - .build()) - .build()); - } - - @Test - void generateSpan_ProducerKind() { - MutableSpan data = span(Kind.PRODUCER); - - assertThat(transformer.translate(data)) - .isEqualTo( - TracesData.newBuilder().addResourceSpans(ResourceSpans - .newBuilder() - .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( - KeyValue.newBuilder().setKey("service.name").setValue( - AnyValue.newBuilder().setStringValue("tweetiebird").build()) - .build() - ).build()) - .addScopeSpans(ScopeSpans.newBuilder() - .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() - .setSpanId(ByteString.fromHex(data.id())) - .setTraceId(ByteString.fromHex(data.traceId())) - .setParentSpanId(ByteString.fromHex(data.parentId())) - .setName(data.name()) - .setStartTimeUnixNano(1505855794194009000L) - .setEndTimeUnixNano(1505855799465726000L) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes( - KeyValue.newBuilder().setKey("otel.library.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .setKind(io.opentelemetry.proto.trace.v1.Span.SpanKind.SPAN_KIND_PRODUCER) - .setStatus( - Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) - .addEvents(Event.newBuilder().setName("\"RECEIVED\":{}").setTimeUnixNano(1505855799433901000L).build()) - .addEvents(Event.newBuilder().setName("\"SENT\":{}").setTimeUnixNano(1505855799459486000L).build()) - .build()) - .build()) - .build()) - .build()); - } - - @Test - void generateSpan_ResourceServiceNameMapping() { - MutableSpan data = span(Kind.PRODUCER); - data.localServiceName("super-zipkin-service"); - - assertThat(transformer.translate(data)) - .isEqualTo( - TracesData.newBuilder().addResourceSpans(ResourceSpans - .newBuilder() - .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( - KeyValue.newBuilder().setKey("service.name").setValue( - AnyValue.newBuilder().setStringValue("super-zipkin-service").build()) - .build() - ).build()) - .addScopeSpans(ScopeSpans.newBuilder() - .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() - .setSpanId(ByteString.fromHex(data.id())) - .setTraceId(ByteString.fromHex(data.traceId())) - .setParentSpanId(ByteString.fromHex(data.parentId())) - .setName(data.name()) - .setStartTimeUnixNano(1505855794194009000L) - .setEndTimeUnixNano(1505855799465726000L) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes( - KeyValue.newBuilder().setKey("otel.library.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .setKind(SpanKind.SPAN_KIND_PRODUCER) - .setStatus( - Status.newBuilder().setCode(StatusCode.STATUS_CODE_OK).build()) - .addEvents(Event.newBuilder().setName("\"RECEIVED\":{}").setTimeUnixNano(1505855799433901000L).build()) - .addEvents(Event.newBuilder().setName("\"SENT\":{}").setTimeUnixNano(1505855799459486000L).build()) - .build()) - .build()) - .build()) - .build()); - } - - @Test - void generateSpan_defaultResourceServiceName() { - MutableSpan data = span(Kind.PRODUCER); - data.localServiceName(null); - - assertThat(transformer.translate(data)) - .isEqualTo( - TracesData.newBuilder().addResourceSpans(ResourceSpans - .newBuilder() - .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( - KeyValue.newBuilder().setKey("service.name").setValue(AnyValue.newBuilder() - .setStringValue( - Resource.getDefault().getAttribute(stringKey("service.name"))).build()) - .build() - ).build()) - .addScopeSpans(ScopeSpans.newBuilder() - .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() - .setSpanId(ByteString.fromHex(data.id())) - .setTraceId(ByteString.fromHex(data.traceId())) - .setParentSpanId(ByteString.fromHex(data.parentId())) - .setName(data.name()) - .setStartTimeUnixNano(1505855794194009000L) - .setEndTimeUnixNano(1505855799465726000L) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes( - KeyValue.newBuilder().setKey("otel.library.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .setKind(io.opentelemetry.proto.trace.v1.Span.SpanKind.SPAN_KIND_PRODUCER) - .setStatus( - Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) - .addEvents(Event.newBuilder().setName("\"RECEIVED\":{}").setTimeUnixNano(1505855799433901000L).build()) - .addEvents(Event.newBuilder().setName("\"SENT\":{}").setTimeUnixNano(1505855799459486000L).build()) - .build()) - .build()) - .build()) - .build()); - } - - @ParameterizedTest - @EnumSource( - value = Kind.class, - names = {"CLIENT", "PRODUCER"}) - void generateSpan_RemoteEndpointMapping(Kind spanKind) { - MutableSpan data = new MutableSpan(); - data.remoteServiceName("remote-test-service"); - data.remoteIp("8.8.8.8"); - data.remotePort(42); - data.kind(spanKind); - - assertThat(transformer.translate(data)) - .isEqualTo( - TracesData.newBuilder().addResourceSpans(ResourceSpans - .newBuilder() - .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( - KeyValue.newBuilder().setKey("service.name").setValue(AnyValue.newBuilder() - .setStringValue( - Resource.getDefault().getAttribute(stringKey("service.name"))).build()) - .build() - ).build()) - .addScopeSpans(ScopeSpans.newBuilder() - .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() - .setSpanId(ByteString.fromHex(SpanContext.getInvalid().getSpanId())) - .setTraceId(ByteString.fromHex(SpanContext.getInvalid().getTraceId())) - .setName("unknown") - .setKind( - toSpanKind(spanKind)) - .addAttributes(KeyValue.newBuilder().setKey("net.sock.peer.name").setValue( - AnyValue.newBuilder().setStringValue("remote-test-service").build()) - .build()) - .addAttributes(KeyValue.newBuilder().setKey("net.sock.peer.addr") - .setValue(AnyValue.newBuilder().setStringValue("8.8.8.8").build()) - .build()) - .addAttributes(KeyValue.newBuilder().setKey("net.sock.peer.port") - .setValue(AnyValue.newBuilder().setIntValue(42).build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes( - KeyValue.newBuilder().setKey("otel.library.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .setStatus( - Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) - .build()) - .build()) - .build()) - .build()); - } - - @ParameterizedTest - @EnumSource( - value = Kind.class, - names = {"SERVER", "CONSUMER"}) - void generateSpan_RemoteEndpointMappingWhenKindIsNotClientOrProducer(Kind spanKind) { - MutableSpan data = new MutableSpan(); - data.remoteServiceName("remote-test-service"); - data.remoteIp("8.8.8.8"); - data.remotePort(42); - data.kind(spanKind); - - assertThat(transformer.translate(data)) - .isEqualTo( - TracesData.newBuilder().addResourceSpans(ResourceSpans - .newBuilder() - .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( - KeyValue.newBuilder().setKey("service.name").setValue(AnyValue.newBuilder() - .setStringValue( - Resource.getDefault().getAttribute(stringKey("service.name"))).build()) - .build() - ).build()) - .addScopeSpans(ScopeSpans.newBuilder() - .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() - .setSpanId(ByteString.fromHex(SpanContext.getInvalid().getSpanId())) - .setTraceId(ByteString.fromHex(SpanContext.getInvalid().getTraceId())) - .setName("unknown") - .setKind( - toSpanKind(spanKind)) - .addAttributes(KeyValue.newBuilder().setKey("net.sock.peer.name").setValue( - AnyValue.newBuilder().setStringValue("remote-test-service").build()) - .build()) - .addAttributes(KeyValue.newBuilder().setKey("net.sock.peer.addr") - .setValue(AnyValue.newBuilder().setStringValue("8.8.8.8").build()) - .build()) - .addAttributes(KeyValue.newBuilder().setKey("net.sock.peer.port") - .setValue(AnyValue.newBuilder().setIntValue(42).build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes( - KeyValue.newBuilder().setKey("otel.library.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .setStatus( - Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) - .build()) - .build()) - .build()) - .build()); - } - - @ParameterizedTest - @EnumSource( - value = Kind.class, - names = {"CLIENT", "PRODUCER"}) - void generateSpan_RemoteEndpointMappingWhenServiceNameIsMissing(Kind spanKind) { - MutableSpan data = new MutableSpan(); - data.remoteIp("8.8.8.8"); - data.remotePort(42); - data.kind(spanKind); - - assertThat(transformer.translate(data)) - .isEqualTo( - TracesData.newBuilder().addResourceSpans(ResourceSpans - .newBuilder() - .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( - KeyValue.newBuilder().setKey("service.name").setValue(AnyValue.newBuilder() - .setStringValue( - Resource.getDefault().getAttribute(stringKey("service.name"))).build()) - .build() - ).build()) - .addScopeSpans(ScopeSpans.newBuilder() - .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() - .setSpanId(ByteString.fromHex(SpanContext.getInvalid().getSpanId())) - .setTraceId(ByteString.fromHex(SpanContext.getInvalid().getTraceId())) - .setName("unknown") - .setKind( - toSpanKind(spanKind)) - .addAttributes(KeyValue.newBuilder().setKey("net.sock.peer.addr") - .setValue(AnyValue.newBuilder().setStringValue("8.8.8.8").build()) - .build()) - .addAttributes(KeyValue.newBuilder().setKey("net.sock.peer.port") - .setValue(AnyValue.newBuilder().setIntValue(42).build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes( - KeyValue.newBuilder().setKey("otel.library.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .setStatus( - Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) - .build()) - .build()) - .build()) - .build()); - } - - @ParameterizedTest - @EnumSource( - value = Kind.class, - names = {"CLIENT", "PRODUCER"}) - void generateSpan_RemoteEndpointMappingWhenPortIsMissing(Kind spanKind) { - MutableSpan data = new MutableSpan(); - data.remoteServiceName("remote-test-service"); - data.remoteIp("8.8.8.8"); - data.kind(spanKind); - - assertThat(transformer.translate(data)) - .isEqualTo( - TracesData.newBuilder().addResourceSpans(ResourceSpans - .newBuilder() - .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( - KeyValue.newBuilder().setKey("service.name").setValue(AnyValue.newBuilder() - .setStringValue( - Resource.getDefault().getAttribute(stringKey("service.name"))).build()) - .build() - ).build()) - .addScopeSpans(ScopeSpans.newBuilder() - .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() - .setSpanId(ByteString.fromHex(SpanContext.getInvalid().getSpanId())) - .setTraceId(ByteString.fromHex(SpanContext.getInvalid().getTraceId())) - .setName("unknown") - .setKind( - toSpanKind(spanKind)) - .addAttributes(KeyValue.newBuilder().setKey("net.sock.peer.name").setValue( - AnyValue.newBuilder().setStringValue("remote-test-service").build()) - .build()) - .addAttributes(KeyValue.newBuilder().setKey("net.sock.peer.addr") - .setValue(AnyValue.newBuilder().setStringValue("8.8.8.8").build()) - .build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes( - KeyValue.newBuilder().setKey("otel.library.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .setStatus( - Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) - .build()) - .build()) - .build()) - .build()); - } - - @ParameterizedTest - @EnumSource( - value = Kind.class, - names = {"CLIENT", "PRODUCER"}) - void generateSpan_RemoteEndpointMappingWhenIpAndPortAreMissing(Kind spanKind) { - MutableSpan data = new MutableSpan(); - data.remoteServiceName("remote-test-service"); - data.kind(spanKind); - - assertThat(transformer.translate(data)) - .isEqualTo( - TracesData.newBuilder().addResourceSpans(ResourceSpans - .newBuilder() - .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( - KeyValue.newBuilder().setKey("service.name").setValue(AnyValue.newBuilder() - .setStringValue( - Resource.getDefault().getAttribute(stringKey("service.name"))).build()) - .build() - ).build()) - .addScopeSpans(ScopeSpans.newBuilder() - .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() - .setSpanId(ByteString.fromHex(SpanContext.getInvalid().getSpanId())) - .setTraceId(ByteString.fromHex(SpanContext.getInvalid().getTraceId())) - .setName("unknown") - .setKind( - toSpanKind(spanKind)) - .addAttributes(KeyValue.newBuilder().setKey("net.sock.peer.name").setValue( - AnyValue.newBuilder().setStringValue("remote-test-service").build()) - .build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes( - KeyValue.newBuilder().setKey("otel.library.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .setStatus( - Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) - .build()) - .build()) - .build()) - .build()); - } - - @Test - void generateSpan_WithAttributes() { - MutableSpan data = new MutableSpan(); - data.kind(Kind.CLIENT); - data.tag("string", "string value"); - data.tag("boolean", "false"); - data.tag("long", "9999"); - data.tag("double", "222.333"); - data.tag("booleanArray", "true,false"); - data.tag("stringArray", "Hello"); - data.tag("doubleArray", "32.33,-98.3"); - data.tag("longArray", "33,999"); - - assertThat(transformer.translate(data)) - .isEqualTo( - TracesData.newBuilder().addResourceSpans(ResourceSpans - .newBuilder() - .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( - KeyValue.newBuilder().setKey("service.name").setValue(AnyValue.newBuilder() - .setStringValue( - Resource.getDefault().getAttribute(stringKey("service.name"))).build()) - .build() - ).build()) - .addScopeSpans(ScopeSpans.newBuilder() - .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() - .setSpanId(ByteString.fromHex(SpanContext.getInvalid().getSpanId())) - .setTraceId(ByteString.fromHex(SpanContext.getInvalid().getTraceId())) - .setName("unknown") - .setKind( - toSpanKind(Kind.CLIENT)) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes( - KeyValue.newBuilder().setKey("otel.library.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("string").setValue( - AnyValue.newBuilder().setStringValue("string value").build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("boolean").setValue( - AnyValue.newBuilder().setStringValue("false").build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("long").setValue( - AnyValue.newBuilder().setStringValue("9999").build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("double").setValue( - AnyValue.newBuilder().setStringValue("222.333").build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("booleanArray").setValue( - AnyValue.newBuilder().setStringValue("true,false").build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("stringArray").setValue( - AnyValue.newBuilder().setStringValue("Hello").build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("doubleArray").setValue( - AnyValue.newBuilder().setStringValue("32.33,-98.3").build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("longArray").setValue( - AnyValue.newBuilder().setStringValue("33,999").build()).build()) - .setStatus( - Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) - .build()) - .build()) - .build()) - .build()); - } - - @Test - void generateSpan_WithInstrumentationLibraryInfo() { - MutableSpan data = new MutableSpan(); - data.kind(Kind.CLIENT); - - assertThat(transformer.translate(data)) - .isEqualTo( - TracesData.newBuilder().addResourceSpans(ResourceSpans - .newBuilder() - .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( - KeyValue.newBuilder().setKey("service.name").setValue(AnyValue.newBuilder() - .setStringValue( - Resource.getDefault().getAttribute(stringKey("service.name"))).build()) - .build() - ).build()) - .addScopeSpans(ScopeSpans.newBuilder() - .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() - .setSpanId(ByteString.fromHex(SpanContext.getInvalid().getSpanId())) - .setTraceId(ByteString.fromHex(SpanContext.getInvalid().getTraceId())) - .setName("unknown") - .setKind( - toSpanKind(Kind.CLIENT)) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes( - KeyValue.newBuilder().setKey("otel.library.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .setStatus( - Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_OK).build()) - .build()) - .build()) - .build()) - .build()); - } - - @Test - void generateSpan_AlreadyHasHttpStatusInfo() { - MutableSpan data = new MutableSpan(); - data.kind(Kind.CLIENT); - data.error(new RuntimeException("A user provided error")); - data.tag("http.response.status.code", "404"); - - assertThat(transformer.translate(data)) - .isEqualTo( - TracesData.newBuilder().addResourceSpans(ResourceSpans - .newBuilder() - .setResource(io.opentelemetry.proto.resource.v1.Resource.newBuilder().addAttributes( - KeyValue.newBuilder().setKey("service.name").setValue(AnyValue.newBuilder() - .setStringValue( - Resource.getDefault().getAttribute(stringKey("service.name"))).build()) - .build() - ).build()) - .addScopeSpans(ScopeSpans.newBuilder() - .addSpans(io.opentelemetry.proto.trace.v1.Span.newBuilder() - .setSpanId(ByteString.fromHex(SpanContext.getInvalid().getSpanId())) - .setTraceId(ByteString.fromHex(SpanContext.getInvalid().getTraceId())) - .setName("unknown") - .setKind( - toSpanKind(Kind.CLIENT)) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.scope.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("otel.library.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build()) - .addAttributes( - KeyValue.newBuilder().setKey("otel.library.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build()) - .addAttributes( - KeyValue.newBuilder().setKey("http.response.status.code").setValue( - AnyValue.newBuilder().setStringValue("404").build()).build()) - .addAttributes(KeyValue.newBuilder().setKey("error").setValue( - AnyValue.newBuilder().setStringValue("A user provided error").build()) - .build()) - .setStatus( - Status.newBuilder().setCode(Status.StatusCode.STATUS_CODE_ERROR) - .build()) - .build()) - .build()) - .build()) - .build()); - } - - static MutableSpan span(@Nullable Span.Kind kind) { - MutableSpan mutableSpan = new MutableSpan(); - mutableSpan.traceId(TRACE_ID); - mutableSpan.parentId(PARENT_SPAN_ID); - mutableSpan.id(SPAN_ID); - mutableSpan.kind(kind); - mutableSpan.name("Recv.helloworld.Greeter.SayHello"); - mutableSpan.startTimestamp(1505855794000000L + 194009601L / 1000); - mutableSpan.finishTimestamp(1505855799000000L + 465726528L / 1000); - mutableSpan.localServiceName("tweetiebird"); - mutableSpan.annotate(1505855799000000L + 433901068L / 1000, "\"RECEIVED\":{}"); - mutableSpan.annotate(1505855799000000L + 459486280L / 1000, "\"SENT\":{}"); - return mutableSpan; - } - - static io.opentelemetry.proto.trace.v1.Span.SpanKind toSpanKind(Span.Kind kind) { - switch (kind) { - case CLIENT: - return SpanKind.SPAN_KIND_CLIENT; - case SERVER: - return SpanKind.SPAN_KIND_SERVER; - case PRODUCER: - return SpanKind.SPAN_KIND_PRODUCER; - case CONSUMER: - return SpanKind.SPAN_KIND_CONSUMER; - } - return SpanKind.SPAN_KIND_INTERNAL; - } -} diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/SpanTranslatorTest.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/SpanTranslatorTest.java deleted file mode 100644 index 4a05ebe..0000000 --- a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/SpanTranslatorTest.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin2.reporter.otel.brave; - -import static org.assertj.core.api.Assertions.assertThat; -import static zipkin2.reporter.otel.brave.TestObjects.clientSpan; - -import brave.Tags; -import brave.handler.MutableSpan; -import brave.propagation.TraceContext; -import com.google.protobuf.ByteString; -import io.opentelemetry.proto.common.v1.AnyValue; -import io.opentelemetry.proto.common.v1.KeyValue; -import io.opentelemetry.proto.trace.v1.Span; -import io.opentelemetry.proto.trace.v1.Span.Event; -import io.opentelemetry.proto.trace.v1.Span.Link; -import io.opentelemetry.proto.trace.v1.Span.SpanKind; -import io.opentelemetry.proto.trace.v1.Status; -import io.opentelemetry.proto.trace.v1.Status.StatusCode; -import io.opentelemetry.proto.trace.v1.TracesData; -import java.time.Instant; -import java.util.Arrays; -import java.util.concurrent.TimeUnit; -import org.junit.jupiter.api.Test; - -class SpanTranslatorTest { - - SpanTranslator spanTranslator = new SpanTranslator(Tags.ERROR); - - /** - * This test is intentionally sensitive, so changing other parts makes obvious impact here - */ - @Test - void translate_clientSpan() { - MutableSpan braveSpan = clientSpan(); - TracesData translated = spanTranslator.translate(braveSpan); - - assertThat(firstSpan(translated)) - .isEqualTo( - Span.newBuilder() - .setTraceId(ByteString.fromHex(braveSpan.traceId())) - .setSpanId(ByteString.fromHex(braveSpan.id())) - .setParentSpanId(ByteString.fromHex(braveSpan.parentId())) - .setName("get") - .setKind(SpanKind.SPAN_KIND_CLIENT) - .setStartTimeUnixNano( - TimeUnit.MILLISECONDS.toNanos( - Instant.ofEpochSecond(1472470996, 199_000_000).toEpochMilli())) - .setEndTimeUnixNano( - TimeUnit.MILLISECONDS.toNanos( - Instant.ofEpochSecond(1472470996, 406_000_000).toEpochMilli())) - .addAllAttributes( - Arrays.asList(KeyValue.newBuilder().setKey("network.local.address").setValue( - AnyValue.newBuilder().setStringValue("127.0.0.1").build()).build(), - KeyValue.newBuilder().setKey("net.sock.peer.name").setValue( - AnyValue.newBuilder().setStringValue("backend").build()).build(), - KeyValue.newBuilder().setKey("net.sock.peer.addr").setValue( - AnyValue.newBuilder().setStringValue("192.168.99.101").build()).build(), - KeyValue.newBuilder().setKey("net.sock.peer.port").setValue( - AnyValue.newBuilder().setIntValue(9000).build()).build(), - KeyValue.newBuilder().setKey("otel.scope.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build(), - KeyValue.newBuilder().setKey("otel.scope.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build(), - KeyValue.newBuilder().setKey("otel.library.name").setValue( - AnyValue.newBuilder().setStringValue("zipkin2.reporter.otel").build()) - .build(), - KeyValue.newBuilder().setKey("otel.library.version").setValue( - AnyValue.newBuilder().setStringValue("0.0.1").build()).build(), - KeyValue.newBuilder().setKey("clnt/finagle.version").setValue( - AnyValue.newBuilder().setStringValue("6.45.0").build()).build(), - KeyValue.newBuilder().setKey("http.path").setValue( - AnyValue.newBuilder().setStringValue("/api").build()).build()) - ) - .addAllEvents(Arrays.asList( - Event.newBuilder().setTimeUnixNano( - TimeUnit.MILLISECONDS.toNanos( - Instant.ofEpochSecond(1472470996, 238_000_000).toEpochMilli())) - .setName("foo").build(), - Event.newBuilder().setTimeUnixNano(TimeUnit.MILLISECONDS.toNanos( - Instant.ofEpochSecond(1472470996, 403_000_000).toEpochMilli())) - .setName("bar").build())) - .setStatus(Status.newBuilder().setCode(StatusCode.STATUS_CODE_OK).build()) - .addLinks(Link.newBuilder() - .setSpanId(ByteString.fromHex("6c5295666d50f69c")) - .setTraceId(ByteString.fromHex("8291c278b62e8f6a216a2aea45d08fc8")) - .addAttributes(KeyValue.newBuilder().setKey("foo").setValue( - AnyValue.newBuilder().setStringValue("bar").build()).build()) - .build()) - .build()); - } - - @Test - void translate_missingName() { - MutableSpan braveSpan = - new MutableSpan(TraceContext.newBuilder().traceId(3).spanId(2).build(), null); - TracesData translated = spanTranslator.translate(braveSpan); - - assertThat(firstSpan(translated).getName()).isEqualTo("unknown"); - } - - @Test - void translate_emptyName() { - MutableSpan braveSpan = - new MutableSpan(TraceContext.newBuilder().traceId(3).spanId(2).build(), null); - braveSpan.name(""); - TracesData translated = spanTranslator.translate(braveSpan); - - assertThat(firstSpan(translated).getName()).isEqualTo("unknown"); - } - - private static Span firstSpan(TracesData translated) { - return translated.getResourceSpans(0).getScopeSpans(0).getSpans(0); - } -} diff --git a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/TestObjects.java b/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/TestObjects.java deleted file mode 100644 index 7e9e356..0000000 --- a/encoder-otel-brave/src/test/java/zipkin2/reporter/otel/brave/TestObjects.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2024 The OpenZipkin Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package zipkin2.reporter.otel.brave; - -import brave.handler.MutableSpan; - -public class TestObjects { - static MutableSpan clientSpan() { - MutableSpan braveSpan = new MutableSpan(); - braveSpan.traceId("7180c278b62e8f6a216a2aea45d08fc9"); - braveSpan.parentId("6b221d5bc9e6496c"); - braveSpan.id("5b4185666d50f68b"); - braveSpan.name("get"); - braveSpan.kind(brave.Span.Kind.CLIENT); - braveSpan.localServiceName("frontend"); - braveSpan.localIp("127.0.0.1"); - braveSpan.remoteServiceName("backend"); - braveSpan.remoteIpAndPort("192.168.99.101", 9000); - braveSpan.startTimestamp(1472470996199000L); - braveSpan.finishTimestamp(1472470996199000L + 207000L); - braveSpan.annotate(1472470996238000L, "foo"); - braveSpan.annotate(1472470996403000L, "bar"); - braveSpan.tag("clnt/finagle.version", "6.45.0"); - braveSpan.tag("http.path", "/api"); - braveSpan.tag("links[0].traceId", "8291c278b62e8f6a216a2aea45d08fc8"); - braveSpan.tag("links[0].spanId", "6c5295666d50f69c"); - braveSpan.tag("links[0].tags[foo]", "bar"); - return braveSpan; - } -} diff --git a/pom.xml b/pom.xml index c5a4ef4..fdda283 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,6 @@ module translation-otel - encoder-otel-brave encoder-otel-zipkin collector-http From e5bc2a47c958639a2d72041db35fcc6a58d8c4de Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 27 Jun 2024 09:57:23 +0200 Subject: [PATCH 24/30] Version bumps --- .github/workflows/create_release.yml | 30 +++++----- .github/workflows/deploy.yml | 53 ++++++++++-------- .github/workflows/docker_push.yml | 27 +++++---- .github/workflows/test.yml | 66 ++++++++-------------- docker/Dockerfile | 4 +- pom.xml | 82 ++++++++++++++++++++-------- 6 files changed, 145 insertions(+), 117 deletions(-) diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index 303f885..153991a 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -1,13 +1,12 @@ -# yamllint --format github .github/workflows/create_release.yml --- name: create_release -# We create a release version on a trigger tag, regardless of if the commit is documentation-only. -# -# See https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet -on: +# We create a release version on a trigger tag, regardless of if the commit is +# documentation-only. +on: # yamllint disable-line rule:truthy push: - tags: 'release-[0-9]+.[0-9]+.[0-9]+**' # Ex. release-1.2.3 + tags: # e.g. release-1.2.3 + - 'release-[0-9]+.[0-9]+.[0-9]+**' jobs: create_release: @@ -16,30 +15,29 @@ jobs: - name: Checkout Repository uses: actions/checkout@v4 with: - # Prevent use of implicit GitHub Actions read-only token GITHUB_TOKEN. We don't deploy on - # the tag MAJOR.MINOR.PATCH event, but we still need to deploy the maven-release-plugin main commit. + # Prevent use of implicit GitHub Actions read-only GITHUB_TOKEN + # because maven-release-plugin pushes commits to master. token: ${{ secrets.GH_TOKEN }} - fetch-depth: 1 # only need the HEAD commit as license check isn't run - name: Setup java uses: actions/setup-java@v4 with: distribution: 'zulu' # zulu as it supports a wide version range - java-version: '11' # earliest LTS and last that can compile the 1.6 release profile. + java-version: '17' # earliest LTS supported by Spring Boot 3 - name: Cache local Maven repository uses: actions/cache@v3 with: path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-maven- + key: ${{ runner.os }}-jdk-17-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-jdk-17-maven- - name: Create Release env: # GH_USER= GH_USER: ${{ secrets.GH_USER }} # GH_TOKEN= - # - makes release commits and tags - # - needs repo:status, public_repo - # - referenced in .settings.xml + # * makes release commits and tags + # * needs repo:status, public_repo + # * referenced in .settings.xml GH_TOKEN: ${{ secrets.GH_TOKEN }} run: | # GITHUB_REF will be refs/tags/release-MAJOR.MINOR.PATCH build-bin/git/login_git && - build-bin/maven/maven_release $(echo ${GITHUB_REF} | cut -d/ -f 3) + build-bin/maven/maven_release $(echo ${GITHUB_REF} | cut -d/ -f 3) \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c72e997..63e11b7 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -4,13 +4,14 @@ name: deploy # We deploy on main and release versions, regardless of if the commit is # documentation-only or not. -# -# See https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet -on: +on: # yamllint disable-line rule:truthy push: - # Don't deploy tags as they conflict with [maven-release-plugin] prepare release MAJOR.MINOR.PATCH - tags: '' - branches: main + branches: + - main + # Don't deploy tags because the same commit for MAJOR.MINOR.PATCH is also + # on master: Redundant deployment of a release version will fail uploading. + tags-ignore: + - '*' jobs: deploy: @@ -19,21 +20,20 @@ jobs: - name: Checkout Repository uses: actions/checkout@v4 with: - # Prevent use of implicit GitHub Actions read-only token GITHUB_TOKEN. - # We push Javadocs to the gh-pages branch on commit. + # Prevent use of implicit GitHub Actions read-only GITHUB_TOKEN + # because javadoc_to_gh_pages pushes commits to the gh-pages branch. token: ${{ secrets.GH_TOKEN }} - fetch-depth: 0 # allow build-bin/idl_to_gh_pages to get the full history - name: Setup java uses: actions/setup-java@v4 with: distribution: 'zulu' # zulu as it supports a wide version range - java-version: '11' # earliest LTS and last that can compile the 1.6 release profile. + java-version: '17' # earliest LTS supported by Spring Boot 3 - name: Cache local Maven repository uses: actions/cache@v3 with: path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-maven- + key: ${{ runner.os }}-jdk-17-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-jdk-17-maven- # Don't attempt to cache Docker. Sensitive information can be stolen # via forks, and login session ends up in ~/.docker. This is ok because # we publish DOCKER_PARENT_IMAGE to ghcr.io, hence local to the runner. @@ -42,23 +42,30 @@ jobs: # GH_USER= GH_USER: ${{ secrets.GH_USER }} # GH_TOKEN= - # - pushes Docker images to ghcr.io - # - create via https://github.com/settings/tokens - # - needs repo:status, public_repo, write:packages, delete:packages + # * pushes gh-pages during build-bin/javadoc_to_gh_pages + # * pushes Docker images to ghcr.io + # * create via https://github.com/settings/tokens + # * needs repo:status, public_repo, write:packages, delete:packages GH_TOKEN: ${{ secrets.GH_TOKEN }} GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }} # GPG_PASSPHRASE= - # - referenced in .settings.xml + # * referenced in .settings.xml GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} # SONATYPE_USER= - # - deploys snapshots and releases to Sonatype - # - needs access to io.zipkin via https://issues.sonatype.org/browse/OSSRH-16669 - # - generate via https://oss.sonatype.org/#profile;User%20Token - # - referenced in .settings.xml + # * deploys snapshots and releases to Sonatype + # * needs access to io.zipkin via OSSRH-16669 + # * generate via https://oss.sonatype.org/#profile;User%20Token + # * referenced in .settings.xml SONATYPE_USER: ${{ secrets.SONATYPE_USER }} # SONATYPE_PASSWORD= - # - referenced in .settings.xml + # * referenced in .settings.xml SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} - run: | # GITHUB_REF will be refs/heads/main or refs/tags/MAJOR.MINOR.PATCH + # DOCKERHUB_USER= + # * only push repos in openzipkin org to Docker Hub on release + DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }} + # DOCKERHUB_TOKEN= + # * Access Token from here https://hub.docker.com/settings/security + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} + run: | # GITHUB_REF = refs/heads/main or refs/tags/MAJOR.MINOR.PATCH build-bin/configure_deploy && - build-bin/deploy $(echo ${GITHUB_REF} | cut -d/ -f 3) + build-bin/deploy $(echo ${GITHUB_REF} | cut -d/ -f 3) \ No newline at end of file diff --git a/.github/workflows/docker_push.yml b/.github/workflows/docker_push.yml index 3cd27e4..736d85f 100644 --- a/.github/workflows/docker_push.yml +++ b/.github/workflows/docker_push.yml @@ -1,13 +1,12 @@ -# yamllint --format github .github/workflows/docker_push.yml --- name: docker_push -# We re-push docker images on a trigger tag, regardless of if the commit is documentation-only. -# -# See https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet -on: +# We re-push docker on a trigger tag, regardless of if the commit is +# documentation-only. +on: # yamllint disable-line rule:truthy push: - tags: 'docker-[0-9]+.[0-9]+.[0-9]+**' # Ex. docker-1.2.3 + tags: # e.g. docker-1.2.3 + - 'docker-[0-9]+.[0-9]+.[0-9]+**' jobs: docker_push: @@ -15,13 +14,11 @@ jobs: steps: - name: Checkout Repository uses: actions/checkout@v4 - with: - fetch-depth: 1 # only needed to get the sha label # Don't attempt to cache Docker. Sensitive information can be stolen # via forks, and login session ends up in ~/.docker. This is ok because # we publish DOCKER_PARENT_IMAGE to ghcr.io, hence local to the runner. - name: Docker Push - run: | # GITHUB_REF will be refs/tags/docker-MAJOR.MINOR.PATCH + run: | # GITHUB_REF = refs/tags/docker-MAJOR.MINOR.PATCH build-bin/git/login_git && build-bin/docker/configure_docker_push && build-bin/docker_push $(echo ${GITHUB_REF} | cut -d/ -f 3) @@ -29,7 +26,13 @@ jobs: # GH_USER= GH_USER: ${{ secrets.GH_USER }} # GH_TOKEN= - # - pushes Docker images to ghcr.io - # - create via https://github.com/settings/tokens - # - needs repo:status, public_repo, write:packages, delete:packages + # * pushes Docker images to ghcr.io + # * create via https://github.com/settings/tokens + # * needs repo:status, public_repo, write:packages, delete:packages GH_TOKEN: ${{ secrets.GH_TOKEN }} + # DOCKERHUB_USER= + # * only push repos in openzipkin org to Docker Hub on release + DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }} + # DOCKERHUB_TOKEN= + # * Access Token from here https://hub.docker.com/settings/security + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1fd4247..d787d58 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,59 +1,40 @@ -# yamllint --format github .github/workflows/test.yml --- name: test # We don't test documentation-only commits. -on: - # We run tests on non-tagged pushes to main that aren't a commit made by the release plugin - push: - tags: '' - branches: main - paths-ignore: '**/*.md' - # We also run tests on pull requests targeted at the main branch. - pull_request: - branches: main - paths-ignore: '**/*.md' +on: # yamllint disable-line rule:truthy + push: # non-tagged pushes to main + branches: + - main + tags-ignore: + - '*' + paths-ignore: + - '**/*.md' + - './build-bin/*lint' + - ./build-bin/mlc_config.json + pull_request: # pull requests targeted at the main branch. + branches: + - main + paths-ignore: + - '**/*.md' + - './build-bin/*lint' + - ./build-bin/mlc_config.json jobs: - test-javadoc: - name: Test JavaDoc Builds - runs-on: ubuntu-22.04 # newest available distribution, aka jellyfish - if: "!contains(github.event.head_commit.message, 'maven-release-plugin')" - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 # full git history for license check - - name: Setup java - uses: actions/setup-java@v4 - with: - distribution: 'zulu' # zulu as it supports a wide version range - java-version: '11' # earliest LTS and last that can compile the 1.6 release profile. - - name: Cache local Maven repository - uses: actions/cache@v3 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-jdk-11-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-jdk-11-maven- - - name: Build JavaDoc - run: ./mvnw clean javadoc:aggregate -Prelease - test: name: test (JDK ${{ matrix.java_version }}) - runs-on: ubuntu-22.04 # newest available distribution, aka jellyfish + runs-on: ubuntu-22.04 # newest available distribution, aka jellyfish if: "!contains(github.event.head_commit.message, 'maven-release-plugin')" strategy: - fail-fast: false # don't fail fast as sometimes failures are operating system specific - matrix: # use latest available versions and be consistent on all workflows! + fail-fast: false # don't fail fast as some failures are LTS specific + matrix: # match with maven-enforcer-plugin rules in pom.xml include: - - java_version: 11 # Last that can compile zipkin core to 1.6 for zipkin-reporter - maven_args: -Prelease -Dgpg.skip -Dmaven.javadoc.skip=true + - java_version: 17 # earliest LTS supported by Spring Boot 3 + maven_args: -Prelease -Dgpg.skip - java_version: 21 # Most recent LTS steps: - name: Checkout Repository uses: actions/checkout@v4 - with: - fetch-depth: 0 # full git history for license check - name: Setup java uses: actions/setup-java@v4 with: @@ -63,10 +44,11 @@ jobs: uses: actions/cache@v3 with: path: ~/.m2/repository + # yamllint disable-line rule:line-length key: ${{ runner.os }}-jdk-${{ matrix.java_version }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-jdk-${{ matrix.java_version }}-maven- # Don't attempt to cache Docker. Sensitive information can be stolen # via forks, and login session ends up in ~/.docker. This is ok because # we publish DOCKER_PARENT_IMAGE to ghcr.io, hence local to the runner. - name: Test - run: build-bin/configure_test && build-bin/test + run: build-bin/configure_test && build-bin/test ${{ matrix.maven_args }} \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile index 04a8f93..296d6ff 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -13,7 +13,7 @@ # # zipkin version should match zipkin.version in /pom.xml -ARG zipkin_version=2.26.0 +ARG zipkin_version=3.4.0 # java_version is used during the installation process to build or download the module jar. # @@ -62,4 +62,4 @@ COPY --from=install --chown=${USER} /install/* /zipkin/${module} ENV QUERY_ENABLED false # * Active profile typically corresponds $1 in module/src/main/resources/zipkin-server-(.*).yml -ENV MODULE_OPTS="-Dloader.path=${module} -Dspring.profiles.active=${module}" +ENV MODULE_OPTS="-Dloader.path=${module} -Dspring.profiles.active=${module}" \ No newline at end of file diff --git a/pom.xml b/pom.xml index 1f51392..3038090 100644 --- a/pom.xml +++ b/pom.xml @@ -15,8 +15,8 @@ --> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 io.zipkin.contrib.otel @@ -59,29 +59,45 @@ io.zipkin.zipkin2 - 2.26.0 - 2.17.1 - 2.7.18 + 3.4.0 + 3.4.0 + 3.3.0 com.linecorp.armeria - 1.26.4 - - 3.24.2 - 4.2.0 - 5.10.1 - 5.8.0 + 1.28.4 + + + + + io.zipkin.brave + 6.0.3 + + 1.39.0 + + 1.3.1-alpha + 1.25.0-alpha + + 3.25.1 + + 3.25.3 + 4.2.1 + 5.10.2 + 5.12.0 + 1.19.8 + 4.12.0 - 2.24.0 + 2.27.1 ${skipTests} 1.23 1.2.8 - 4.3 - 3.11.0 + 4.5 + 5.1.9 + 3.13.0 3.6.1 3.1.1 @@ -89,12 +105,12 @@ 3.4.0 3.1.1 - 3.6.2 + 3.6.3 3.3.0 3.0.1 - 3.5.1 - 3.3.0 - 3.2.2 + 3.5.2 + 3.3.1 + 3.2.5 1.6.13 @@ -133,6 +149,28 @@ + + + + io.opentelemetry + opentelemetry-bom + ${opentelemetry.version} + pom + import + + + io.opentelemetry.proto + opentelemetry-proto + ${opentelemetry-proto.version} + + + + io.opentelemetry.semconv + opentelemetry-semconv + ${opentelemetry-semconv.version} + + + org.junit.jupiter @@ -150,7 +188,7 @@ ch.qos.logback logback-classic - 1.2.13 + 1.5.6 test @@ -359,7 +397,7 @@ - [11,12),[17,18),[21,22) + [17,18),[21,22) @@ -373,7 +411,7 @@ ${license-maven-plugin.version} ${license.skip} -
${main.basedir}/src/etc/header.txt
+
${maven.multiModuleProjectDirectory}/src/etc/header.txt
SLASHSTAR_STYLE @@ -614,4 +652,4 @@
- + \ No newline at end of file From bcbfc367b5d8db3da94ca30303b3224be41c3a9b Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 27 Jun 2024 10:39:25 +0200 Subject: [PATCH 25/30] Uses decorators instead of manually decoding payload --- .../otel/http/OpenTelemetryHttpCollector.java | 12 +++++------- .../otel/http/ITOpenTelemetryHttpCollector.java | 4 +++- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java b/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java index bb8b182..59f0a8a 100644 --- a/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java +++ b/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java @@ -19,10 +19,13 @@ import com.linecorp.armeria.common.HttpResponse; import com.linecorp.armeria.common.HttpStatus; import com.linecorp.armeria.common.ResponseHeaders; +import com.linecorp.armeria.common.encoding.StreamDecoderFactory; import com.linecorp.armeria.server.AbstractHttpService; +import com.linecorp.armeria.server.HttpServiceWithRoutes; import com.linecorp.armeria.server.ServerBuilder; import com.linecorp.armeria.server.ServerConfigurator; import com.linecorp.armeria.server.ServiceRequestContext; +import com.linecorp.armeria.server.encoding.DecodingService; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; @@ -108,6 +111,7 @@ public String toString() { */ @Override public void reconfigure(ServerBuilder sb) { + sb.decorator(DecodingService.newDecorator(StreamDecoderFactory.gzip())); sb.service("/v1/traces", new HttpService(this)); } @@ -139,13 +143,7 @@ protected HttpResponse doPost(ServiceRequestContext ctx, HttpRequest req) final ByteBuffer nioBuffer = byteBuf.nioBuffer(); try (ReadBuffer readBuffer = ReadBuffer.wrapUnsafe(nioBuffer)) { try { - InputStream inputStream = readBuffer; - if ("gzip".equalsIgnoreCase(req.headers().get("content-encoding"))) { - inputStream = new GZIPInputStream(content.toInputStream()); - } else if ("base64".equalsIgnoreCase(req.headers().get("content-encoding"))) { - inputStream = Base64.getDecoder().wrap(content.toInputStream()); - } - ExportTraceServiceRequest request = ExportTraceServiceRequest.parseFrom(inputStream); + ExportTraceServiceRequest request = ExportTraceServiceRequest.parseFrom(readBuffer); List spans = SpanTranslator.translate(request); collector.collector.accept(spans, result); } catch (IOException e) { diff --git a/collector-http/src/test/java/zipkin2/collector/otel/http/ITOpenTelemetryHttpCollector.java b/collector-http/src/test/java/zipkin2/collector/otel/http/ITOpenTelemetryHttpCollector.java index 109e245..4f4f2b9 100644 --- a/collector-http/src/test/java/zipkin2/collector/otel/http/ITOpenTelemetryHttpCollector.java +++ b/collector-http/src/test/java/zipkin2/collector/otel/http/ITOpenTelemetryHttpCollector.java @@ -50,7 +50,9 @@ class ITOpenTelemetryHttpCollector { InMemoryCollectorMetrics metrics; CollectorComponent collector; - SpanExporter spanExporter = OtlpHttpSpanExporter.builder().build(); + SpanExporter spanExporter = OtlpHttpSpanExporter.builder() + .setCompression("gzip") + .build(); SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder() .setSampler(alwaysOn()) From 265e37378e4c80484a9abc843ba407eb096fd5d1 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 27 Jun 2024 11:14:35 +0200 Subject: [PATCH 26/30] Optimize imports --- .../collector/otel/http/OpenTelemetryHttpCollector.java | 4 ---- .../collector/otel/http/OpenTelemetryHttpCollectorTest.java | 4 ++-- .../otel/ZipkinOpenTelemetryHttpCollectorModuleTest.java | 6 +++--- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java b/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java index 59f0a8a..e477ce6 100644 --- a/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java +++ b/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java @@ -21,7 +21,6 @@ import com.linecorp.armeria.common.ResponseHeaders; import com.linecorp.armeria.common.encoding.StreamDecoderFactory; import com.linecorp.armeria.server.AbstractHttpService; -import com.linecorp.armeria.server.HttpServiceWithRoutes; import com.linecorp.armeria.server.ServerBuilder; import com.linecorp.armeria.server.ServerConfigurator; import com.linecorp.armeria.server.ServiceRequestContext; @@ -30,12 +29,9 @@ import io.netty.buffer.ByteBufAllocator; import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; import java.io.IOException; -import java.io.InputStream; import java.nio.ByteBuffer; -import java.util.Base64; import java.util.List; import java.util.concurrent.CompletableFuture; -import java.util.zip.GZIPInputStream; import zipkin2.Callback; import zipkin2.Span; import zipkin2.collector.Collector; diff --git a/collector-http/src/test/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollectorTest.java b/collector-http/src/test/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollectorTest.java index f943a7a..323f0e4 100644 --- a/collector-http/src/test/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollectorTest.java +++ b/collector-http/src/test/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollectorTest.java @@ -13,11 +13,11 @@ */ package zipkin2.collector.otel.http; +import static org.assertj.core.api.Assertions.assertThat; + import org.junit.jupiter.api.Test; import zipkin2.storage.InMemoryStorage; -import static org.assertj.core.api.Assertions.assertThat; - class OpenTelemetryHttpCollectorTest { OpenTelemetryHttpCollector collector = OpenTelemetryHttpCollector.newBuilder() .storage(InMemoryStorage.newBuilder().build()) diff --git a/module/src/test/java/zipkin/module/otel/ZipkinOpenTelemetryHttpCollectorModuleTest.java b/module/src/test/java/zipkin/module/otel/ZipkinOpenTelemetryHttpCollectorModuleTest.java index 2155da9..2edd961 100644 --- a/module/src/test/java/zipkin/module/otel/ZipkinOpenTelemetryHttpCollectorModuleTest.java +++ b/module/src/test/java/zipkin/module/otel/ZipkinOpenTelemetryHttpCollectorModuleTest.java @@ -13,6 +13,9 @@ */ package zipkin.module.otel; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; + import com.linecorp.armeria.spring.ArmeriaServerConfigurator; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -27,9 +30,6 @@ import zipkin2.storage.InMemoryStorage; import zipkin2.storage.StorageComponent; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; - class ZipkinOpenTelemetryHttpCollectorModuleTest { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); From c706abbf1db3e755d67dd7935dff5303a372d9fd Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 27 Jun 2024 11:18:35 +0200 Subject: [PATCH 27/30] Simplified Collector logic --- .../otel/http/OpenTelemetryHttpCollector.java | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java b/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java index e477ce6..798153d 100644 --- a/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java +++ b/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java @@ -13,6 +13,7 @@ */ package zipkin2.collector.otel.http; +import com.google.protobuf.CodedInputStream; import com.linecorp.armeria.common.AggregationOptions; import com.linecorp.armeria.common.HttpData; import com.linecorp.armeria.common.HttpRequest; @@ -25,11 +26,9 @@ import com.linecorp.armeria.server.ServerConfigurator; import com.linecorp.armeria.server.ServiceRequestContext; import com.linecorp.armeria.server.encoding.DecodingService; -import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; import java.io.IOException; -import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.CompletableFuture; import zipkin2.Callback; @@ -38,7 +37,6 @@ import zipkin2.collector.CollectorComponent; import zipkin2.collector.CollectorMetrics; import zipkin2.collector.CollectorSampler; -import zipkin2.internal.ReadBuffer; import zipkin2.storage.StorageComponent; import zipkin2.translation.zipkin.SpanTranslator; @@ -135,16 +133,12 @@ protected HttpResponse doPost(ServiceRequestContext ctx, HttpRequest req) return null; } - ByteBuf byteBuf = content.byteBuf(); - final ByteBuffer nioBuffer = byteBuf.nioBuffer(); - try (ReadBuffer readBuffer = ReadBuffer.wrapUnsafe(nioBuffer)) { - try { - ExportTraceServiceRequest request = ExportTraceServiceRequest.parseFrom(readBuffer); - List spans = SpanTranslator.translate(request); - collector.collector.accept(spans, result); - } catch (IOException e) { - throw new RuntimeException(e); - } + try { + ExportTraceServiceRequest request = ExportTraceServiceRequest.parseFrom(content.toInputStream()); + List spans = SpanTranslator.translate(request); + collector.collector.accept(spans, result); + } catch (IOException e) { + throw new RuntimeException(e); } return null; } From 579ef19630aa61699b2c860ad9e96545427a90a9 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 27 Jun 2024 12:03:29 +0200 Subject: [PATCH 28/30] Improved byte decoding --- .../zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java b/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java index 798153d..84cb809 100644 --- a/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java +++ b/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java @@ -134,7 +134,7 @@ protected HttpResponse doPost(ServiceRequestContext ctx, HttpRequest req) } try { - ExportTraceServiceRequest request = ExportTraceServiceRequest.parseFrom(content.toInputStream()); + ExportTraceServiceRequest request = ExportTraceServiceRequest.parseFrom(CodedInputStream.newInstance(content.byteBuf().nioBuffer())); List spans = SpanTranslator.translate(request); collector.collector.accept(spans, result); } catch (IOException e) { From d87046d9bd39025606bcfb3323796eea7b1f837c Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 28 Jun 2024 10:54:53 +0200 Subject: [PATCH 29/30] Changes following the review --- .../collector/otel/http/OpenTelemetryHttpCollector.java | 3 ++- module/pom.xml | 2 +- .../java/zipkin2/translation/zipkin/AttributesExtractor.java | 2 +- .../src/main/java/zipkin2/translation/zipkin/LinkUtils.java | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java b/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java index 84cb809..905e4ad 100644 --- a/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java +++ b/collector-http/src/main/java/zipkin2/collector/otel/http/OpenTelemetryHttpCollector.java @@ -29,6 +29,7 @@ import io.netty.buffer.ByteBufAllocator; import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; import java.io.IOException; +import java.io.UncheckedIOException; import java.util.List; import java.util.concurrent.CompletableFuture; import zipkin2.Callback; @@ -138,7 +139,7 @@ protected HttpResponse doPost(ServiceRequestContext ctx, HttpRequest req) List spans = SpanTranslator.translate(request); collector.collector.accept(spans, result); } catch (IOException e) { - throw new RuntimeException(e); + throw new UncheckedIOException(e); } return null; } diff --git a/module/pom.xml b/module/pom.xml index c2e7afc..37627ff 100644 --- a/module/pom.xml +++ b/module/pom.xml @@ -132,7 +132,7 @@ com.fasterxml.jackson.core jackson-annotations - 2.17.0 + 2.17.1 test diff --git a/translation-otel/src/main/java/zipkin2/translation/zipkin/AttributesExtractor.java b/translation-otel/src/main/java/zipkin2/translation/zipkin/AttributesExtractor.java index 9947030..cfb2f37 100644 --- a/translation-otel/src/main/java/zipkin2/translation/zipkin/AttributesExtractor.java +++ b/translation-otel/src/main/java/zipkin2/translation/zipkin/AttributesExtractor.java @@ -19,7 +19,7 @@ import java.util.Map; /** - * LabelExtractor extracts the set of OTel Span labels equivalent to the annotations in a + * AttributesExtractor extracts the set of OTel Span labels equivalent to the annotations in a * given Zipkin Span. * *

Zipkin annotations are converted to OTel Span labels by using annotation.value as the diff --git a/translation-otel/src/main/java/zipkin2/translation/zipkin/LinkUtils.java b/translation-otel/src/main/java/zipkin2/translation/zipkin/LinkUtils.java index df3a950..8d7bfec 100644 --- a/translation-otel/src/main/java/zipkin2/translation/zipkin/LinkUtils.java +++ b/translation-otel/src/main/java/zipkin2/translation/zipkin/LinkUtils.java @@ -25,7 +25,7 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -class LinkUtils { +final class LinkUtils { private static final String LINKS_PREFIX = "links["; From cb1387e55f0f004145f86819cf3339c61f583b4a Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 28 Jun 2024 11:12:53 +0200 Subject: [PATCH 30/30] Polish --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9508fb2..63e11b7 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -2,7 +2,7 @@ --- name: deploy -# We deploy on master and release versions, regardless of if the commit is +# We deploy on main and release versions, regardless of if the commit is # documentation-only or not. on: # yamllint disable-line rule:truthy push: