Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented the Jaeger Span Exporter using gRPC as the transport #481

Merged
merged 6 commits into from
Aug 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ subprojects {
it.options.encoding = "UTF-8"

// Ignore warnings for protobuf generated files.
it.options.compilerArgs += ["-XepExcludedPaths:.*/sdk/build/generated/source/proto/.*"]
jpkrohling marked this conversation as resolved.
Show resolved Hide resolved
it.options.compilerArgs += ["-XepExcludedPaths:.*/build/generated/source/proto/.*"]

// Enforce errorprone warnings to be errors.
if (!JavaVersion.current().isJava9() && !JavaVersion.current().isJava10()) {
Expand Down Expand Up @@ -115,6 +115,7 @@ subprojects {
auto_value_annotation: "com.google.auto.value:auto-value-annotations:${autoValueVersion}",
disruptor: 'com.lmax:disruptor:3.4.2',
errorprone_annotation: "com.google.errorprone:error_prone_annotations:${errorProneVersion}",
grpc_api: "io.grpc:grpc-api:${grpcVersion}",
grpc_context: "io.grpc:grpc-context:${grpcVersion}",
guava: "com.google.guava:guava:${guavaVersion}",
jsr305: "com.google.code.findbugs:jsr305:${findBugsJsr305Version}",
Expand Down
10 changes: 10 additions & 0 deletions exporters/jaeger/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# OpenTelemetry - Jaeger Exporter - gRPC

This is the OpenTelemetry exporter, sending span data to Jaeger via gRPC.

## Proto files

The proto files in this repository were copied over from the [Jaeger main repository][proto-origin]. At this moment, they have to be manually synchronize, but a [discussion exists][proto-discussion] on how to properly consume them in a more appropriate manner.

[proto-origin]: https://github.com/jaegertracing/jaeger/tree/5b8c1f40f932897b9322bf3f110d830536ae4c71/model/proto
[proto-discussion]: https://github.com/open-telemetry/opentelemetry-java/issues/235
62 changes: 62 additions & 0 deletions exporters/jaeger/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
description = 'OpenTelemetry - Jaeger Exporter'

apply plugin: 'com.google.protobuf'

def protobufVersion = '3.7.1'
def protocVersion = '3.7.1'

buildscript {
repositories {
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.8"
}
}

dependencies {
api project(':opentelemetry-sdk')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be SDK as provided dependency?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean implementation instead of provided? I think it's appropriate to have api here, as methods from this module accept and return instances of classes defined by the SDK module.

An API dependency is one that contains at least one type that is exposed in the library binary interface, often referred to as its ABI (Application Binary Interface).

https://docs.gradle.org/current/userguide/java_library_plugin.html#sec:java_library_recognizing_dependencies

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is api translated to maven dependency type? Is it compile or provided? I thought it is provided.

Provided means that you need the JAR for compiling, but at run time there is already a JAR provided by the environment so you don't need it packaged with your app. For a web app, this means that the JAR file will not be placed into the WEB-INF/lib directory.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The differences in Gradle are mostly about what is exported to consumers of the library: an application declaring a dependency on the exporters/jaeger module will have opentelemetry-sdk as a transitive dependency, as it's declared as api. The dependencies that exporters/jaeger declare as implementation aren't transitive dependencies for a consumer.

There's a runtimeOnly mode as well, which is probably close to what provided does in Maven. But still, I don't think it's the right mode. api still seems the most appropriate here.


implementation "io.grpc:grpc-protobuf:${grpcVersion}"
implementation "io.grpc:grpc-stub:${grpcVersion}"

implementation "com.google.protobuf:protobuf-java:${protobufVersion}",
"com.google.protobuf:protobuf-java-util:${protobufVersion}"

annotationProcessor libraries.auto_value

testImplementation "io.grpc:grpc-testing:${grpcVersion}"
testRuntime "io.grpc:grpc-netty-shaded:${grpcVersion}"

signature "org.codehaus.mojo.signature:java17:1.0@signature"
signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature"
}

animalsniffer {
// Don't check sourceSets.jmh and sourceSets.test
sourceSets = [
sourceSets.main
]
}

protobuf {
protoc {
// 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
// generated Java directories as source folders.
idea {
module {
sourceDirs += file("build/generated/source/proto/main/java");
// If you have additional sourceSets and/or codegen plugins, add all of them
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
/*
* 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.jaeger;

import com.google.common.annotations.VisibleForTesting;
import com.google.protobuf.util.Timestamps;
import io.opentelemetry.exporters.jaeger.proto.api_v2.Model;
import io.opentelemetry.proto.trace.v1.AttributeValue;
import io.opentelemetry.proto.trace.v1.Span;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.annotation.concurrent.ThreadSafe;

/** Adapts OpenTelemetry objects to Jaeger objects. */
@ThreadSafe
final class Adapter {
private static final String KEY_LOG_MESSAGE = "message";
private static final String KEY_SPAN_KIND = "span.kind";
private static final String KEY_SPAN_STATUS_MESSAGE = "span.status.message";
private static final String KEY_SPAN_STATUS_CODE = "span.status.code";

private Adapter() {}

/**
* Converts a list of {@link Span} into a collection of Jaeger's {@link Model.Span}.
*
* @param spans the list of spans to be converted
* @return the collection of Jaeger spans
* @see #toJaeger(Span)
*/
static Collection<Model.Span> toJaeger(List<Span> spans) {
jpkrohling marked this conversation as resolved.
Show resolved Hide resolved
List<Model.Span> convertedList = new ArrayList<>(spans.size());
for (Span span : spans) {
convertedList.add(toJaeger(span));
}
return convertedList;
}

/**
* Converts a single {@link Span} into a Jaeger's {@link Model.Span}.
*
* @param span the span to be converted
* @return the Jaeger span
*/
static Model.Span toJaeger(Span span) {
jpkrohling marked this conversation as resolved.
Show resolved Hide resolved
Model.Span.Builder target = Model.Span.newBuilder();

target.setTraceId(span.getTraceId());
target.setSpanId(span.getSpanId());
target.setOperationName(span.getName());
target.setStartTime(span.getStartTime());
target.setDuration(Timestamps.between(span.getStartTime(), span.getEndTime()));

target.addAllTags(toKeyValues(span.getAttributes()));
target.addAllLogs(toJaegerLogs(span.getTimeEvents()));
target.addAllReferences(toSpanRefs(span.getLinks()));
jpkrohling marked this conversation as resolved.
Show resolved Hide resolved

// add the parent span
target.addReferences(
Model.SpanRef.newBuilder()
.setTraceId(span.getTraceId())
.setSpanId(span.getParentSpanId())
.setRefType(Model.SpanRefType.CHILD_OF));

if (span.getKind() != Span.SpanKind.SPAN_KIND_UNSPECIFIED) {
target.addTags(
Model.KeyValue.newBuilder()
.setKey(KEY_SPAN_KIND)
.setVStr(span.getKind().getValueDescriptor().getName())
.build());
}

target.addTags(
Model.KeyValue.newBuilder()
.setKey(KEY_SPAN_STATUS_MESSAGE)
.setVStr(span.getStatus().getMessage())
.build());

target.addTags(
Model.KeyValue.newBuilder()
.setKey(KEY_SPAN_STATUS_CODE)
.setVInt64(span.getStatus().getCode())
.build());

return target.build();
}

/**
* Converts {@link Span.TimedEvents} into a collection of Jaeger's {@link Model.Log}.
*
* @param timeEvents the timed events to be converted
* @return a collection of Jaeger logs
* @see #toJaegerLog(Span.TimedEvent)
*/
@VisibleForTesting
static Collection<Model.Log> toJaegerLogs(Span.TimedEvents timeEvents) {
jpkrohling marked this conversation as resolved.
Show resolved Hide resolved
List<Model.Log> logs = new ArrayList<>(timeEvents.getTimedEventCount());
for (Span.TimedEvent e : timeEvents.getTimedEventList()) {
logs.add(toJaegerLog(e));
}
return logs;
}

/**
* Converts a {@link Span.TimedEvent} into Jaeger's {@link Model.Log}.
*
* @param timeEvent the timed event to be converted
* @return a Jaeger log
*/
@VisibleForTesting
static Model.Log toJaegerLog(Span.TimedEvent timeEvent) {
Model.Log.Builder builder = Model.Log.newBuilder();
builder.setTimestamp(timeEvent.getTime());

Span.TimedEvent.Event event = timeEvent.getEvent();

// name is a top-level property in OpenTelemetry
builder.addFields(
Model.KeyValue.newBuilder().setKey(KEY_LOG_MESSAGE).setVStr(event.getName()).build());
builder.addAllFields(toKeyValues(event.getAttributes()));

return builder.build();
}

/**
* Converts {@link Span.Attributes} into a collection of Jaeger's {@link Model.KeyValue}.
*
* @param attributes the span attributes
* @return a collection of Jaeger key values
* @see #toKeyValue(String, AttributeValue)
*/
@VisibleForTesting
static Collection<Model.KeyValue> toKeyValues(Span.Attributes attributes) {
ArrayList<Model.KeyValue> tags = new ArrayList<>(attributes.getAttributeMapCount());
for (Map.Entry<String, AttributeValue> entry : attributes.getAttributeMapMap().entrySet()) {
tags.add(toKeyValue(entry.getKey(), entry.getValue()));
}
return tags;
}

/**
* Converts the given key and {@link AttributeValue} into Jaeger's {@link Model.KeyValue}.
*
* @param key the entry key as string
* @param value the entry value
* @return a Jaeger key value
*/
@VisibleForTesting
static Model.KeyValue toKeyValue(String key, AttributeValue value) {
Model.KeyValue.Builder builder = Model.KeyValue.newBuilder();
builder.setKey(key);

switch (value.getValueCase()) {
case STRING_VALUE:
builder.setVStr(value.getStringValue());
break;
case INT_VALUE:
builder.setVInt64(value.getIntValue());
break;
case BOOL_VALUE:
builder.setVBool(value.getBoolValue());
break;
case DOUBLE_VALUE:
builder.setVFloat64(value.getDoubleValue());
break;
case VALUE_NOT_SET:
break;
}

return builder.build();
}

/**
* Converts {@link Span.Links} into a collection of Jaeger's {@link Model.SpanRef}.
*
* @param links the span's links property to be converted
* @return a collection of Jaeger span references
*/
@VisibleForTesting
static Collection<Model.SpanRef> toSpanRefs(Span.Links links) {
List<Model.SpanRef> spanRefs = new ArrayList<>(links.getLinkCount());
for (Span.Link link : links.getLinkList()) {
spanRefs.add(toSpanRef(link));
}
return spanRefs;
}

/**
* Converts a single {@link Span.Link} into a Jaeger's {@link Model.SpanRef}.
*
* @param link the OpenTelemetry link to be converted
* @return the Jaeger span reference
*/
@VisibleForTesting
static Model.SpanRef toSpanRef(Span.Link link) {
Model.SpanRef.Builder builder = Model.SpanRef.newBuilder();
builder.setTraceId(link.getTraceId());
builder.setSpanId(link.getSpanId());

// we can assume that all links are *follows from*
// https://github.com/open-telemetry/opentelemetry-java/issues/475
// https://github.com/open-telemetry/opentelemetry-java/pull/481/files#r312577862
builder.setRefType(Model.SpanRefType.FOLLOWS_FROM);

return builder.build();
}
}
Loading