aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdrian Cole <adriancole@users.noreply.github.com>2017-09-24 10:46:17 +0800
committerGitHub <noreply@github.com>2017-09-24 10:46:17 +0800
commitd05056c351f301e52efc07eb6389afd10d3949ca (patch)
treeb6d5a0801fe5d329aab57363399276ad0f95bb03
parent83f82dbe96fd5e0fc2468aa2b696a92953666c65 (diff)
downloadplatform_external_opencensus-java-d05056c351f301e52efc07eb6389afd10d3949ca.tar.gz
platform_external_opencensus-java-d05056c351f301e52efc07eb6389afd10d3949ca.tar.bz2
platform_external_opencensus-java-d05056c351f301e52efc07eb6389afd10d3949ca.zip
Adds ZipkinExporter (#620)
This adds `ZipkinExporter` which allows you to report traces to Zipkin. By default, this uses http, but it can also work with Kafka, SQS, etc as you can override the `Sender`.
-rw-r--r--build.gradle3
-rw-r--r--exporters/trace_zipkin/README.md74
-rw-r--r--exporters/trace_zipkin/build.gradle16
-rw-r--r--exporters/trace_zipkin/src/main/java/io/opencensus/exporter/trace/zipkin/ZipkinExporter.java116
-rw-r--r--exporters/trace_zipkin/src/main/java/io/opencensus/exporter/trace/zipkin/ZipkinExporterHandler.java189
-rw-r--r--exporters/trace_zipkin/src/test/java/io/opencensus/exporter/trace/zipkin/ZipkinExporterHandlerTest.java110
-rw-r--r--exporters/trace_zipkin/src/test/java/io/opencensus/exporter/trace/zipkin/ZipkinExporterTest.java57
-rw-r--r--settings.gradle2
8 files changed, 567 insertions, 0 deletions
diff --git a/build.gradle b/build.gradle
index 0048a24a..60ebe354 100644
--- a/build.gradle
+++ b/build.gradle
@@ -108,6 +108,7 @@ subprojects {
guavaVersion = '19.0'
googleAuthVersion = '0.8.0'
googleCloudVersion = '0.24.0-alpha'
+ zipkinReporterVersion = '2.0.0'
libraries = [
auto_value: "com.google.auto.value:auto-value:${autoValueVersion}",
@@ -118,6 +119,8 @@ subprojects {
findbugs_annotations: "com.google.code.findbugs:annotations:${findBugsVersion}",
google_auth: "com.google.auth:google-auth-library-credentials:${googleAuthVersion}",
google_cloud_trace: "com.google.cloud:google-cloud-trace:${googleCloudVersion}",
+ zipkin_reporter: "io.zipkin.reporter2:zipkin-reporter:${zipkinReporterVersion}",
+ zipkin_urlconnection: "io.zipkin.reporter2:zipkin-sender-urlconnection:${zipkinReporterVersion}",
grpc_context: "io.grpc:grpc-context:${grpcVersion}",
grpc_core: "io.grpc:grpc-core:${grpcVersion}",
guava: "com.google.guava:guava:${guavaVersion}",
diff --git a/exporters/trace_zipkin/README.md b/exporters/trace_zipkin/README.md
new file mode 100644
index 00000000..2e2a5e42
--- /dev/null
+++ b/exporters/trace_zipkin/README.md
@@ -0,0 +1,74 @@
+# OpenCensus Zipkin Trace Exporter
+[![Build Status][travis-image]][travis-url] [![Build status][appveyor-image]][appveyor-url] [![Maven Central][maven-image]][maven-url]
+
+The *OpenCensus Zipkin Trace Exporter* is a trace exporter that exports
+data to Zipkin. [Zipkin](http://zipkin.io/) Zipkin is a distributed
+tracing system. It helps gather timing data needed to troubleshoot
+latency problems in microservice architectures. It manages both the
+collection and lookup of this data.
+
+## Quickstart
+
+### Prerequisites
+
+[Zipkin](http://zipkin.io/) stores and queries traces exported by
+applications instrumented with Census. The easiest way to start a zipkin
+server is to paste the below:
+
+```bash
+wget -O zipkin.jar 'https://search.maven.org/remote_content?g=io.zipkin.java&a=zipkin-server&v=LATEST&c=exec'
+java -jar zipkin.jar
+```
+
+
+### Hello Zipkin
+
+#### Add the dependencies to your project
+
+For Maven add to your `pom.xml`:
+```xml
+<dependencies>
+ <dependency>
+ <groupId>io.opencensus</groupId>
+ <artifactId>opencensus-exporter-trace-zipkin</artifactId>
+ <version>0.7.0</version>
+ </dependency>
+ <dependency>
+ <groupId>io.opencensus</groupId>
+ <artifactId>opencensus-impl</artifactId>
+ <version>0.7.0</version>
+ <scope>runtime</scope>
+ </dependency>
+</dependencies>
+```
+
+For Gradle add to your dependencies:
+```groovy
+compile 'io.opencensus:opencensus-exporter-trace-zipkin:0.7.0'
+runtime 'io.opencensus:opencensus-impl:0.7.0'
+```
+
+#### Register the exporter
+
+This will report Zipkin v2 json format to a single server. Alternate
+[senders](https://github.com/openzipkin/zipkin-reporter-java) are available.
+
+```java
+public class MyMainClass {
+ public static void main(String[] args) throws Exception {
+ ZipkinExporter.createAndRegister("http://127.0.0.1:9411/api/v2/spans");
+ // ...
+ }
+}
+```
+
+#### Java Versions
+
+Java 6 or above is required for using this exporter.
+
+[travis-image]: https://travis-ci.org/census-instrumentation/opencensus-java.svg?branch=master
+[travis-url]: https://travis-ci.org/census-instrumentation/opencensus-java
+[appveyor-image]: https://ci.appveyor.com/api/projects/status/hxthmpkxar4jq4be/branch/master?svg=true
+[appveyor-url]: https://ci.appveyor.com/project/instrumentationjavateam/opencensus-java/branch/master
+[maven-image]: https://maven-badges.herokuapp.com/maven-central/io.opencensus/opencensus-exporter-trace-zipkin/badge.svg
+[maven-url]: https://maven-badges.herokuapp.com/maven-central/io.opencensus/opencensus-exporter-trace-zipkin \ No newline at end of file
diff --git a/exporters/trace_zipkin/build.gradle b/exporters/trace_zipkin/build.gradle
new file mode 100644
index 00000000..3a479690
--- /dev/null
+++ b/exporters/trace_zipkin/build.gradle
@@ -0,0 +1,16 @@
+description = 'OpenCensus Trace Zipkin Exporter'
+
+[compileJava, compileTestJava].each() {
+ it.sourceCompatibility = 1.6
+ it.targetCompatibility = 1.6
+}
+
+dependencies {
+ compile project(':opencensus-api'),
+ libraries.zipkin_reporter,
+ libraries.zipkin_urlconnection
+
+ testCompile project(':opencensus-api')
+
+ signature "org.codehaus.mojo.signature:java16:+@signature"
+}
diff --git a/exporters/trace_zipkin/src/main/java/io/opencensus/exporter/trace/zipkin/ZipkinExporter.java b/exporters/trace_zipkin/src/main/java/io/opencensus/exporter/trace/zipkin/ZipkinExporter.java
new file mode 100644
index 00000000..b90abfd4
--- /dev/null
+++ b/exporters/trace_zipkin/src/main/java/io/opencensus/exporter/trace/zipkin/ZipkinExporter.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2017, OpenCensus 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.opencensus.exporter.trace.zipkin;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.opencensus.trace.Tracing;
+import io.opencensus.trace.export.SpanExporter;
+import io.opencensus.trace.export.SpanExporter.Handler;
+import javax.annotation.concurrent.GuardedBy;
+import zipkin2.Span;
+import zipkin2.codec.SpanBytesEncoder;
+import zipkin2.reporter.Sender;
+import zipkin2.reporter.urlconnection.URLConnectionSender;
+
+/**
+ * An OpenCensus span exporter implementation which exports data to Zipkin.
+ *
+ * <p>Example of usage:
+ *
+ * <pre><code>
+ * public static void main(String[] args) {
+ * ZipkinExporter.createAndRegister("http://127.0.0.1:9411/api/v2/spans", "myservicename");
+ * ... // Do work.
+ * }
+ * </code></pre>
+ */
+public final class ZipkinExporter {
+
+ private static final String REGISTER_NAME = ZipkinExporter.class.getName();
+ private static final Object monitor = new Object();
+
+ @GuardedBy("monitor")
+ private static Handler handler = null;
+
+ private ZipkinExporter() {}
+
+ /**
+ * Creates and registers the Zipkin Trace exporter to the OpenCensus library. Only one Zipkin
+ * exporter can be registered at any point.
+ *
+ * @param v2Url Ex http://127.0.0.1:9411/api/v2/spans
+ * @param serviceName the {@link Span#localServiceName() local service name} of the process.
+ * @throws IllegalStateException if a Zipkin exporter is already registered.
+ */
+ public static void createAndRegister(String v2Url, String serviceName) {
+ createAndRegister(SpanBytesEncoder.JSON_V2, URLConnectionSender.create(v2Url), serviceName);
+ }
+
+ /**
+ * Creates and registers the Zipkin Trace exporter to the OpenCensus library. Only one Zipkin
+ * exporter can be registered at any point.
+ *
+ * @param encoder Usually {@link SpanBytesEncoder#JSON_V2}
+ * @param sender Often, but not necessarily an http sender. This could be Kafka or SQS.
+ * @param serviceName the {@link Span#localServiceName() local service name} of the process.
+ * @throws IllegalStateException if a Zipkin exporter is already registered.
+ */
+ public static void createAndRegister(SpanBytesEncoder encoder, Sender sender,
+ String serviceName) {
+ synchronized (monitor) {
+ checkState(handler == null, "Zipkin exporter is already registered.");
+ handler = new ZipkinExporterHandler(encoder, sender, serviceName);
+ register(Tracing.getExportComponent().getSpanExporter(), handler);
+ }
+ }
+
+ /**
+ * Registers the {@code ZipkinExporter}.
+ *
+ * @param spanExporter the instance of the {@code SpanExporter} where this service is registered.
+ */
+ @VisibleForTesting
+ static void register(SpanExporter spanExporter, Handler handler) {
+ spanExporter.registerHandler(REGISTER_NAME, handler);
+ }
+
+ /**
+ * Unregisters the Zipkin Trace exporter from the OpenCensus library.
+ *
+ * @throws IllegalStateException if a Zipkin exporter is not registered.
+ */
+ public static void unregister() {
+ synchronized (monitor) {
+ checkState(handler != null, "Zipkin exporter is not registered.");
+ unregister(Tracing.getExportComponent().getSpanExporter());
+ handler = null;
+ }
+ }
+
+ /**
+ * Unregisters the {@code ZipkinExporter}.
+ *
+ * @param spanExporter the instance of the {@code SpanExporter} from where this service is
+ * unregistered.
+ */
+ @VisibleForTesting
+ static void unregister(SpanExporter spanExporter) {
+ spanExporter.unregisterHandler(REGISTER_NAME);
+ }
+}
diff --git a/exporters/trace_zipkin/src/main/java/io/opencensus/exporter/trace/zipkin/ZipkinExporterHandler.java b/exporters/trace_zipkin/src/main/java/io/opencensus/exporter/trace/zipkin/ZipkinExporterHandler.java
new file mode 100644
index 00000000..96d8352a
--- /dev/null
+++ b/exporters/trace_zipkin/src/main/java/io/opencensus/exporter/trace/zipkin/ZipkinExporterHandler.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2017, OpenCensus 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.opencensus.exporter.trace.zipkin;
+
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import com.google.common.io.BaseEncoding;
+import io.opencensus.common.Function;
+import io.opencensus.common.Functions;
+import io.opencensus.common.Timestamp;
+import io.opencensus.trace.Annotation;
+import io.opencensus.trace.AttributeValue;
+import io.opencensus.trace.NetworkEvent;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.SpanId;
+import io.opencensus.trace.TraceId;
+import io.opencensus.trace.export.SpanData;
+import io.opencensus.trace.export.SpanData.TimedEvent;
+import io.opencensus.trace.export.SpanExporter;
+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.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import zipkin2.Endpoint;
+import zipkin2.Span;
+import zipkin2.codec.SpanBytesEncoder;
+import zipkin2.reporter.Sender;
+
+final class ZipkinExporterHandler extends SpanExporter.Handler {
+
+ static final Logger logger = Logger.getLogger(ZipkinExporterHandler.class.getName());
+
+ private static final String STATUS_CODE = "census.status_code";
+ private static final String STATUS_DESCRIPTION = "census.status_description";
+ private static final Function<Object, String> RETURN_STRING =
+ new Function<Object, String>() {
+ @Override
+ public String apply(Object input) {
+ return input.toString();
+ }
+ };
+ private final SpanBytesEncoder encoder;
+ private final Sender sender;
+ private final Endpoint localEndpoint;
+
+ ZipkinExporterHandler(SpanBytesEncoder encoder, Sender sender, String serviceName) {
+ this.encoder = encoder;
+ this.sender = sender;
+ this.localEndpoint = produceLocalEndpoint(serviceName);
+ }
+
+ /**
+ * Logic borrowed from brave.internal.Platform.produceLocalEndpoint
+ */
+ static Endpoint produceLocalEndpoint(String serviceName) {
+ Endpoint.Builder builder = Endpoint.newBuilder().serviceName(serviceName);
+ try {
+ Enumeration<NetworkInterface> nics = NetworkInterface.getNetworkInterfaces();
+ if (nics == null) {
+ return builder.build();
+ }
+ while (nics.hasMoreElements()) {
+ NetworkInterface nic = nics.nextElement();
+ Enumeration<InetAddress> addresses = nic.getInetAddresses();
+ while (addresses.hasMoreElements()) {
+ InetAddress address = addresses.nextElement();
+ if (address.isSiteLocalAddress()) {
+ builder.ip(address);
+ break;
+ }
+ }
+ }
+ } catch (Exception e) {
+ // don't crash the caller if there was a problem reading nics.
+ if (logger.isLoggable(Level.FINE)) {
+ logger.log(Level.FINE, "error reading nics", e);
+ }
+ }
+ return builder.build();
+ }
+
+ static Span generateSpan(SpanData spanData, Endpoint localEndpoint) {
+ SpanContext context = spanData.getContext();
+ long startTimestamp = toEpochMicros(spanData.getStartTimestamp());
+ long endTimestamp = toEpochMicros(spanData.getEndTimestamp());
+ Span.Builder spanBuilder = Span.newBuilder()
+ .traceId(encodeTraceId(context.getTraceId()))
+ .id(encodeSpanId(context.getSpanId()))
+ .kind(toSpanKind(spanData))
+ .name(spanData.getName())
+ .timestamp(toEpochMicros(spanData.getStartTimestamp()))
+ .duration(endTimestamp - startTimestamp)
+ .localEndpoint(localEndpoint);
+
+ if (spanData.getParentSpanId() != null && spanData.getParentSpanId().isValid()) {
+ spanBuilder.parentId(encodeSpanId(spanData.getParentSpanId()));
+ }
+
+ for (Map.Entry<String, AttributeValue> label :
+ spanData.getAttributes().getAttributeMap().entrySet()) {
+ spanBuilder.putTag(label.getKey(), attributeValueToString(label.getValue()));
+ }
+ spanBuilder.putTag(STATUS_CODE, spanData.getStatus().getCanonicalCode().toString());
+ if (spanData.getStatus().getDescription() != null) {
+ spanBuilder.putTag(STATUS_DESCRIPTION, spanData.getStatus().getDescription());
+ }
+
+ for (TimedEvent<Annotation> annotation : spanData.getAnnotations().getEvents()) {
+ spanBuilder.addAnnotation(
+ toEpochMicros(annotation.getTimestamp()),
+ annotation.getEvent().getDescription()
+ );
+ }
+
+ for (TimedEvent<NetworkEvent> networkEvent : spanData.getNetworkEvents().getEvents()) {
+ spanBuilder.addAnnotation(
+ toEpochMicros(networkEvent.getTimestamp()),
+ networkEvent.getEvent().getType().name()
+ );
+ }
+
+ return spanBuilder.build();
+ }
+
+ private static String encodeTraceId(TraceId traceId) {
+ return BaseEncoding.base16().lowerCase().encode(traceId.getBytes());
+ }
+
+ private static String encodeSpanId(SpanId spanId) {
+ return BaseEncoding.base16().lowerCase().encode(spanId.getBytes());
+ }
+
+ private static Span.Kind toSpanKind(SpanData spanData) {
+ if (Boolean.TRUE.equals(spanData.getHasRemoteParent())) {
+ return Span.Kind.SERVER;
+ }
+
+ // This is a hack because the v2 API does not have SpanKind. When switch to v2 this will be
+ // fixed.
+ if (spanData.getName().startsWith("Sent.")) {
+ return Span.Kind.CLIENT;
+ }
+
+ return null;
+ }
+
+ private static long toEpochMicros(Timestamp timestamp) {
+ return SECONDS.toMicros(timestamp.getSeconds()) + NANOSECONDS.toMicros(timestamp.getNanos());
+ }
+
+ private static String attributeValueToString(AttributeValue attributeValue) {
+ return attributeValue.match(
+ RETURN_STRING, RETURN_STRING, RETURN_STRING, Functions.<String>returnNull());
+ }
+
+ @Override
+ public void export(Collection<SpanData> spanDataList) {
+ List<byte[]> encodedSpans = new ArrayList<byte[]>(spanDataList.size());
+ for (SpanData spanData : spanDataList) {
+ encodedSpans.add(encoder.encode(generateSpan(spanData, localEndpoint)));
+ }
+ try {
+ sender.sendSpans(encodedSpans).execute();
+ } catch (IOException e) {
+ throw new RuntimeException(e); // TODO: should we instead do drop metrics?
+ }
+ }
+}
diff --git a/exporters/trace_zipkin/src/test/java/io/opencensus/exporter/trace/zipkin/ZipkinExporterHandlerTest.java b/exporters/trace_zipkin/src/test/java/io/opencensus/exporter/trace/zipkin/ZipkinExporterHandlerTest.java
new file mode 100644
index 00000000..3d95a795
--- /dev/null
+++ b/exporters/trace_zipkin/src/test/java/io/opencensus/exporter/trace/zipkin/ZipkinExporterHandlerTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2017, OpenCensus 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.opencensus.exporter.trace.zipkin;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.BaseEncoding;
+import io.opencensus.common.Timestamp;
+import io.opencensus.trace.Annotation;
+import io.opencensus.trace.AttributeValue;
+import io.opencensus.trace.Link;
+import io.opencensus.trace.NetworkEvent;
+import io.opencensus.trace.NetworkEvent.Type;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.SpanId;
+import io.opencensus.trace.Status;
+import io.opencensus.trace.TraceId;
+import io.opencensus.trace.TraceOptions;
+import io.opencensus.trace.export.SpanData;
+import io.opencensus.trace.export.SpanData.Attributes;
+import io.opencensus.trace.export.SpanData.Links;
+import io.opencensus.trace.export.SpanData.TimedEvent;
+import io.opencensus.trace.export.SpanData.TimedEvents;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import zipkin2.Endpoint;
+import zipkin2.Span;
+import zipkin2.Span.Kind;
+
+/**
+ * Unit tests for {@link ZipkinExporterHandler}.
+ */
+@RunWith(JUnit4.class)
+public class ZipkinExporterHandlerTest {
+
+ @Test
+ public void generateSpan() {
+ Endpoint localEndpoint = Endpoint.newBuilder().serviceName("tweetiebird").build();
+ String traceId = "d239036e7d5cec116b562147388b35bf";
+ String spanId = "9cc1e3049173be09";
+ String parentId = "8b03ab423da481c5";
+ Map<String, AttributeValue> attributes = Collections.emptyMap();
+ List<TimedEvent<Annotation>> annotations = Collections.emptyList();
+ List<TimedEvent<NetworkEvent>> networkEvents = ImmutableList.of(
+ TimedEvent.create(
+ Timestamp.create(1505855799, 433901068),
+ NetworkEvent.builder(Type.RECV, 0).setCompressedMessageSize(7).build()
+ ),
+ TimedEvent.create(
+ Timestamp.create(1505855799, 459486280),
+ NetworkEvent.builder(Type.SENT, 0).setCompressedMessageSize(13).build()
+ )
+ );
+ SpanData data = SpanData.create(
+ SpanContext.create(
+ // TODO SpanId.fromLowerBase16
+ TraceId.fromBytes(BaseEncoding.base16().lowerCase().decode(traceId)),
+ // TODO SpanId.fromLowerBase16
+ SpanId.fromBytes(BaseEncoding.base16().lowerCase().decode(spanId)),
+ TraceOptions.fromBytes(new byte[]{1} /* sampled */)
+ ),
+ // TODO SpanId.fromLowerBase16
+ SpanId.fromBytes(BaseEncoding.base16().lowerCase().decode(parentId)),
+ true, /* hasRemoteParent */
+ "Recv.helloworld.Greeter.SayHello", /* name */
+ Timestamp.create(1505855794, 194009601) /* startTimestamp */,
+ Attributes.create(attributes, 0 /* droppedAttributesCount */),
+ TimedEvents.create(annotations, 0 /* droppedEventsCount */),
+ TimedEvents.create(networkEvents, 0 /* droppedEventsCount */),
+ Links.create(Collections.<Link>emptyList(), 0 /* droppedLinksCount */),
+ null, /* childSpanCount */
+ Status.OK,
+ Timestamp.create(1505855799, 465726528) /* endTimestamp */
+ );
+
+ assertThat(ZipkinExporterHandler.generateSpan(data, localEndpoint)).isEqualTo(Span.newBuilder()
+ .traceId(traceId)
+ .parentId(parentId)
+ .id(spanId)
+ .kind(Kind.SERVER)
+ .name(data.getName())
+ .timestamp(1505855794000000L + 194009601L / 1000)
+ .duration((1505855799000000L + 465726528L / 1000) - (1505855794000000L + 194009601L / 1000))
+ .localEndpoint(localEndpoint)
+ .addAnnotation(1505855799000000L + 433901068L / 1000, "RECV")
+ .addAnnotation(1505855799000000L + 459486280L / 1000, "SENT")
+ .putTag("census.status_code", "OK")
+ .build()
+ );
+ }
+}
diff --git a/exporters/trace_zipkin/src/test/java/io/opencensus/exporter/trace/zipkin/ZipkinExporterTest.java b/exporters/trace_zipkin/src/test/java/io/opencensus/exporter/trace/zipkin/ZipkinExporterTest.java
new file mode 100644
index 00000000..d43dc205
--- /dev/null
+++ b/exporters/trace_zipkin/src/test/java/io/opencensus/exporter/trace/zipkin/ZipkinExporterTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2017, OpenCensus 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.opencensus.exporter.trace.zipkin;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.verify;
+
+import io.opencensus.trace.export.SpanExporter;
+import io.opencensus.trace.export.SpanExporter.Handler;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link ZipkinExporter}.
+ */
+@RunWith(JUnit4.class)
+public class ZipkinExporterTest {
+ @Mock
+ private SpanExporter spanExporter;
+ @Mock
+ private Handler handler;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void registerUnregisterZipkinExporter() {
+ ZipkinExporter.register(spanExporter, handler);
+ verify(spanExporter)
+ .registerHandler(
+ eq("io.opencensus.exporter.trace.zipkin.ZipkinExporter"), same(handler));
+ ZipkinExporter.unregister(spanExporter);
+ verify(spanExporter)
+ .unregisterHandler(eq("io.opencensus.exporter.trace.zipkin.ZipkinExporter"));
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index c3f164e3..abdebed8 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -7,6 +7,7 @@ include ":opencensus-impl"
include ":opencensus-testing"
include ":opencensus-exporter-trace-logging"
include ":opencensus-exporter-trace-stackdriver"
+include ":opencensus-exporter-trace-zipkin"
include ":core"
include ":core_impl"
include ":core_impl_java"
@@ -23,6 +24,7 @@ project(':opencensus-contrib-agent').projectDir = "$rootDir/contrib/agent" as Fi
project(':opencensus-contrib-grpc-util').projectDir = "$rootDir/contrib/grpc_util" as File
project(':opencensus-exporter-trace-logging').projectDir = "$rootDir/exporters/trace_logging" as File
project(':opencensus-exporter-trace-stackdriver').projectDir = "$rootDir/exporters/trace_stackdriver" as File
+project(':opencensus-exporter-trace-zipkin').projectDir = "$rootDir/exporters/trace_zipkin" as File
// Java8 projects only
if (JavaVersion.current().isJava8Compatible()) {