Add OTLP trace implementation (#984)

Signed-off-by: Bogdan Cristian Drutu <bogdandrutu@gmail.com>
This commit is contained in:
Bogdan Drutu 2020-03-10 16:12:42 -07:00 committed by GitHub
parent 49a5e01ce1
commit 0aa70d123f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 1091 additions and 4 deletions

View File

@ -9,10 +9,12 @@ description = 'OpenTelemetry Protocol Exporter'
ext.moduleName = "io.opentelemetry.exporters.otprotocol"
dependencies {
api project(':opentelemetry-proto'),
project(':opentelemetry-sdk')
api project(':opentelemetry-sdk')
implementation libraries.grpc_protobuf,
implementation project(':opentelemetry-sdk-contrib-otproto'),
project(':opentelemetry-sdk'),
libraries.grpc_api,
libraries.grpc_protobuf,
libraries.grpc_stub,
libraries.protobuf,
libraries.protobuf_util

View File

@ -0,0 +1,49 @@
/*
* Copyright 2020, OpenTelemetry 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 io.opentelemetry.exporters.otlp;
import io.opentelemetry.proto.common.v1.AttributeKeyValue;
import io.opentelemetry.proto.common.v1.AttributeKeyValue.ValueType;
import io.opentelemetry.trace.AttributeValue;
final class CommonAdapter {
static AttributeKeyValue toProtoAttribute(String key, AttributeValue attributeValue) {
AttributeKeyValue.Builder builder = AttributeKeyValue.newBuilder().setKey(key);
switch (attributeValue.getType()) {
case STRING:
return builder
.setType(ValueType.STRING)
.setStringValue(attributeValue.getStringValue())
.build();
case BOOLEAN:
return builder
.setType(ValueType.BOOL)
.setBoolValue(attributeValue.getBooleanValue())
.build();
case LONG:
return builder.setType(ValueType.INT).setIntValue(attributeValue.getLongValue()).build();
case DOUBLE:
return builder
.setType(ValueType.DOUBLE)
.setDoubleValue(attributeValue.getDoubleValue())
.build();
}
return builder.setType(ValueType.UNRECOGNIZED).build();
}
private CommonAdapter() {}
}

View File

@ -0,0 +1,153 @@
/*
* Copyright 2020, OpenTelemetry 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 io.opentelemetry.exporters.otlp;
import io.grpc.ManagedChannel;
import io.grpc.StatusRuntimeException;
import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest;
import io.opentelemetry.proto.collector.trace.v1.TraceServiceGrpc;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe;
/** Exports spans using OTLP via gRPC, using OpenTelemetry's protobuf model. */
@ThreadSafe
public final class OtlpGrpcSpanExporter implements SpanExporter {
private static final Logger logger = Logger.getLogger(OtlpGrpcSpanExporter.class.getName());
private final TraceServiceGrpc.TraceServiceBlockingStub blockingStub;
private final ManagedChannel managedChannel;
private final long deadlineMs;
/**
* Creates a new Jaeger gRPC Span Reporter with the given name, using the given channel.
*
* @param channel the channel to use when communicating with the Jaeger Collector.
* @param deadlineMs max waiting time for the collector to process each span batch. When set to 0
* or to a negative value, the exporter will wait indefinitely.
*/
private OtlpGrpcSpanExporter(ManagedChannel channel, long deadlineMs) {
this.managedChannel = channel;
this.blockingStub = TraceServiceGrpc.newBlockingStub(channel);
this.deadlineMs = deadlineMs;
}
/**
* Submits all the given spans in a single batch to the Jaeger collector.
*
* @param spans the list of sampled Spans to be exported.
* @return the result of the operation
*/
@Override
public ResultCode export(List<SpanData> spans) {
ExportTraceServiceRequest exportTraceServiceRequest =
ExportTraceServiceRequest.newBuilder()
.addAllResourceSpans(SpanAdapter.toProtoResourceSpans(spans))
.build();
try {
TraceServiceGrpc.TraceServiceBlockingStub stub = this.blockingStub;
if (deadlineMs > 0) {
stub = stub.withDeadlineAfter(deadlineMs, TimeUnit.MILLISECONDS);
}
// for now, there's nothing to check in the response object
// noinspection ResultOfMethodCallIgnored
stub.export(exportTraceServiceRequest);
return ResultCode.SUCCESS;
} catch (StatusRuntimeException e) {
// Retryable codes from https://github.com/open-telemetry/oteps/pull/65
switch (e.getStatus().getCode()) {
case CANCELLED:
case DEADLINE_EXCEEDED:
case RESOURCE_EXHAUSTED:
case OUT_OF_RANGE:
case UNAVAILABLE:
case DATA_LOSS:
logger.info(e.getStatus().toString());
return ResultCode.FAILED_RETRYABLE;
default:
return ResultCode.FAILED_NOT_RETRYABLE;
}
} catch (Throwable t) {
return ResultCode.FAILED_NOT_RETRYABLE;
}
}
/**
* Creates a new builder instance.
*
* @return a new instance builder for this exporter
*/
public static Builder newBuilder() {
return new Builder();
}
/**
* Initiates an orderly shutdown in which preexisting calls continue but new calls are immediately
* cancelled. The channel is forcefully closed after a timeout.
*/
@Override
public void shutdown() {
try {
managedChannel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
logger.log(Level.WARNING, "Failed to shutdown the gRPC channel", e);
}
}
/** Builder utility for this exporter. */
public static class Builder {
private ManagedChannel channel;
private long deadlineMs = 1_000; // 1 second
/**
* Sets the managed chanel to use when communicating with the backend. Required.
*
* @param channel the channel to use
* @return this builder's instance
*/
public Builder setChannel(ManagedChannel channel) {
this.channel = channel;
return this;
}
/**
* Sets the max waiting time for the collector to process each span batch. Optional.
*
* @param deadlineMs the max waiting time
* @return this builder's instance
*/
public Builder setDeadlineMs(long deadlineMs) {
this.deadlineMs = deadlineMs;
return this;
}
/**
* Constructs a new instance of the exporter based on the builder's values.
*
* @return a new exporter's instance
*/
public OtlpGrpcSpanExporter build() {
return new OtlpGrpcSpanExporter(channel, deadlineMs);
}
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright 2020, OpenTelemetry 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 io.opentelemetry.exporters.otlp;
import io.opentelemetry.proto.resource.v1.Resource;
import io.opentelemetry.trace.AttributeValue;
import java.util.Map;
final class ResourceAdapter {
static Resource toProtoResource(io.opentelemetry.sdk.resources.Resource resource) {
Resource.Builder builder = Resource.newBuilder();
for (Map.Entry<String, AttributeValue> resourceEntry : resource.getLabels().entrySet()) {
builder.addAttributes(
CommonAdapter.toProtoAttribute(resourceEntry.getKey(), resourceEntry.getValue()));
}
return builder.build();
}
private ResourceAdapter() {}
}

View File

@ -0,0 +1,135 @@
/*
* Copyright 2020, OpenTelemetry 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 io.opentelemetry.exporters.otlp;
import io.opentelemetry.proto.trace.v1.ResourceSpans;
import io.opentelemetry.proto.trace.v1.Span;
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.sdk.contrib.otproto.TraceProtoUtils;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.data.SpanData.TimedEvent;
import io.opentelemetry.trace.AttributeValue;
import io.opentelemetry.trace.Link;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
final class SpanAdapter {
static List<ResourceSpans> toProtoResourceSpans(List<SpanData> spanDataList) {
Map<Resource, ResourceSpans.Builder> resourceSpansBuilderMap = new HashMap<>();
for (SpanData spanData : spanDataList) {
Resource resource = spanData.getResource();
ResourceSpans.Builder resourceSpansBuilder =
resourceSpansBuilderMap.get(spanData.getResource());
if (resourceSpansBuilder == null) {
resourceSpansBuilder =
ResourceSpans.newBuilder().setResource(ResourceAdapter.toProtoResource(resource));
resourceSpansBuilderMap.put(resource, resourceSpansBuilder);
}
resourceSpansBuilder.addSpans(toProtoSpan(spanData));
}
List<ResourceSpans> resourceSpans = new ArrayList<>(resourceSpansBuilderMap.size());
for (ResourceSpans.Builder resourceSpansBuilder : resourceSpansBuilderMap.values()) {
resourceSpans.add(resourceSpansBuilder.build());
}
return resourceSpans;
}
static Span toProtoSpan(SpanData spanData) {
Span.Builder builder = Span.newBuilder();
builder.setTraceId(TraceProtoUtils.toProtoTraceId(spanData.getTraceId()));
builder.setSpanId(TraceProtoUtils.toProtoSpanId(spanData.getSpanId()));
// TODO: Set TraceState;
builder.setParentSpanId(TraceProtoUtils.toProtoSpanId(spanData.getParentSpanId()));
builder.setName(spanData.getName());
builder.setKind(toProtoSpanKind(spanData.getKind()));
builder.setStartTimeUnixnano(spanData.getStartEpochNanos());
builder.setEndTimeUnixnano(spanData.getEndEpochNanos());
for (Map.Entry<String, AttributeValue> resourceEntry : spanData.getAttributes().entrySet()) {
builder.addAttributes(
CommonAdapter.toProtoAttribute(resourceEntry.getKey(), resourceEntry.getValue()));
}
// TODO: Set DroppedAttributesCount;
for (TimedEvent timedEvent : spanData.getTimedEvents()) {
builder.addEvents(toProtoSpanEvent(timedEvent));
}
builder.setDroppedEventsCount(
spanData.getTotalRecordedEvents() - spanData.getTimedEvents().size());
for (Link link : spanData.getLinks()) {
builder.addLinks(toProtoSpanLink(link));
}
builder.setDroppedLinksCount(spanData.getTotalRecordedLinks() - spanData.getLinks().size());
builder.setStatus(toStatusProto(spanData.getStatus()));
return builder.build();
}
static Span.SpanKind toProtoSpanKind(io.opentelemetry.trace.Span.Kind kind) {
switch (kind) {
case INTERNAL:
return SpanKind.INTERNAL;
case SERVER:
return SpanKind.SERVER;
case CLIENT:
return SpanKind.CLIENT;
case PRODUCER:
return SpanKind.PRODUCER;
case CONSUMER:
return SpanKind.CONSUMER;
}
return SpanKind.UNRECOGNIZED;
}
static Span.Event toProtoSpanEvent(TimedEvent timedEvent) {
Span.Event.Builder builder = Span.Event.newBuilder();
builder.setName(timedEvent.getName());
builder.setTimeUnixnano(timedEvent.getEpochNanos());
for (Map.Entry<String, AttributeValue> resourceEntry : timedEvent.getAttributes().entrySet()) {
builder.addAttributes(
CommonAdapter.toProtoAttribute(resourceEntry.getKey(), resourceEntry.getValue()));
}
// TODO: Set DroppedAttributesCount;
return builder.build();
}
static Span.Link toProtoSpanLink(Link link) {
Span.Link.Builder builder = Span.Link.newBuilder();
builder.setTraceId(TraceProtoUtils.toProtoTraceId(link.getContext().getTraceId()));
builder.setSpanId(TraceProtoUtils.toProtoSpanId(link.getContext().getSpanId()));
// TODO: Set TraceState;
for (Map.Entry<String, AttributeValue> resourceEntry : link.getAttributes().entrySet()) {
builder.addAttributes(
CommonAdapter.toProtoAttribute(resourceEntry.getKey(), resourceEntry.getValue()));
}
// TODO: Set DroppedAttributesCount;
return builder.build();
}
static Status toStatusProto(io.opentelemetry.trace.Status status) {
Status.Builder builder =
Status.newBuilder().setCode(StatusCode.forNumber(status.getCanonicalCode().value()));
if (status.getDescription() != null) {
builder.setMessage(status.getDescription());
}
return builder.build();
}
private SpanAdapter() {}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright 2019, OpenTelemetry 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 io.opentelemetry.exporters.otlp;
import static com.google.common.truth.Truth.assertThat;
import io.opentelemetry.proto.common.v1.AttributeKeyValue;
import io.opentelemetry.proto.common.v1.AttributeKeyValue.ValueType;
import io.opentelemetry.trace.AttributeValue;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link CommonAdapter}. */
@RunWith(JUnit4.class)
public class CommonAdapterTest {
@Test
public void toProtoAttribute_Bool() {
assertThat(CommonAdapter.toProtoAttribute("key", AttributeValue.booleanAttributeValue(true)))
.isEqualTo(
AttributeKeyValue.newBuilder()
.setKey("key")
.setBoolValue(true)
.setType(ValueType.BOOL)
.build());
}
@Test
public void toProtoAttribute_String() {
assertThat(CommonAdapter.toProtoAttribute("key", AttributeValue.stringAttributeValue("string")))
.isEqualTo(
AttributeKeyValue.newBuilder()
.setKey("key")
.setStringValue("string")
.setType(ValueType.STRING)
.build());
}
@Test
public void toProtoAttribute_Int() {
assertThat(CommonAdapter.toProtoAttribute("key", AttributeValue.longAttributeValue(100)))
.isEqualTo(
AttributeKeyValue.newBuilder()
.setKey("key")
.setIntValue(100)
.setType(ValueType.INT)
.build());
}
@Test
public void toProtoAttribute_Double() {
assertThat(CommonAdapter.toProtoAttribute("key", AttributeValue.doubleAttributeValue(100.3)))
.isEqualTo(
AttributeKeyValue.newBuilder()
.setKey("key")
.setDoubleValue(100.3)
.setType(ValueType.DOUBLE)
.build());
}
}

View File

@ -0,0 +1,253 @@
/*
* Copyright 2019, OpenTelemetry 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 io.opentelemetry.exporters.otlp;
import static com.google.common.truth.Truth.assertThat;
import io.grpc.ManagedChannel;
import io.grpc.Status.Code;
import io.grpc.inprocess.InProcessChannelBuilder;
import io.grpc.inprocess.InProcessServerBuilder;
import io.grpc.testing.GrpcCleanupRule;
import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest;
import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse;
import io.opentelemetry.proto.collector.trace.v1.TraceServiceGrpc;
import io.opentelemetry.proto.trace.v1.ResourceSpans;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.export.SpanExporter.ResultCode;
import io.opentelemetry.trace.Link;
import io.opentelemetry.trace.Span.Kind;
import io.opentelemetry.trace.SpanId;
import io.opentelemetry.trace.Status;
import io.opentelemetry.trace.TraceId;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link OtlpGrpcSpanExporter}. */
@RunWith(JUnit4.class)
public class OtlpGrpcSpanExporterTest {
private static final String TRACE_ID = "00000000000000000000000000abc123";
private static final String SPAN_ID = "0000000000def456";
@Rule public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
private final FakeCollector fakeCollector = new FakeCollector();
private final String serverName = InProcessServerBuilder.generateName();
private final ManagedChannel inProcessChannel =
InProcessChannelBuilder.forName(serverName).directExecutor().build();
@Before
public void setup() throws IOException {
grpcCleanup.register(
InProcessServerBuilder.forName(serverName)
.directExecutor()
.addService(fakeCollector)
.build()
.start());
grpcCleanup.register(inProcessChannel);
}
@Test
public void testExport() {
SpanData span = generateFakeSpan();
OtlpGrpcSpanExporter exporter =
OtlpGrpcSpanExporter.newBuilder().setChannel(inProcessChannel).build();
try {
assertThat(exporter.export(Collections.singletonList(span))).isEqualTo(ResultCode.SUCCESS);
assertThat(fakeCollector.getReceivedSpans())
.isEqualTo(SpanAdapter.toProtoResourceSpans(Collections.singletonList(span)));
} finally {
exporter.shutdown();
}
}
@Test
public void testExport_MultipleSpans() {
List<SpanData> spans = new ArrayList<>();
for (int i = 0; i < 10; i++) {
spans.add(generateFakeSpan());
}
OtlpGrpcSpanExporter exporter =
OtlpGrpcSpanExporter.newBuilder().setChannel(inProcessChannel).build();
try {
assertThat(exporter.export(spans)).isEqualTo(ResultCode.SUCCESS);
assertThat(fakeCollector.getReceivedSpans())
.isEqualTo(SpanAdapter.toProtoResourceSpans(spans));
} finally {
exporter.shutdown();
}
}
@Test
public void testExport_AfterShutdown() {
SpanData span = generateFakeSpan();
OtlpGrpcSpanExporter exporter =
OtlpGrpcSpanExporter.newBuilder().setChannel(inProcessChannel).build();
exporter.shutdown();
// TODO: This probably should not be retryable because we never restart the channel.
assertThat(exporter.export(Collections.singletonList(span)))
.isEqualTo(ResultCode.FAILED_RETRYABLE);
}
@Test
public void testExport_Cancelled() {
fakeCollector.setReturnedStatus(io.grpc.Status.CANCELLED);
OtlpGrpcSpanExporter exporter =
OtlpGrpcSpanExporter.newBuilder().setChannel(inProcessChannel).build();
try {
assertThat(exporter.export(Collections.singletonList(generateFakeSpan())))
.isEqualTo(ResultCode.FAILED_RETRYABLE);
} finally {
exporter.shutdown();
}
}
@Test
public void testExport_DeadlineExceeded() {
fakeCollector.setReturnedStatus(io.grpc.Status.DEADLINE_EXCEEDED);
OtlpGrpcSpanExporter exporter =
OtlpGrpcSpanExporter.newBuilder().setChannel(inProcessChannel).build();
try {
assertThat(exporter.export(Collections.singletonList(generateFakeSpan())))
.isEqualTo(ResultCode.FAILED_RETRYABLE);
} finally {
exporter.shutdown();
}
}
@Test
public void testExport_ResourceExhausted() {
fakeCollector.setReturnedStatus(io.grpc.Status.RESOURCE_EXHAUSTED);
OtlpGrpcSpanExporter exporter =
OtlpGrpcSpanExporter.newBuilder().setChannel(inProcessChannel).build();
try {
assertThat(exporter.export(Collections.singletonList(generateFakeSpan())))
.isEqualTo(ResultCode.FAILED_RETRYABLE);
} finally {
exporter.shutdown();
}
}
@Test
public void testExport_OutOfRange() {
fakeCollector.setReturnedStatus(io.grpc.Status.OUT_OF_RANGE);
OtlpGrpcSpanExporter exporter =
OtlpGrpcSpanExporter.newBuilder().setChannel(inProcessChannel).build();
try {
assertThat(exporter.export(Collections.singletonList(generateFakeSpan())))
.isEqualTo(ResultCode.FAILED_RETRYABLE);
} finally {
exporter.shutdown();
}
}
@Test
public void testExport_Unavailable() {
fakeCollector.setReturnedStatus(io.grpc.Status.UNAVAILABLE);
OtlpGrpcSpanExporter exporter =
OtlpGrpcSpanExporter.newBuilder().setChannel(inProcessChannel).build();
try {
assertThat(exporter.export(Collections.singletonList(generateFakeSpan())))
.isEqualTo(ResultCode.FAILED_RETRYABLE);
} finally {
exporter.shutdown();
}
}
@Test
public void testExport_DataLoss() {
fakeCollector.setReturnedStatus(io.grpc.Status.DATA_LOSS);
OtlpGrpcSpanExporter exporter =
OtlpGrpcSpanExporter.newBuilder().setChannel(inProcessChannel).build();
try {
assertThat(exporter.export(Collections.singletonList(generateFakeSpan())))
.isEqualTo(ResultCode.FAILED_RETRYABLE);
} finally {
exporter.shutdown();
}
}
@Test
public void testExport_PermissionDenied() {
fakeCollector.setReturnedStatus(io.grpc.Status.PERMISSION_DENIED);
OtlpGrpcSpanExporter exporter =
OtlpGrpcSpanExporter.newBuilder().setChannel(inProcessChannel).build();
try {
assertThat(exporter.export(Collections.singletonList(generateFakeSpan())))
.isEqualTo(ResultCode.FAILED_NOT_RETRYABLE);
} finally {
exporter.shutdown();
}
}
private static SpanData generateFakeSpan() {
long duration = TimeUnit.MILLISECONDS.toNanos(900);
long startNs = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis());
long endNs = startNs + duration;
return SpanData.newBuilder()
.setHasEnded(true)
.setTraceId(TraceId.fromLowerBase16(TRACE_ID, 0))
.setSpanId(SpanId.fromLowerBase16(SPAN_ID, 0))
.setName("GET /api/endpoint")
.setStartEpochNanos(startNs)
.setEndEpochNanos(endNs)
.setStatus(Status.OK)
.setKind(Kind.SERVER)
.setLinks(Collections.<Link>emptyList())
.setTotalRecordedLinks(0)
.setTotalRecordedEvents(0)
.build();
}
private static final class FakeCollector extends TraceServiceGrpc.TraceServiceImplBase {
private final List<ResourceSpans> receivedSpans = new ArrayList<>();
private io.grpc.Status returnedStatus = io.grpc.Status.OK;
@Override
public void export(
ExportTraceServiceRequest request,
io.grpc.stub.StreamObserver<ExportTraceServiceResponse> responseObserver) {
receivedSpans.addAll(request.getResourceSpansList());
responseObserver.onNext(ExportTraceServiceResponse.newBuilder().build());
if (!returnedStatus.isOk()) {
if (returnedStatus.getCode() == Code.DEADLINE_EXCEEDED) {
// Do not call onCompleted to simulate a deadline exceeded.
return;
}
responseObserver.onError(returnedStatus.asRuntimeException());
return;
}
responseObserver.onCompleted();
}
List<ResourceSpans> getReceivedSpans() {
return receivedSpans;
}
void setReturnedStatus(io.grpc.Status returnedStatus) {
this.returnedStatus = returnedStatus;
}
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright 2019, OpenTelemetry 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 io.opentelemetry.exporters.otlp;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableMap;
import io.opentelemetry.proto.common.v1.AttributeKeyValue;
import io.opentelemetry.proto.common.v1.AttributeKeyValue.ValueType;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.trace.AttributeValue;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link ResourceAdapter}. */
@RunWith(JUnit4.class)
public class ResourceAdapterTest {
@Test
public void toProtoResource() {
assertThat(
ResourceAdapter.toProtoResource(
Resource.create(
ImmutableMap.of(
"key_bool",
AttributeValue.booleanAttributeValue(true),
"key_string",
AttributeValue.stringAttributeValue("string"),
"key_int",
AttributeValue.longAttributeValue(100),
"key_double",
AttributeValue.doubleAttributeValue(100.3))))
.getAttributesList())
.containsExactly(
AttributeKeyValue.newBuilder()
.setKey("key_bool")
.setBoolValue(true)
.setType(ValueType.BOOL)
.build(),
AttributeKeyValue.newBuilder()
.setKey("key_string")
.setStringValue("string")
.setType(ValueType.STRING)
.build(),
AttributeKeyValue.newBuilder()
.setKey("key_int")
.setIntValue(100)
.setType(ValueType.INT)
.build(),
AttributeKeyValue.newBuilder()
.setKey("key_double")
.setDoubleValue(100.3)
.setType(ValueType.DOUBLE)
.build());
}
@Test
public void toProtoResource_Empty() {
assertThat(ResourceAdapter.toProtoResource(Resource.getEmpty()))
.isEqualTo(io.opentelemetry.proto.resource.v1.Resource.newBuilder().build());
}
}

View File

@ -0,0 +1,302 @@
/*
* Copyright 2019, OpenTelemetry 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 io.opentelemetry.exporters.otlp;
import static com.google.common.truth.Truth.assertThat;
import com.google.protobuf.ByteString;
import io.opentelemetry.proto.common.v1.AttributeKeyValue;
import io.opentelemetry.proto.common.v1.AttributeKeyValue.ValueType;
import io.opentelemetry.proto.trace.v1.Span;
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.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.data.SpanData.Link;
import io.opentelemetry.sdk.trace.data.SpanData.TimedEvent;
import io.opentelemetry.trace.AttributeValue;
import io.opentelemetry.trace.Span.Kind;
import io.opentelemetry.trace.SpanContext;
import io.opentelemetry.trace.SpanId;
import io.opentelemetry.trace.TraceFlags;
import io.opentelemetry.trace.TraceId;
import io.opentelemetry.trace.TraceState;
import java.util.Collections;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link ResourceAdapter}. */
@RunWith(JUnit4.class)
public class SpanAdapterTest {
private static final byte[] TRACE_ID_BYTES =
new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4};
private static final TraceId TRACE_ID = TraceId.fromBytes(TRACE_ID_BYTES, 0);
private static final byte[] SPAN_ID_BYTES = new byte[] {0, 0, 0, 0, 4, 3, 2, 1};
private static final SpanId SPAN_ID = SpanId.fromBytes(SPAN_ID_BYTES, 0);
private static final TraceState TRACE_STATE = TraceState.builder().build();
private static final SpanContext SPAN_CONTEXT =
SpanContext.create(
TRACE_ID, SPAN_ID, TraceFlags.builder().setIsSampled(true).build(), TRACE_STATE);
@Test
public void toProtoSpan() {
Span span =
SpanAdapter.toProtoSpan(
SpanData.newBuilder()
.setHasEnded(true)
.setTraceId(TRACE_ID)
.setSpanId(SPAN_ID)
.setParentSpanId(SpanId.getInvalid())
.setName("GET /api/endpoint")
.setKind(Kind.SERVER)
.setStartEpochNanos(12345)
.setEndEpochNanos(12349)
.setAttributes(
Collections.singletonMap("key", AttributeValue.booleanAttributeValue(true)))
.setTimedEvents(
Collections.singletonList(
TimedEvent.create(
12347, "my_event", Collections.<String, AttributeValue>emptyMap())))
.setTotalRecordedEvents(3)
.setLinks(
Collections.<io.opentelemetry.trace.Link>singletonList(
Link.create(SPAN_CONTEXT)))
.setTotalRecordedLinks(2)
.setStatus(io.opentelemetry.trace.Status.OK)
.build());
assertThat(span.getTraceId().toByteArray()).isEqualTo(TRACE_ID_BYTES);
assertThat(span.getSpanId().toByteArray()).isEqualTo(SPAN_ID_BYTES);
assertThat(span.getParentSpanId().toByteArray()).isEqualTo(new byte[] {0, 0, 0, 0, 0, 0, 0, 0});
assertThat(span.getName()).isEqualTo("GET /api/endpoint");
assertThat(span.getKind()).isEqualTo(SpanKind.SERVER);
assertThat(span.getStartTimeUnixnano()).isEqualTo(12345);
assertThat(span.getEndTimeUnixnano()).isEqualTo(12349);
assertThat(span.getAttributesList())
.containsExactly(
AttributeKeyValue.newBuilder()
.setKey("key")
.setBoolValue(true)
.setType(ValueType.BOOL)
.build());
// Fix this when we plugged dropped attributes.
assertThat(span.getDroppedAttributesCount()).isEqualTo(0);
assertThat(span.getEventsList())
.containsExactly(
Span.Event.newBuilder().setTimeUnixnano(12347).setName("my_event").build());
assertThat(span.getDroppedEventsCount()).isEqualTo(2); // 3 - 1
assertThat(span.getLinksList())
.containsExactly(
Span.Link.newBuilder()
.setTraceId(ByteString.copyFrom(TRACE_ID_BYTES))
.setSpanId(ByteString.copyFrom(SPAN_ID_BYTES))
.build());
assertThat(span.getDroppedLinksCount()).isEqualTo(1); // 2 - 1
assertThat(span.getStatus()).isEqualTo(Status.newBuilder().setCode(StatusCode.Ok).build());
}
@Test
public void toProtoSpanKind() {
assertThat(SpanAdapter.toProtoSpanKind(Kind.INTERNAL)).isEqualTo(SpanKind.INTERNAL);
assertThat(SpanAdapter.toProtoSpanKind(Kind.CLIENT)).isEqualTo(SpanKind.CLIENT);
assertThat(SpanAdapter.toProtoSpanKind(Kind.SERVER)).isEqualTo(SpanKind.SERVER);
assertThat(SpanAdapter.toProtoSpanKind(Kind.PRODUCER)).isEqualTo(SpanKind.PRODUCER);
assertThat(SpanAdapter.toProtoSpanKind(Kind.CONSUMER)).isEqualTo(SpanKind.CONSUMER);
}
@Test
public void toProtoStatus() {
assertThat(SpanAdapter.toStatusProto(io.opentelemetry.trace.Status.OK))
.isEqualTo(Status.newBuilder().setCode(StatusCode.Ok).build());
assertThat(
SpanAdapter.toStatusProto(
io.opentelemetry.trace.Status.CANCELLED.withDescription("CANCELLED")))
.isEqualTo(
Status.newBuilder().setCode(StatusCode.Cancelled).setMessage("CANCELLED").build());
assertThat(
SpanAdapter.toStatusProto(
io.opentelemetry.trace.Status.UNKNOWN.withDescription("UNKNOWN")))
.isEqualTo(
Status.newBuilder().setCode(StatusCode.UnknownError).setMessage("UNKNOWN").build());
assertThat(
SpanAdapter.toStatusProto(
io.opentelemetry.trace.Status.INVALID_ARGUMENT.withDescription("INVALID_ARGUMENT")))
.isEqualTo(
Status.newBuilder()
.setCode(StatusCode.InvalidArgument)
.setMessage("INVALID_ARGUMENT")
.build());
assertThat(
SpanAdapter.toStatusProto(
io.opentelemetry.trace.Status.DEADLINE_EXCEEDED.withDescription(
"DEADLINE_EXCEEDED")))
.isEqualTo(
Status.newBuilder()
.setCode(StatusCode.DeadlineExceeded)
.setMessage("DEADLINE_EXCEEDED")
.build());
assertThat(
SpanAdapter.toStatusProto(
io.opentelemetry.trace.Status.NOT_FOUND.withDescription("NOT_FOUND")))
.isEqualTo(
Status.newBuilder().setCode(StatusCode.NotFound).setMessage("NOT_FOUND").build());
assertThat(
SpanAdapter.toStatusProto(
io.opentelemetry.trace.Status.ALREADY_EXISTS.withDescription("ALREADY_EXISTS")))
.isEqualTo(
Status.newBuilder()
.setCode(StatusCode.AlreadyExists)
.setMessage("ALREADY_EXISTS")
.build());
assertThat(
SpanAdapter.toStatusProto(
io.opentelemetry.trace.Status.PERMISSION_DENIED.withDescription(
"PERMISSION_DENIED")))
.isEqualTo(
Status.newBuilder()
.setCode(StatusCode.PermissionDenied)
.setMessage("PERMISSION_DENIED")
.build());
assertThat(
SpanAdapter.toStatusProto(
io.opentelemetry.trace.Status.RESOURCE_EXHAUSTED.withDescription(
"RESOURCE_EXHAUSTED")))
.isEqualTo(
Status.newBuilder()
.setCode(StatusCode.ResourceExhausted)
.setMessage("RESOURCE_EXHAUSTED")
.build());
assertThat(
SpanAdapter.toStatusProto(
io.opentelemetry.trace.Status.FAILED_PRECONDITION.withDescription(
"FAILED_PRECONDITION")))
.isEqualTo(
Status.newBuilder()
.setCode(StatusCode.FailedPrecondition)
.setMessage("FAILED_PRECONDITION")
.build());
assertThat(
SpanAdapter.toStatusProto(
io.opentelemetry.trace.Status.ABORTED.withDescription("ABORTED")))
.isEqualTo(Status.newBuilder().setCode(StatusCode.Aborted).setMessage("ABORTED").build());
assertThat(
SpanAdapter.toStatusProto(
io.opentelemetry.trace.Status.OUT_OF_RANGE.withDescription("OUT_OF_RANGE")))
.isEqualTo(
Status.newBuilder().setCode(StatusCode.OutOfRange).setMessage("OUT_OF_RANGE").build());
assertThat(
SpanAdapter.toStatusProto(
io.opentelemetry.trace.Status.UNIMPLEMENTED.withDescription("UNIMPLEMENTED")))
.isEqualTo(
Status.newBuilder()
.setCode(StatusCode.Unimplemented)
.setMessage("UNIMPLEMENTED")
.build());
assertThat(
SpanAdapter.toStatusProto(
io.opentelemetry.trace.Status.INTERNAL.withDescription("INTERNAL")))
.isEqualTo(
Status.newBuilder().setCode(StatusCode.InternalError).setMessage("INTERNAL").build());
assertThat(
SpanAdapter.toStatusProto(
io.opentelemetry.trace.Status.UNAVAILABLE.withDescription("UNAVAILABLE")))
.isEqualTo(
Status.newBuilder().setCode(StatusCode.Unavailable).setMessage("UNAVAILABLE").build());
assertThat(
SpanAdapter.toStatusProto(
io.opentelemetry.trace.Status.DATA_LOSS.withDescription("DATA_LOSS")))
.isEqualTo(
Status.newBuilder().setCode(StatusCode.DataLoss).setMessage("DATA_LOSS").build());
assertThat(
SpanAdapter.toStatusProto(
io.opentelemetry.trace.Status.UNAUTHENTICATED.withDescription("UNAUTHENTICATED")))
.isEqualTo(
Status.newBuilder()
.setCode(StatusCode.Unauthenticated)
.setMessage("UNAUTHENTICATED")
.build());
}
@Test
public void toProtoSpanEvent_WithoutAttributes() {
assertThat(
SpanAdapter.toProtoSpanEvent(
TimedEvent.create(
12345,
"test_without_attributes",
Collections.<String, AttributeValue>emptyMap())))
.isEqualTo(
Span.Event.newBuilder()
.setTimeUnixnano(12345)
.setName("test_without_attributes")
.build());
}
@Test
public void toProtoSpanEvent_WithAttributes() {
assertThat(
SpanAdapter.toProtoSpanEvent(
TimedEvent.create(
12345,
"test_with_attributes",
Collections.singletonMap(
"key_string", AttributeValue.stringAttributeValue("string")))))
.isEqualTo(
Span.Event.newBuilder()
.setTimeUnixnano(12345)
.setName("test_with_attributes")
.addAttributes(
AttributeKeyValue.newBuilder()
.setKey("key_string")
.setStringValue("string")
.setType(ValueType.STRING)
.build())
.build());
}
@Test
public void toProtoSpanLink_WithoutAttributes() {
assertThat(SpanAdapter.toProtoSpanLink(Link.create(SPAN_CONTEXT)))
.isEqualTo(
Span.Link.newBuilder()
.setTraceId(ByteString.copyFrom(TRACE_ID_BYTES))
.setSpanId(ByteString.copyFrom(SPAN_ID_BYTES))
.build());
}
@Test
public void toProtoSpanLink_WithAttributes() {
assertThat(
SpanAdapter.toProtoSpanLink(
Link.create(
SPAN_CONTEXT,
Collections.singletonMap(
"key_string", AttributeValue.stringAttributeValue("string")))))
.isEqualTo(
Span.Link.newBuilder()
.setTraceId(ByteString.copyFrom(TRACE_ID_BYTES))
.setSpanId(ByteString.copyFrom(SPAN_ID_BYTES))
.addAttributes(
AttributeKeyValue.newBuilder()
.setKey("key_string")
.setStringValue("string")
.setType(ValueType.STRING)
.build())
.build());
}
}

View File

@ -10,7 +10,10 @@ description = 'OpenTelemetry Proto'
ext.moduleName = 'io.opentelemetry.proto'
dependencies {
api libraries.protobuf
api libraries.protobuf,
libraries.grpc_api,
libraries.grpc_protobuf,
libraries.grpc_stub
signature "org.codehaus.mojo.signature:java17:1.0@signature"
signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature"
@ -28,6 +31,12 @@ protobuf {
// The artifact spec for the Protobuf Compiler
artifact = "com.google.protobuf:protoc:${protocVersion}"
}
plugins {
grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" }
}
generateProtoTasks {
all()*.plugins { grpc {} }
}
}
// IntelliJ complains that the generated classes are not found, ask IntelliJ to include the