Improve single responsibility of ZipkinSpanExporter. (#4675)

* improve single responsibility of ZipkinSpanExporter by factoring out OtelToZipkinSpanTransformer.

* add javadoc

* make utility constructor private

* make new class final and jApiCmp

* add javadocs

* enhance javadoc

* make package private

* hide logger

* spotless

* jApiCmp

* create transformer with supplier

* jApiCmp

* rename all Function vars to "transformer"

* remove dead code

* remove Function interface from OtelToZipkinSpanTransformer

* create factory method and rename test util

* rename attributesMap -> attributes

* rebase after metrics merge

* add javadoc

* remove static shared ip address

* Hey look it's a period.

* allow the builder to control the ip address supplier, not the entire transformer.

* hide instance behind getter

* remove unused

* rebase

* hide OtelToZipkinSpanTransformer from public usage

* jApiCmp

* add default to javadocs
This commit is contained in:
jason plumb 2022-08-17 16:11:20 -07:00 committed by GitHub
parent 6eea0389af
commit 734b13e9c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 718 additions and 552 deletions

View File

@ -1,2 +1,4 @@
Comparing source compatibility of against
No changes.
*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.exporter.zipkin.ZipkinSpanExporterBuilder (not serializable)
=== CLASS FILE FORMAT VERSION: 52.0 <- 52.0
+++ NEW METHOD: PUBLIC(+) io.opentelemetry.exporter.zipkin.ZipkinSpanExporterBuilder setLocalIpAddressSupplier(java.util.function.Supplier)

View File

@ -0,0 +1,58 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.exporter.zipkin;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Enumeration;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
class LocalInetAddressSupplier implements Supplier<InetAddress> {
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<NetworkInterface> nics = NetworkInterface.getNetworkInterfaces();
while (nics.hasMoreElements()) {
NetworkInterface nic = nics.nextElement();
Enumeration<InetAddress> 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;
}
}

View File

@ -0,0 +1,209 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.exporter.zipkin;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.AttributeType;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.data.EventData;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.data.StatusData;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import java.net.InetAddress;
import java.util.List;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import zipkin2.Endpoint;
import zipkin2.Span;
/**
* This class is responsible for transforming an OpenTelemetry SpanData instance into an instance of
* a Zipkin Span. It is based, in part, on code from
* https://github.com/census-instrumentation/opencensus-java/tree/c960b19889de5e4a7b25f90919d28b066590d4f0/exporters/trace/zipkin
*/
final class OtelToZipkinSpanTransformer {
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 final AttributeKey<String> STATUS_ERROR = stringKey("error");
private final Supplier<InetAddress> ipAddressSupplier;
/**
* Creates an instance of an OtelToZipkinSpanTransformer with the given Supplier that can produce
* an InetAddress, which may be null. This value from this Supplier will be used when creating the
* local zipkin Endpoint for each Span. The default implementation uses
* LocalInetAddressSupplier.getInstance().
*
* @param ipAddressSupplier - A Supplier of an InetAddress.
*/
static OtelToZipkinSpanTransformer create(Supplier<InetAddress> ipAddressSupplier) {
return new OtelToZipkinSpanTransformer(ipAddressSupplier);
}
/**
* Creates an instance of an OtelToZipkinSpanTransformer with the given Supplier that can produce
* an InetAddress. Supplier may return null. This value from this Supplier will be used when
* creating the local zipkin Endpoint for each Span.
*
* @param ipAddressSupplier - A Supplier of an InetAddress, which can be null
*/
private OtelToZipkinSpanTransformer(Supplier<InetAddress> ipAddressSupplier) {
this.ipAddressSupplier = ipAddressSupplier;
}
/**
* Creates an instance of a Zipkin Span from an OpenTelemetry SpanData instance.
*
* @param spanData an OpenTelemetry spanData instance
* @return a new Zipkin Span
*/
Span generateSpan(SpanData spanData) {
Endpoint endpoint = getEndpoint(spanData);
long startTimestamp = toEpochMicros(spanData.getStartEpochNanos());
long endTimestamp = toEpochMicros(spanData.getEndEpochNanos());
Span.Builder spanBuilder =
Span.newBuilder()
.traceId(spanData.getTraceId())
.id(spanData.getSpanId())
.kind(toSpanKind(spanData))
.name(spanData.getName())
.timestamp(toEpochMicros(spanData.getStartEpochNanos()))
.duration(Math.max(1, endTimestamp - startTimestamp))
.localEndpoint(endpoint);
if (spanData.getParentSpanContext().isValid()) {
spanBuilder.parentId(spanData.getParentSpanId());
}
Attributes spanAttributes = spanData.getAttributes();
spanAttributes.forEach(
(key, value) -> spanBuilder.putTag(key.getKey(), valueToString(key, value)));
int droppedAttributes = spanData.getTotalAttributeCount() - spanAttributes.size();
if (droppedAttributes > 0) {
spanBuilder.putTag(OTEL_DROPPED_ATTRIBUTES_COUNT, String.valueOf(droppedAttributes));
}
StatusData status = spanData.getStatus();
// include status code & error.
if (status.getStatusCode() != StatusCode.UNSET) {
spanBuilder.putTag(OTEL_STATUS_CODE, status.getStatusCode().toString());
// add the error tag, if it isn't already in the source span.
if (status.getStatusCode() == StatusCode.ERROR && spanAttributes.get(STATUS_ERROR) == null) {
spanBuilder.putTag(STATUS_ERROR.getKey(), nullToEmpty(status.getDescription()));
}
}
InstrumentationScopeInfo instrumentationScopeInfo = spanData.getInstrumentationScopeInfo();
if (!instrumentationScopeInfo.getName().isEmpty()) {
spanBuilder.putTag(KEY_INSTRUMENTATION_SCOPE_NAME, instrumentationScopeInfo.getName());
// Include instrumentation library name for backwards compatibility
spanBuilder.putTag(KEY_INSTRUMENTATION_LIBRARY_NAME, instrumentationScopeInfo.getName());
}
if (instrumentationScopeInfo.getVersion() != null) {
spanBuilder.putTag(KEY_INSTRUMENTATION_SCOPE_VERSION, instrumentationScopeInfo.getVersion());
// Include instrumentation library name for backwards compatibility
spanBuilder.putTag(
KEY_INSTRUMENTATION_LIBRARY_VERSION, instrumentationScopeInfo.getVersion());
}
for (EventData annotation : spanData.getEvents()) {
spanBuilder.addAnnotation(toEpochMicros(annotation.getEpochNanos()), annotation.getName());
}
int droppedEvents = spanData.getTotalRecordedEvents() - spanData.getEvents().size();
if (droppedEvents > 0) {
spanBuilder.putTag(OTEL_DROPPED_EVENTS_COUNT, String.valueOf(droppedEvents));
}
return spanBuilder.build();
}
private static String nullToEmpty(String value) {
return value != null ? value : "";
}
private Endpoint getEndpoint(SpanData spanData) {
Attributes resourceAttributes = spanData.getResource().getAttributes();
Endpoint.Builder endpoint = Endpoint.newBuilder();
endpoint.ip(ipAddressSupplier.get());
// use the service.name from the Resource, if it's been set.
String serviceNameValue = resourceAttributes.get(ResourceAttributes.SERVICE_NAME);
if (serviceNameValue == null) {
serviceNameValue = Resource.getDefault().getAttribute(ResourceAttributes.SERVICE_NAME);
}
// In practice should never be null unless the default Resource spec is changed.
if (serviceNameValue != null) {
endpoint.serviceName(serviceNameValue);
}
return endpoint.build();
}
@Nullable
private static Span.Kind toSpanKind(SpanData spanData) {
switch (spanData.getKind()) {
case SERVER:
return Span.Kind.SERVER;
case CLIENT:
return Span.Kind.CLIENT;
case PRODUCER:
return Span.Kind.PRODUCER;
case CONSUMER:
return Span.Kind.CONSUMER;
case INTERNAL:
return null;
}
return null;
}
private static long toEpochMicros(long epochNanos) {
return NANOSECONDS.toMicros(epochNanos);
}
private static String valueToString(AttributeKey<?> key, Object attributeValue) {
AttributeType type = key.getType();
switch (type) {
case STRING:
case BOOLEAN:
case LONG:
case DOUBLE:
return String.valueOf(attributeValue);
case STRING_ARRAY:
case BOOLEAN_ARRAY:
case LONG_ARRAY:
case DOUBLE_ARRAY:
return commaSeparated((List<?>) attributeValue);
}
throw new IllegalStateException("Unknown attribute type: " + 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();
}
}

View File

@ -5,36 +5,19 @@
package io.opentelemetry.exporter.zipkin;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.AttributeType;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.MeterProvider;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.exporter.internal.ExporterMetrics;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.internal.ThrottlingLogger;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.data.EventData;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.data.StatusData;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import zipkin2.Callback;
import zipkin2.Endpoint;
import zipkin2.Span;
import zipkin2.codec.BytesEncoder;
import zipkin2.codec.Encoding;
@ -51,185 +34,25 @@ public final class ZipkinSpanExporter implements SpanExporter {
public static final String DEFAULT_ENDPOINT = "http://localhost:9411/api/v2/spans";
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 final AttributeKey<String> STATUS_ERROR = stringKey("error");
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";
private final ThrottlingLogger logger = new ThrottlingLogger(baseLogger);
private final BytesEncoder<Span> encoder;
private final Sender sender;
private final ExporterMetrics exporterMetrics;
@Nullable private final InetAddress localAddress;
ZipkinSpanExporter(BytesEncoder<Span> encoder, Sender sender, MeterProvider meterProvider) {
private final OtelToZipkinSpanTransformer transformer;
ZipkinSpanExporter(
BytesEncoder<Span> encoder,
Sender sender,
MeterProvider meterProvider,
OtelToZipkinSpanTransformer transformer) {
this.encoder = encoder;
this.sender = sender;
this.exporterMetrics =
sender.encoding() == Encoding.JSON
? ExporterMetrics.createHttpJson("zipkin", "span", meterProvider)
: ExporterMetrics.createHttpProtobuf("zipkin", "span", meterProvider);
localAddress = produceLocalIp();
}
/** Logic borrowed from brave.internal.Platform.produceLocalEndpoint */
@Nullable
static InetAddress produceLocalIp() {
try {
Enumeration<NetworkInterface> nics = NetworkInterface.getNetworkInterfaces();
while (nics.hasMoreElements()) {
NetworkInterface nic = nics.nextElement();
Enumeration<InetAddress> 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
baseLogger.log(Level.FINE, "error reading nics", e);
}
return null;
}
// VisibleForTesting
Span generateSpan(SpanData spanData) {
Endpoint endpoint = getEndpoint(spanData);
long startTimestamp = toEpochMicros(spanData.getStartEpochNanos());
long endTimestamp = toEpochMicros(spanData.getEndEpochNanos());
Span.Builder spanBuilder =
Span.newBuilder()
.traceId(spanData.getTraceId())
.id(spanData.getSpanId())
.kind(toSpanKind(spanData))
.name(spanData.getName())
.timestamp(toEpochMicros(spanData.getStartEpochNanos()))
.duration(Math.max(1, endTimestamp - startTimestamp))
.localEndpoint(endpoint);
if (spanData.getParentSpanContext().isValid()) {
spanBuilder.parentId(spanData.getParentSpanId());
}
Attributes spanAttributes = spanData.getAttributes();
spanAttributes.forEach(
(key, value) -> spanBuilder.putTag(key.getKey(), valueToString(key, value)));
int droppedAttributes = spanData.getTotalAttributeCount() - spanAttributes.size();
if (droppedAttributes > 0) {
spanBuilder.putTag(OTEL_DROPPED_ATTRIBUTES_COUNT, String.valueOf(droppedAttributes));
}
StatusData status = spanData.getStatus();
// include status code & error
if (status.getStatusCode() != StatusCode.UNSET) {
spanBuilder.putTag(OTEL_STATUS_CODE, status.getStatusCode().toString());
// add the error tag, if it isn't already in the source span
if (status.getStatusCode() == StatusCode.ERROR && spanAttributes.get(STATUS_ERROR) == null) {
spanBuilder.putTag(STATUS_ERROR.getKey(), status.getDescription());
}
}
InstrumentationScopeInfo instrumentationScopeInfo = spanData.getInstrumentationScopeInfo();
if (!instrumentationScopeInfo.getName().isEmpty()) {
spanBuilder.putTag(KEY_INSTRUMENTATION_SCOPE_NAME, instrumentationScopeInfo.getName());
// include instrumentation library name for backwards compatibility
spanBuilder.putTag(KEY_INSTRUMENTATION_LIBRARY_NAME, instrumentationScopeInfo.getName());
}
if (instrumentationScopeInfo.getVersion() != null) {
spanBuilder.putTag(KEY_INSTRUMENTATION_SCOPE_VERSION, instrumentationScopeInfo.getVersion());
// include instrumentation library name for backwards compatibility
spanBuilder.putTag(
KEY_INSTRUMENTATION_LIBRARY_VERSION, instrumentationScopeInfo.getVersion());
}
for (EventData annotation : spanData.getEvents()) {
spanBuilder.addAnnotation(toEpochMicros(annotation.getEpochNanos()), annotation.getName());
}
int droppedEvents = spanData.getTotalRecordedEvents() - spanData.getEvents().size();
if (droppedEvents > 0) {
spanBuilder.putTag(OTEL_DROPPED_EVENTS_COUNT, String.valueOf(droppedEvents));
}
return spanBuilder.build();
}
private Endpoint getEndpoint(SpanData spanData) {
Attributes resourceAttributes = spanData.getResource().getAttributes();
Endpoint.Builder endpoint = Endpoint.newBuilder().ip(localAddress);
// use the service.name from the Resource, if it's been set
String serviceNameValue = resourceAttributes.get(ResourceAttributes.SERVICE_NAME);
if (serviceNameValue == null) {
serviceNameValue = Resource.getDefault().getAttribute(ResourceAttributes.SERVICE_NAME);
}
// in practice should never be null unless the default Resource spec is changed
if (serviceNameValue != null) {
endpoint.serviceName(serviceNameValue);
}
return endpoint.build();
}
@Nullable
private static Span.Kind toSpanKind(SpanData spanData) {
switch (spanData.getKind()) {
case SERVER:
return Span.Kind.SERVER;
case CLIENT:
return Span.Kind.CLIENT;
case PRODUCER:
return Span.Kind.PRODUCER;
case CONSUMER:
return Span.Kind.CONSUMER;
case INTERNAL:
return null;
}
return null;
}
private static long toEpochMicros(long epochNanos) {
return NANOSECONDS.toMicros(epochNanos);
}
private static String valueToString(AttributeKey<?> key, Object attributeValue) {
AttributeType type = key.getType();
switch (type) {
case STRING:
case BOOLEAN:
case LONG:
case DOUBLE:
return String.valueOf(attributeValue);
case STRING_ARRAY:
case BOOLEAN_ARRAY:
case LONG_ARRAY:
case DOUBLE_ARRAY:
return commaSeparated((List<?>) attributeValue);
}
throw new IllegalStateException("Unknown attribute type: " + 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();
this.transformer = transformer;
}
@Override
@ -239,7 +62,8 @@ public final class ZipkinSpanExporter implements SpanExporter {
List<byte[]> encodedSpans = new ArrayList<>(numItems);
for (SpanData spanData : spanDataList) {
encodedSpans.add(encoder.encode(generateSpan(spanData)));
Span zipkinSpan = transformer.generateSpan(spanData);
encodedSpans.add(encoder.encode(zipkinSpan));
}
CompletableResultCode result = new CompletableResultCode();
@ -287,10 +111,4 @@ public final class ZipkinSpanExporter implements SpanExporter {
public static ZipkinSpanExporterBuilder builder() {
return new ZipkinSpanExporterBuilder();
}
// VisibleForTesting
@Nullable
InetAddress getLocalAddressForTest() {
return localAddress;
}
}

View File

@ -9,8 +9,10 @@ import static io.opentelemetry.api.internal.Utils.checkArgument;
import static java.util.Objects.requireNonNull;
import io.opentelemetry.api.metrics.MeterProvider;
import java.net.InetAddress;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import zipkin2.Span;
import zipkin2.codec.BytesEncoder;
@ -21,6 +23,7 @@ import zipkin2.reporter.okhttp3.OkHttpSender;
/** Builder class for {@link ZipkinSpanExporter}. */
public final class ZipkinSpanExporterBuilder {
private BytesEncoder<Span> encoder = SpanBytesEncoder.JSON_V2;
private Supplier<InetAddress> localIpAddressSupplier = LocalInetAddressSupplier.getInstance();
@Nullable private Sender sender;
private String endpoint = ZipkinSpanExporter.DEFAULT_ENDPOINT;
private long readTimeoutMillis = TimeUnit.SECONDS.toMillis(10);
@ -55,6 +58,21 @@ public final class ZipkinSpanExporterBuilder {
return this;
}
/**
* Sets the Supplier of InetAddress. This Supplier will be used by the {@link
* OtelToZipkinSpanTransformer} when creating the Zipkin local endpoint. The default
* implementation uses a Supplier that returns a single unchanging IP address that is captured at
* creation time.
*
* @param supplier - A supplier that returns an InetAddress that may be null.
* @return this
*/
public ZipkinSpanExporterBuilder setLocalIpAddressSupplier(Supplier<InetAddress> supplier) {
requireNonNull(supplier, "encoder");
this.localIpAddressSupplier = supplier;
return this;
}
/**
* Sets the zipkin endpoint. This will use the endpoint to assign a {@link OkHttpSender} instance
* to this builder.
@ -118,6 +136,8 @@ public final class ZipkinSpanExporterBuilder {
sender =
OkHttpSender.newBuilder().endpoint(endpoint).readTimeout((int) readTimeoutMillis).build();
}
return new ZipkinSpanExporter(encoder, sender, meterProvider);
OtelToZipkinSpanTransformer transformer =
OtelToZipkinSpanTransformer.create(localIpAddressSupplier);
return new ZipkinSpanExporter(encoder, sender, meterProvider, transformer);
}
}

View File

@ -0,0 +1,305 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.exporter.zipkin;
import static io.opentelemetry.api.common.AttributeKey.booleanArrayKey;
import static io.opentelemetry.api.common.AttributeKey.booleanKey;
import static io.opentelemetry.api.common.AttributeKey.doubleArrayKey;
import static io.opentelemetry.api.common.AttributeKey.doubleKey;
import static io.opentelemetry.api.common.AttributeKey.longArrayKey;
import static io.opentelemetry.api.common.AttributeKey.longKey;
import static io.opentelemetry.api.common.AttributeKey.stringArrayKey;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static io.opentelemetry.exporter.zipkin.ZipkinTestUtil.spanBuilder;
import static io.opentelemetry.exporter.zipkin.ZipkinTestUtil.zipkinSpan;
import static io.opentelemetry.exporter.zipkin.ZipkinTestUtil.zipkinSpanBuilder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.data.StatusData;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.Collections;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import zipkin2.Endpoint;
import zipkin2.Span;
class OtelToZipkinSpanTransformerTest {
private OtelToZipkinSpanTransformer transformer;
private InetAddress localIp;
@BeforeEach
void setup() {
localIp = mock(InetAddress.class);
transformer = OtelToZipkinSpanTransformer.create(() -> localIp);
}
@Test
void generateSpan_remoteParent() {
SpanData data = spanBuilder().build();
assertThat(transformer.generateSpan(data))
.isEqualTo(
zipkinSpanBuilder(Span.Kind.SERVER, localIp)
.putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK")
.build());
}
@Test
void generateSpan_subMicroDurations() {
SpanData data =
spanBuilder()
.setStartEpochNanos(1505855794_194009601L)
.setEndEpochNanos(1505855794_194009999L)
.build();
Span expected =
zipkinSpanBuilder(Span.Kind.SERVER, localIp)
.putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK")
.duration(1)
.build();
assertThat(transformer.generateSpan(data)).isEqualTo(expected);
}
@Test
void generateSpan_ServerKind() {
SpanData data = spanBuilder().setKind(SpanKind.SERVER).build();
assertThat(transformer.generateSpan(data))
.isEqualTo(
zipkinSpanBuilder(Span.Kind.SERVER, localIp)
.putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK")
.build());
}
@Test
void generateSpan_ClientKind() {
SpanData data = spanBuilder().setKind(SpanKind.CLIENT).build();
assertThat(transformer.generateSpan(data))
.isEqualTo(
zipkinSpanBuilder(Span.Kind.CLIENT, localIp)
.putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK")
.build());
}
@Test
void generateSpan_InternalKind() {
SpanData data = spanBuilder().setKind(SpanKind.INTERNAL).build();
assertThat(transformer.generateSpan(data))
.isEqualTo(
zipkinSpanBuilder(null, localIp)
.putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK")
.build());
}
@Test
void generateSpan_ConsumeKind() {
SpanData data = spanBuilder().setKind(SpanKind.CONSUMER).build();
assertThat(transformer.generateSpan(data))
.isEqualTo(
zipkinSpanBuilder(Span.Kind.CONSUMER, localIp)
.putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK")
.build());
}
@Test
void generateSpan_ProducerKind() {
SpanData data = spanBuilder().setKind(SpanKind.PRODUCER).build();
assertThat(transformer.generateSpan(data))
.isEqualTo(
zipkinSpanBuilder(Span.Kind.PRODUCER, localIp)
.putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK")
.build());
}
@Test
void generateSpan_ResourceServiceNameMapping() {
Resource resource =
Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, "super-zipkin-service"));
SpanData data = spanBuilder().setResource(resource).build();
Endpoint expectedEndpoint =
Endpoint.newBuilder().serviceName("super-zipkin-service").ip(localIp).build();
Span expectedZipkinSpan =
zipkinSpan(Span.Kind.SERVER, localIp).toBuilder()
.localEndpoint(expectedEndpoint)
.putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK")
.build();
assertThat(transformer.generateSpan(data)).isEqualTo(expectedZipkinSpan);
}
@Test
void generateSpan_defaultResourceServiceName() {
SpanData data = spanBuilder().setResource(Resource.empty()).build();
Endpoint expectedEndpoint =
Endpoint.newBuilder()
.serviceName(Resource.getDefault().getAttribute(ResourceAttributes.SERVICE_NAME))
.ip(localIp)
.build();
Span expectedZipkinSpan =
zipkinSpan(Span.Kind.SERVER, localIp).toBuilder()
.localEndpoint(expectedEndpoint)
.putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK")
.build();
assertThat(transformer.generateSpan(data)).isEqualTo(expectedZipkinSpan);
}
@Test
void generateSpan_WithAttributes() {
Attributes attributes =
Attributes.builder()
.put(stringKey("string"), "string value")
.put(booleanKey("boolean"), false)
.put(longKey("long"), 9999L)
.put(doubleKey("double"), 222.333d)
.put(booleanArrayKey("booleanArray"), Arrays.asList(true, false))
.put(stringArrayKey("stringArray"), Collections.singletonList("Hello"))
.put(doubleArrayKey("doubleArray"), Arrays.asList(32.33d, -98.3d))
.put(longArrayKey("longArray"), Arrays.asList(33L, 999L))
.build();
SpanData data =
spanBuilder()
.setAttributes(attributes)
.setTotalAttributeCount(28)
.setTotalRecordedEvents(3)
.setKind(SpanKind.CLIENT)
.build();
assertThat(transformer.generateSpan(data))
.isEqualTo(
zipkinSpan(Span.Kind.CLIENT, localIp).toBuilder()
.putTag("string", "string value")
.putTag("boolean", "false")
.putTag("long", "9999")
.putTag("double", "222.333")
.putTag("booleanArray", "true,false")
.putTag("stringArray", "Hello")
.putTag("doubleArray", "32.33,-98.3")
.putTag("longArray", "33,999")
.putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK")
.putTag(OtelToZipkinSpanTransformer.OTEL_DROPPED_ATTRIBUTES_COUNT, "20")
.putTag(OtelToZipkinSpanTransformer.OTEL_DROPPED_EVENTS_COUNT, "1")
.build());
}
@Test
void generateSpan_WithInstrumentationLibraryInfo() {
SpanData data =
spanBuilder()
.setInstrumentationScopeInfo(
InstrumentationScopeInfo.create("io.opentelemetry.auto", "1.0.0", null))
.setKind(SpanKind.CLIENT)
.build();
assertThat(transformer.generateSpan(data))
.isEqualTo(
zipkinSpan(Span.Kind.CLIENT, localIp).toBuilder()
.putTag("otel.scope.name", "io.opentelemetry.auto")
.putTag("otel.scope.version", "1.0.0")
.putTag("otel.library.name", "io.opentelemetry.auto")
.putTag("otel.library.version", "1.0.0")
.putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK")
.build());
}
@Test
void generateSpan_AlreadyHasHttpStatusInfo() {
Attributes attributes =
Attributes.of(
SemanticAttributes.HTTP_STATUS_CODE, 404L, stringKey("error"), "A user provided error");
SpanData data =
spanBuilder()
.setAttributes(attributes)
.setKind(SpanKind.CLIENT)
.setStatus(StatusData.error())
.setTotalAttributeCount(2)
.build();
assertThat(transformer.generateSpan(data))
.isEqualTo(
zipkinSpan(Span.Kind.CLIENT, localIp).toBuilder()
.clearTags()
.putTag(SemanticAttributes.HTTP_STATUS_CODE.getKey(), "404")
.putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "ERROR")
.putTag("error", "A user provided error")
.build());
}
@Test
void generateSpan_WithRpcTimeoutErrorStatus_WithTimeoutErrorDescription() {
Attributes attributes = Attributes.of(SemanticAttributes.RPC_SERVICE, "my service name");
String errorMessage = "timeout";
SpanData data =
spanBuilder()
.setStatus(StatusData.create(StatusCode.ERROR, errorMessage))
.setAttributes(attributes)
.setTotalAttributeCount(1)
.build();
assertThat(transformer.generateSpan(data))
.isEqualTo(
zipkinSpan(Span.Kind.SERVER, localIp).toBuilder()
.putTag(SemanticAttributes.RPC_SERVICE.getKey(), "my service name")
.putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "ERROR")
.putTag(OtelToZipkinSpanTransformer.STATUS_ERROR.getKey(), errorMessage)
.build());
}
@Test
void generateSpan_WithRpcErrorStatus_WithEmptyErrorDescription() {
Attributes attributes = Attributes.of(SemanticAttributes.RPC_SERVICE, "my service name");
SpanData data =
spanBuilder()
.setStatus(StatusData.create(StatusCode.ERROR, ""))
.setAttributes(attributes)
.setTotalAttributeCount(1)
.build();
assertThat(transformer.generateSpan(data))
.isEqualTo(
zipkinSpan(Span.Kind.SERVER, localIp).toBuilder()
.putTag(SemanticAttributes.RPC_SERVICE.getKey(), "my service name")
.putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "ERROR")
.putTag(OtelToZipkinSpanTransformer.STATUS_ERROR.getKey(), "")
.build());
}
@Test
void generateSpan_WithRpcUnsetStatus() {
Attributes attributes = Attributes.of(SemanticAttributes.RPC_SERVICE, "my service name");
SpanData data =
spanBuilder()
.setStatus(StatusData.create(StatusCode.UNSET, ""))
.setAttributes(attributes)
.setTotalAttributeCount(1)
.build();
assertThat(transformer.generateSpan(data))
.isEqualTo(
zipkinSpan(Span.Kind.SERVER, localIp).toBuilder()
.putTag(SemanticAttributes.RPC_SERVICE.getKey(), "my service name")
.build());
}
}

View File

@ -6,6 +6,7 @@
package io.opentelemetry.exporter.zipkin;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static org.mockito.Mockito.mock;
import com.linecorp.armeria.client.WebClient;
import com.linecorp.armeria.common.AggregatedHttpResponse;
@ -88,6 +89,8 @@ class ZipkinSpanExporterEndToEndHttpTest {
private final SdkMeterProvider sdkMeterProvider =
SdkMeterProvider.builder().registerMetricReader(sdkMeterReader).build();
private static final InetAddress localIp = mock(InetAddress.class);
@AfterEach
void tearDown() {
sdkMeterProvider.close();
@ -99,6 +102,7 @@ class ZipkinSpanExporterEndToEndHttpTest {
ZipkinSpanExporter.builder()
.setEndpoint(zipkinUrl(ENDPOINT_V2_SPANS))
.setMeterProvider(sdkMeterProvider)
.setLocalIpAddressSupplier(() -> localIp)
.build();
exportAndVerify(exporter);
@ -173,6 +177,7 @@ class ZipkinSpanExporterEndToEndHttpTest {
.setSender(OkHttpSender.newBuilder().endpoint(endpoint).encoding(encoding).build())
.setEncoder(encoder)
.setMeterProvider(meterProvider)
.setLocalIpAddressSupplier(() -> localIp)
.build();
}
@ -191,9 +196,7 @@ class ZipkinSpanExporterEndToEndHttpTest {
assertThat(zipkinSpans).isNotNull();
assertThat(zipkinSpans.size()).isEqualTo(1);
InetAddress address = zipkinSpanExporter.getLocalAddressForTest();
assertThat(address).isNotNull();
assertThat(zipkinSpans.get(0)).isEqualTo(buildZipkinSpan(address, traceId));
assertThat(zipkinSpans.get(0)).isEqualTo(buildZipkinSpan(localIp, traceId));
}
private static TestSpanData.Builder buildStandardSpan(String traceId) {
@ -229,7 +232,7 @@ class ZipkinSpanExporterEndToEndHttpTest {
.localEndpoint(Endpoint.newBuilder().serviceName(SERVICE_NAME).ip(localAddress).build())
.addAnnotation(RECEIVED_TIMESTAMP_NANOS / 1000, "RECEIVED")
.addAnnotation(SENT_TIMESTAMP_NANOS / 1000, "SENT")
.putTag(ZipkinSpanExporter.OTEL_STATUS_CODE, "OK")
.putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK")
.build();
}

View File

@ -5,14 +5,8 @@
package io.opentelemetry.exporter.zipkin;
import static io.opentelemetry.api.common.AttributeKey.booleanArrayKey;
import static io.opentelemetry.api.common.AttributeKey.booleanKey;
import static io.opentelemetry.api.common.AttributeKey.doubleArrayKey;
import static io.opentelemetry.api.common.AttributeKey.doubleKey;
import static io.opentelemetry.api.common.AttributeKey.longArrayKey;
import static io.opentelemetry.api.common.AttributeKey.longKey;
import static io.opentelemetry.api.common.AttributeKey.stringArrayKey;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static io.opentelemetry.exporter.zipkin.ZipkinTestUtil.spanBuilder;
import static io.opentelemetry.exporter.zipkin.ZipkinTestUtil.zipkinSpanBuilder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
@ -20,27 +14,13 @@ import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.MeterProvider;
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.api.trace.TraceFlags;
import io.opentelemetry.api.trace.TraceState;
import io.opentelemetry.internal.testing.slf4j.SuppressLogger;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.testing.trace.TestSpanData;
import io.opentelemetry.sdk.trace.data.EventData;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.data.StatusData;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.io.IOException;
import java.util.Arrays;
import java.net.InetAddress;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -48,7 +28,6 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import zipkin2.Call;
import zipkin2.Callback;
import zipkin2.Endpoint;
import zipkin2.Span;
import zipkin2.codec.SpanBytesEncoder;
import zipkin2.reporter.Sender;
@ -59,289 +38,23 @@ class ZipkinSpanExporterTest {
@Mock private Sender mockSender;
@Mock private SpanBytesEncoder mockEncoder;
@Mock private Call<Void> mockZipkinCall;
private static final String TRACE_ID = "d239036e7d5cec116b562147388b35bf";
private static final String SPAN_ID = "9cc1e3049173be09";
private static final String PARENT_SPAN_ID = "8b03ab423da481c5";
private static final Attributes attributes = Attributes.empty();
private static final List<EventData> annotations =
Collections.unmodifiableList(
Arrays.asList(
EventData.create(1505855799_433901068L, "RECEIVED", Attributes.empty()),
EventData.create(1505855799_459486280L, "SENT", Attributes.empty())));
private final ZipkinSpanExporter exporter = ZipkinSpanExporter.builder().build();
@Test
void generateSpan_remoteParent() {
SpanData data = buildStandardSpan().build();
assertThat(exporter.generateSpan(data))
.isEqualTo(
standardZipkinSpanBuilder(Span.Kind.SERVER)
.putTag(ZipkinSpanExporter.OTEL_STATUS_CODE, "OK")
.build());
}
@Test
void generateSpan_subMicroDurations() {
SpanData data =
buildStandardSpan()
.setStartEpochNanos(1505855794_194009601L)
.setEndEpochNanos(1505855794_194009999L)
.build();
Span expected =
standardZipkinSpanBuilder(Span.Kind.SERVER)
.putTag(ZipkinSpanExporter.OTEL_STATUS_CODE, "OK")
.duration(1)
.build();
assertThat(exporter.generateSpan(data)).isEqualTo(expected);
}
@Test
void generateSpan_ServerKind() {
SpanData data = buildStandardSpan().setKind(SpanKind.SERVER).build();
assertThat(exporter.generateSpan(data))
.isEqualTo(
standardZipkinSpanBuilder(Span.Kind.SERVER)
.putTag(ZipkinSpanExporter.OTEL_STATUS_CODE, "OK")
.build());
}
@Test
void generateSpan_ClientKind() {
SpanData data = buildStandardSpan().setKind(SpanKind.CLIENT).build();
assertThat(exporter.generateSpan(data))
.isEqualTo(
standardZipkinSpanBuilder(Span.Kind.CLIENT)
.putTag(ZipkinSpanExporter.OTEL_STATUS_CODE, "OK")
.build());
}
@Test
void generateSpan_InternalKind() {
SpanData data = buildStandardSpan().setKind(SpanKind.INTERNAL).build();
assertThat(exporter.generateSpan(data))
.isEqualTo(
standardZipkinSpanBuilder(null)
.putTag(ZipkinSpanExporter.OTEL_STATUS_CODE, "OK")
.build());
}
@Test
void generateSpan_ConsumeKind() {
SpanData data = buildStandardSpan().setKind(SpanKind.CONSUMER).build();
assertThat(exporter.generateSpan(data))
.isEqualTo(
standardZipkinSpanBuilder(Span.Kind.CONSUMER)
.putTag(ZipkinSpanExporter.OTEL_STATUS_CODE, "OK")
.build());
}
@Test
void generateSpan_ProducerKind() {
SpanData data = buildStandardSpan().setKind(SpanKind.PRODUCER).build();
assertThat(exporter.generateSpan(data))
.isEqualTo(
standardZipkinSpanBuilder(Span.Kind.PRODUCER)
.putTag(ZipkinSpanExporter.OTEL_STATUS_CODE, "OK")
.build());
}
@Test
void generateSpan_ResourceServiceNameMapping() {
Resource resource =
Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, "super-zipkin-service"));
SpanData data = buildStandardSpan().setResource(resource).build();
Endpoint expectedEndpoint =
Endpoint.newBuilder()
.serviceName("super-zipkin-service")
.ip(exporter.getLocalAddressForTest())
.build();
Span expectedZipkinSpan =
buildZipkinSpan(Span.Kind.SERVER).toBuilder()
.localEndpoint(expectedEndpoint)
.putTag(ZipkinSpanExporter.OTEL_STATUS_CODE, "OK")
.build();
assertThat(exporter.generateSpan(data)).isEqualTo(expectedZipkinSpan);
}
@Test
void generateSpan_defaultResourceServiceName() {
SpanData data = buildStandardSpan().setResource(Resource.empty()).build();
Endpoint expectedEndpoint =
Endpoint.newBuilder()
.serviceName(Resource.getDefault().getAttribute(ResourceAttributes.SERVICE_NAME))
.ip(exporter.getLocalAddressForTest())
.build();
Span expectedZipkinSpan =
buildZipkinSpan(Span.Kind.SERVER).toBuilder()
.localEndpoint(expectedEndpoint)
.putTag(ZipkinSpanExporter.OTEL_STATUS_CODE, "OK")
.build();
assertThat(exporter.generateSpan(data)).isEqualTo(expectedZipkinSpan);
}
@Test
void generateSpan_WithAttributes() {
Attributes attributes =
Attributes.builder()
.put(stringKey("string"), "string value")
.put(booleanKey("boolean"), false)
.put(longKey("long"), 9999L)
.put(doubleKey("double"), 222.333d)
.put(booleanArrayKey("booleanArray"), Arrays.asList(true, false))
.put(stringArrayKey("stringArray"), Collections.singletonList("Hello"))
.put(doubleArrayKey("doubleArray"), Arrays.asList(32.33d, -98.3d))
.put(longArrayKey("longArray"), Arrays.asList(33L, 999L))
.build();
SpanData data =
buildStandardSpan()
.setAttributes(attributes)
.setTotalAttributeCount(28)
.setTotalRecordedEvents(3)
.setKind(SpanKind.CLIENT)
.build();
assertThat(exporter.generateSpan(data))
.isEqualTo(
buildZipkinSpan(Span.Kind.CLIENT).toBuilder()
.putTag("string", "string value")
.putTag("boolean", "false")
.putTag("long", "9999")
.putTag("double", "222.333")
.putTag("booleanArray", "true,false")
.putTag("stringArray", "Hello")
.putTag("doubleArray", "32.33,-98.3")
.putTag("longArray", "33,999")
.putTag(ZipkinSpanExporter.OTEL_STATUS_CODE, "OK")
.putTag(ZipkinSpanExporter.OTEL_DROPPED_ATTRIBUTES_COUNT, "20")
.putTag(ZipkinSpanExporter.OTEL_DROPPED_EVENTS_COUNT, "1")
.build());
}
@Test
void generateSpan_WithInstrumentationLibraryInfo() {
SpanData data =
buildStandardSpan()
.setInstrumentationScopeInfo(
InstrumentationScopeInfo.create("io.opentelemetry.auto", "1.0.0", null))
.setKind(SpanKind.CLIENT)
.build();
assertThat(exporter.generateSpan(data))
.isEqualTo(
buildZipkinSpan(Span.Kind.CLIENT).toBuilder()
.putTag("otel.scope.name", "io.opentelemetry.auto")
.putTag("otel.scope.version", "1.0.0")
.putTag("otel.library.name", "io.opentelemetry.auto")
.putTag("otel.library.version", "1.0.0")
.putTag(ZipkinSpanExporter.OTEL_STATUS_CODE, "OK")
.build());
}
@Test
void generateSpan_AlreadyHasHttpStatusInfo() {
Attributes attributeMap =
Attributes.of(
SemanticAttributes.HTTP_STATUS_CODE, 404L, stringKey("error"), "A user provided error");
SpanData data =
buildStandardSpan()
.setAttributes(attributeMap)
.setKind(SpanKind.CLIENT)
.setStatus(StatusData.error())
.setTotalAttributeCount(2)
.build();
assertThat(exporter.generateSpan(data))
.isEqualTo(
buildZipkinSpan(Span.Kind.CLIENT).toBuilder()
.clearTags()
.putTag(SemanticAttributes.HTTP_STATUS_CODE.getKey(), "404")
.putTag(ZipkinSpanExporter.OTEL_STATUS_CODE, "ERROR")
.putTag("error", "A user provided error")
.build());
}
@Test
void generateSpan_WithRpcTimeoutErrorStatus_WithTimeoutErrorDescription() {
Attributes attributeMap = Attributes.of(SemanticAttributes.RPC_SERVICE, "my service name");
String errorMessage = "timeout";
SpanData data =
buildStandardSpan()
.setStatus(StatusData.create(StatusCode.ERROR, errorMessage))
.setAttributes(attributeMap)
.setTotalAttributeCount(1)
.build();
assertThat(exporter.generateSpan(data))
.isEqualTo(
buildZipkinSpan(Span.Kind.SERVER).toBuilder()
.putTag(SemanticAttributes.RPC_SERVICE.getKey(), "my service name")
.putTag(ZipkinSpanExporter.OTEL_STATUS_CODE, "ERROR")
.putTag(ZipkinSpanExporter.STATUS_ERROR.getKey(), errorMessage)
.build());
}
@Test
void generateSpan_WithRpcErrorStatus_WithEmptyErrorDescription() {
Attributes attributeMap = Attributes.of(SemanticAttributes.RPC_SERVICE, "my service name");
SpanData data =
buildStandardSpan()
.setStatus(StatusData.create(StatusCode.ERROR, ""))
.setAttributes(attributeMap)
.setTotalAttributeCount(1)
.build();
assertThat(exporter.generateSpan(data))
.isEqualTo(
buildZipkinSpan(Span.Kind.SERVER).toBuilder()
.putTag(SemanticAttributes.RPC_SERVICE.getKey(), "my service name")
.putTag(ZipkinSpanExporter.OTEL_STATUS_CODE, "ERROR")
.putTag(ZipkinSpanExporter.STATUS_ERROR.getKey(), "")
.build());
}
@Test
void generateSpan_WithRpcUnsetStatus() {
Attributes attributeMap = Attributes.of(SemanticAttributes.RPC_SERVICE, "my service name");
SpanData data =
buildStandardSpan()
.setStatus(StatusData.create(StatusCode.UNSET, ""))
.setAttributes(attributeMap)
.setTotalAttributeCount(1)
.build();
assertThat(exporter.generateSpan(data))
.isEqualTo(
buildZipkinSpan(Span.Kind.SERVER).toBuilder()
.putTag(SemanticAttributes.RPC_SERVICE.getKey(), "my service name")
.build());
}
@Mock private OtelToZipkinSpanTransformer mockTransformer;
@Mock private InetAddress localIp;
@Test
void testExport() {
TestSpanData testSpanData = spanBuilder().build();
ZipkinSpanExporter zipkinSpanExporter =
new ZipkinSpanExporter(mockEncoder, mockSender, MeterProvider.noop());
new ZipkinSpanExporter(mockEncoder, mockSender, MeterProvider.noop(), mockTransformer);
byte[] someBytes = new byte[0];
when(mockEncoder.encode(
standardZipkinSpanBuilder(Span.Kind.SERVER)
.putTag(ZipkinSpanExporter.OTEL_STATUS_CODE, "OK")
.build()))
.thenReturn(someBytes);
Span zipkinSpan =
zipkinSpanBuilder(Span.Kind.SERVER, localIp)
.putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK")
.build();
when(mockTransformer.generateSpan(testSpanData)).thenReturn(zipkinSpan);
when(mockEncoder.encode(zipkinSpan)).thenReturn(someBytes);
when(mockSender.sendSpans(Collections.singletonList(someBytes))).thenReturn(mockZipkinCall);
doAnswer(
invocation -> {
@ -353,7 +66,7 @@ class ZipkinSpanExporterTest {
.enqueue(any());
CompletableResultCode resultCode =
zipkinSpanExporter.export(Collections.singleton(buildStandardSpan().build()));
zipkinSpanExporter.export(Collections.singleton(testSpanData));
assertThat(resultCode.isSuccess()).isTrue();
}
@ -361,15 +74,18 @@ class ZipkinSpanExporterTest {
@Test
@SuppressLogger(ZipkinSpanExporter.class)
void testExport_failed() {
TestSpanData testSpanData = spanBuilder().build();
ZipkinSpanExporter zipkinSpanExporter =
new ZipkinSpanExporter(mockEncoder, mockSender, MeterProvider.noop());
new ZipkinSpanExporter(mockEncoder, mockSender, MeterProvider.noop(), mockTransformer);
byte[] someBytes = new byte[0];
when(mockEncoder.encode(
standardZipkinSpanBuilder(Span.Kind.SERVER)
.putTag(ZipkinSpanExporter.OTEL_STATUS_CODE, "OK")
.build()))
.thenReturn(someBytes);
Span zipkinSpan =
zipkinSpanBuilder(Span.Kind.SERVER, localIp)
.putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK")
.build();
when(mockTransformer.generateSpan(testSpanData)).thenReturn(zipkinSpan);
when(mockEncoder.encode(zipkinSpan)).thenReturn(someBytes);
when(mockSender.sendSpans(Collections.singletonList(someBytes))).thenReturn(mockZipkinCall);
doAnswer(
invocation -> {
@ -381,7 +97,7 @@ class ZipkinSpanExporterTest {
.enqueue(any());
CompletableResultCode resultCode =
zipkinSpanExporter.export(Collections.singleton(buildStandardSpan().build()));
zipkinSpanExporter.export(Collections.singleton(testSpanData));
assertThat(resultCode.isSuccess()).isFalse();
}
@ -401,51 +117,6 @@ class ZipkinSpanExporterTest {
verify(mockSender).close();
}
private static TestSpanData.Builder buildStandardSpan() {
return TestSpanData.builder()
.setSpanContext(
SpanContext.create(TRACE_ID, SPAN_ID, TraceFlags.getSampled(), TraceState.getDefault()))
.setParentSpanContext(
SpanContext.create(
TRACE_ID, PARENT_SPAN_ID, TraceFlags.getDefault(), TraceState.getDefault()))
.setResource(
Resource.create(
Attributes.builder().put(ResourceAttributes.SERVICE_NAME, "tweetiebird").build()))
.setStatus(StatusData.ok())
.setKind(SpanKind.SERVER)
.setName("Recv.helloworld.Greeter.SayHello")
.setStartEpochNanos(1505855794_194009601L)
.setEndEpochNanos(1505855799_465726528L)
.setAttributes(attributes)
.setTotalAttributeCount(attributes.size())
.setTotalRecordedEvents(annotations.size())
.setEvents(annotations)
.setLinks(Collections.emptyList())
.setHasEnded(true);
}
private Span buildZipkinSpan(Span.Kind kind) {
return standardZipkinSpanBuilder(kind).build();
}
private Span.Builder standardZipkinSpanBuilder(Span.Kind kind) {
return Span.newBuilder()
.traceId(TRACE_ID)
.parentId(PARENT_SPAN_ID)
.id(SPAN_ID)
.kind(kind)
.name("Recv.helloworld.Greeter.SayHello")
.timestamp(1505855794000000L + 194009601L / 1000)
.duration((1505855799000000L + 465726528L / 1000) - (1505855794000000L + 194009601L / 1000))
.localEndpoint(
Endpoint.newBuilder()
.ip(exporter.getLocalAddressForTest())
.serviceName("tweetiebird")
.build())
.addAnnotation(1505855799000000L + 433901068L / 1000, "RECEIVED")
.addAnnotation(1505855799000000L + 459486280L / 1000, "SENT");
}
@Test
@SuppressWarnings("PreferJavaTimeOverload")
void invalidConfig() {

View File

@ -0,0 +1,80 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.exporter.zipkin;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.TraceFlags;
import io.opentelemetry.api.trace.TraceState;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.testing.trace.TestSpanData;
import io.opentelemetry.sdk.trace.data.EventData;
import io.opentelemetry.sdk.trace.data.StatusData;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import zipkin2.Endpoint;
import zipkin2.Span;
class ZipkinTestUtil {
static final String TRACE_ID = "d239036e7d5cec116b562147388b35bf";
static final String SPAN_ID = "9cc1e3049173be09";
static final String PARENT_SPAN_ID = "8b03ab423da481c5";
private static final Attributes attributes = Attributes.empty();
private static final List<EventData> annotations =
Collections.unmodifiableList(
Arrays.asList(
EventData.create(1505855799_433901068L, "RECEIVED", Attributes.empty()),
EventData.create(1505855799_459486280L, "SENT", Attributes.empty())));
private ZipkinTestUtil() {}
static TestSpanData.Builder spanBuilder() {
return TestSpanData.builder()
.setSpanContext(
SpanContext.create(TRACE_ID, SPAN_ID, TraceFlags.getSampled(), TraceState.getDefault()))
.setParentSpanContext(
SpanContext.create(
TRACE_ID, PARENT_SPAN_ID, TraceFlags.getDefault(), TraceState.getDefault()))
.setResource(
Resource.create(
Attributes.builder().put(ResourceAttributes.SERVICE_NAME, "tweetiebird").build()))
.setStatus(StatusData.ok())
.setKind(SpanKind.SERVER)
.setName("Recv.helloworld.Greeter.SayHello")
.setStartEpochNanos(1505855794_194009601L)
.setEndEpochNanos(1505855799_465726528L)
.setAttributes(attributes)
.setTotalAttributeCount(attributes.size())
.setTotalRecordedEvents(annotations.size())
.setEvents(annotations)
.setLinks(Collections.emptyList())
.setHasEnded(true);
}
static Span zipkinSpan(Span.Kind kind, InetAddress localIp) {
return zipkinSpanBuilder(kind, localIp).build();
}
static Span.Builder zipkinSpanBuilder(Span.Kind kind, InetAddress localIp) {
return Span.newBuilder()
.traceId(TRACE_ID)
.parentId(PARENT_SPAN_ID)
.id(SPAN_ID)
.kind(kind)
.name("Recv.helloworld.Greeter.SayHello")
.timestamp(1505855794000000L + 194009601L / 1000)
.duration((1505855799000000L + 465726528L / 1000) - (1505855794000000L + 194009601L / 1000))
.localEndpoint(Endpoint.newBuilder().ip(localIp).serviceName("tweetiebird").build())
.addAnnotation(1505855799000000L + 433901068L / 1000, "RECEIVED")
.addAnnotation(1505855799000000L + 459486280L / 1000, "SENT");
}
}