aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDino Oliva <dinooliva@users.noreply.github.com>2018-09-05 12:09:44 -0700
committerGitHub <noreply@github.com>2018-09-05 12:09:44 -0700
commit30c10cc77cd3a6d9583c2339ade689d06585aba4 (patch)
tree147471d49a23a7fbefc65330b8c2026dc287d853
parent0ecdfd01dbe325690c60b124709cba4b04167172 (diff)
downloadopencensus-java-30c10cc77cd3a6d9583c2339ade689d06585aba4.tar.gz
Spring sleuth (#1378)
* Initial import of OpenCensus/Sleuth integration. * Minor fixes for integration with master. * Adds a README. * Removes hardcoded dependencies in build file. * Adds contrib to spring_sleuth file path. * Updates package names appropriately based on file renames. * Fixes build paths. * Fixes for build checks. * Mark as experimental. * Minor fixes for build files (space vs tab). * Minor fixes for build files (space vs tab). * Update README to absolve Spring Sleuth team of any responsibility for this spring_sleuth plugin. * Minor fixes for import control (space vs tab). * Tag public classes @since 0.16 * Adds javadoc for public methods not already documented. * Updates OpenCensusSleuthTracer to pass nullness checks. * Fixes for formatting issues. * Fixes for typos. * Suppresses the check return value warning added by gRPC. * Suppresses the check return value warning added by gRPC.
-rw-r--r--RELEASING.md1
-rw-r--r--all/build.gradle2
-rw-r--r--build.gradle6
-rw-r--r--buildscripts/import-control.xml12
-rw-r--r--checker-framework/stubs/org-springframework-cloud-sleuth.astub19
-rw-r--r--checker-framework/stubs/org-springframework-cloud-sleuth.log.astub9
-rw-r--r--contrib/spring_sleuth/README.md52
-rw-r--r--contrib/spring_sleuth/build.gradle21
-rw-r--r--contrib/spring_sleuth/src/main/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthAutoConfiguration.java63
-rw-r--r--contrib/spring_sleuth/src/main/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthProperties.java42
-rw-r--r--contrib/spring_sleuth/src/main/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthSpan.java79
-rw-r--r--contrib/spring_sleuth/src/main/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthSpanContextHolder.java139
-rw-r--r--contrib/spring_sleuth/src/main/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthTracer.java329
-rw-r--r--contrib/spring_sleuth/src/main/resources/META-INF/additional-spring-configuration-metadata.json0
-rw-r--r--contrib/spring_sleuth/src/main/resources/META-INF/spring.factories7
-rw-r--r--contrib/spring_sleuth/src/test/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthSpanContextHolderTest.java151
-rw-r--r--contrib/spring_sleuth/src/test/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthSpanTest.java66
-rw-r--r--contrib/spring_sleuth/src/test/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthTracerTest.java185
-rw-r--r--settings.gradle2
19 files changed, 1185 insertions, 0 deletions
diff --git a/RELEASING.md b/RELEASING.md
index c1f2bc24..393bb3a6 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -228,6 +228,7 @@ $ README_FILES=(
contrib/log_correlation/stackdriver/README.md
contrib/monitored_resource_util/README.md
contrib/spring/README.md
+ contrib/spring_sleuth/README.md
contrib/zpages/README.md
exporters/stats/prometheus/README.md
exporters/stats/signalfx/README.md
diff --git a/all/build.gradle b/all/build.gradle
index e0bbab12..97e98ff1 100644
--- a/all/build.gradle
+++ b/all/build.gradle
@@ -22,6 +22,7 @@ def subprojects = [
project(':opencensus-contrib-log-correlation-stackdriver'),
project(':opencensus-contrib-monitored-resource-util'),
project(':opencensus-contrib-spring'),
+ project(':opencensus-contrib-spring-sleuth'),
project(':opencensus-contrib-zpages'),
project(':opencensus-exporter-trace-logging'),
project(':opencensus-exporter-trace-stackdriver'),
@@ -46,6 +47,7 @@ def subprojects_javadoc = [
project(':opencensus-contrib-log-correlation-stackdriver'),
project(':opencensus-contrib-monitored-resource-util'),
project(':opencensus-contrib-spring'),
+ project(':opencensus-contrib-spring-sleuth'),
project(':opencensus-contrib-zpages'),
project(':opencensus-exporter-trace-logging'),
project(':opencensus-exporter-trace-stackdriver'),
diff --git a/build.gradle b/build.gradle
index 14c52111..b0fdc30c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -156,6 +156,8 @@ subprojects {
googleCloudGaVersion = '1.40.0'
log4jVersion = '2.11.1'
signalfxVersion = '0.0.39'
+ springBootVersion = '1.5.15.RELEASE'
+ springCloudVersion = '1.3.4.RELEASE'
springVersion = '4.3.12.RELEASE'
prometheusVersion = '0.4.0'
protobufVersion = '3.5.1'
@@ -186,6 +188,9 @@ subprojects {
jsr305: "com.google.code.findbugs:jsr305:${findBugsJsr305Version}",
signalfx_java: "com.signalfx.public:signalfx-java:${signalfxVersion}",
spring_aspects: "org.springframework:spring-aspects:${springVersion}",
+ spring_boot_starter_web: "org.springframework.boot:spring-boot-starter-web:${springBootVersion}",
+ spring_cloud_build: "org.springframework.cloud:spring-cloud-build:${springCloudVersion}",
+ spring_cloud_starter_sleuth: "org.springframework.cloud:spring-cloud-starter-sleuth:${springCloudVersion}",
spring_context: "org.springframework:spring-context:${springVersion}",
spring_context_support: "org.springframework:spring-context-support:${springVersion}",
prometheus_simpleclient: "io.prometheus:simpleclient:${prometheusVersion}",
@@ -386,6 +391,7 @@ subprojects {
'opencensus-contrib-log-correlation-stackdriver',
'opencensus-contrib-monitored-resource-util',
'opencensus-contrib-spring',
+ 'opencensus-contrib-spring-sleuth',
'opencensus-contrib-zpages',
'opencensus-exporter-stats-prometheus',
'opencensus-exporter-stats-signalfx',
diff --git a/buildscripts/import-control.xml b/buildscripts/import-control.xml
index d0000c57..ba04f07e 100644
--- a/buildscripts/import-control.xml
+++ b/buildscripts/import-control.xml
@@ -108,6 +108,18 @@ General guidelines on imports:
<allow pkg="org.aspectj.lang.annotation"/>
<allow pkg="org.aspectj.lang.reflect"/>
<allow pkg="org.springframework.beans.factory.annotation"/>
+ <subpackage name="sleuth">
+ <allow pkg="io.opencensus.trace"/>
+ <allow pkg="org.apache.commons.logging"/>
+ <allow pkg="org.springframework.beans.factory.annotation"/>
+ <allow pkg="org.springframework.beans.factory.config"/>
+ <allow pkg="org.springframework.boot.autoconfigure"/>
+ <allow pkg="org.springframework.boot.context"/>
+ <allow pkg="org.springframework.context.annotation"/>
+ <allow pkg="org.springframework.boot.context.properties"/>
+ <allow pkg="org.springframework.cloud.sleuth"/>
+ <allow pkg="org.springframework.core"/>
+ </subpackage>
</subpackage>
<subpackage name="zpages">
<allow pkg="com.sun.net.httpserver"/>
diff --git a/checker-framework/stubs/org-springframework-cloud-sleuth.astub b/checker-framework/stubs/org-springframework-cloud-sleuth.astub
new file mode 100644
index 00000000..61d2fa11
--- /dev/null
+++ b/checker-framework/stubs/org-springframework-cloud-sleuth.astub
@@ -0,0 +1,19 @@
+package org.springframework.cloud.sleuth;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.springframework.cloud.sleuth.Sampler;
+import org.springframework.cloud.sleuth.Span;
+
+interface Tracer {
+ @Nullable Span close(@Nullable Span span);
+ @Nullable Span continueSpan(@Nullable Span span);
+ @Nullable Span createSpan(String name);
+ @Nullable Span createSpan(String name, @Nullable Sampler sampler);
+ @Nullable Span createSpan(String name, @Nullable Span parent);
+ @Nullable Span detach(@Nullable Span span);
+ @Nullable Span getCurrentSpan();
+}
+
+class Span {
+ Span (Span span, @Nullable Span parent);
+}
diff --git a/checker-framework/stubs/org-springframework-cloud-sleuth.log.astub b/checker-framework/stubs/org-springframework-cloud-sleuth.log.astub
new file mode 100644
index 00000000..9497f6f2
--- /dev/null
+++ b/checker-framework/stubs/org-springframework-cloud-sleuth.log.astub
@@ -0,0 +1,9 @@
+package org.springframework.cloud.sleuth.log;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.springframework.cloud.sleuth.Span;
+
+interface SpanLogger {
+ void logStartedSpan(@Nullable Span parent, Span span);
+ void logStoppedSpan(@Nullable Span parent, Span span);
+}
diff --git a/contrib/spring_sleuth/README.md b/contrib/spring_sleuth/README.md
new file mode 100644
index 00000000..a05eccb4
--- /dev/null
+++ b/contrib/spring_sleuth/README.md
@@ -0,0 +1,52 @@
+# OpenCensus Spring Sleuth
+[![Build Status][travis-image]][travis-url]
+[![Windows Build Status][appveyor-image]][appveyor-url]
+[![Maven Central][maven-image]][maven-url]
+
+The *OpenCensus Spring Sleuth for Java* is a library for automatically
+propagating the OpenCensus trace context when working with [Spring Sleuth][spring-sleuth-url].
+
+This is an __experimental component__, please bring feedback to
+https://gitter.im/census-instrumentation/Lobby not the usual
+sleuth channel https://gitter.im/spring-cloud/spring-cloud-sleuth.
+
+This version is compatible with [Spring Boot 1.5.x][spring-boot-1.5-url].
+
+## Quickstart
+
+### Add the dependencies to your project
+
+For Maven add to your `pom.xml`:
+```xml
+<dependencies>
+ <dependency>
+ <groupId>io.opencensus</groupId>
+ <artifactId>opencensus-contrib-spring-sleuth</artifactId>
+ <version>0.16.0</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-build</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-starter-sleuth</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+</dependencies>
+```
+
+For Gradle add to your dependencies:
+```gradle
+compile 'io.opencensus:opencensus-contrib-spring-sleuth:0.16.0'
+```
+
+[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/opencensusjavateam/opencensus-java/branch/master
+[maven-image]: https://maven-badges.herokuapp.com/maven-central/io.opencensus/opencensus-contrib-spring-sleuth/badge.svg
+[maven-url]: https://maven-badges.herokuapp.com/maven-central/io.opencensus/opencensus-contrib-spring-sleuth
+[spring-boot-1.5-url]: https://github.com/spring-projects/spring-boot/tree/1.5.x
+[spring-sleuth-url]: https://github.com/spring-cloud/spring-cloud-sleuth
diff --git a/contrib/spring_sleuth/build.gradle b/contrib/spring_sleuth/build.gradle
new file mode 100644
index 00000000..53ff1c04
--- /dev/null
+++ b/contrib/spring_sleuth/build.gradle
@@ -0,0 +1,21 @@
+description = 'OpenCensus Spring Sleuth'
+
+apply plugin: 'java'
+
+[compileJava, compileTestJava].each() {
+ it.sourceCompatibility = 1.6
+ it.targetCompatibility = 1.6
+}
+
+dependencies {
+ compile project(':opencensus-api'),
+ libraries.spring_boot_starter_web,
+ libraries.spring_cloud_build,
+ libraries.spring_cloud_starter_sleuth
+
+ testCompile project(':opencensus-impl'),
+ project(':opencensus-testing'),
+ libraries.spring_test
+
+ signature "org.codehaus.mojo.signature:java16:+@signature"
+}
diff --git a/contrib/spring_sleuth/src/main/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthAutoConfiguration.java b/contrib/spring_sleuth/src/main/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthAutoConfiguration.java
new file mode 100644
index 00000000..3a11cc3a
--- /dev/null
+++ b/contrib/spring_sleuth/src/main/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthAutoConfiguration.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2018, 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.contrib.spring.sleuth;
+
+import io.opencensus.common.ExperimentalApi;
+import java.util.Random;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cloud.sleuth.Sampler;
+import org.springframework.cloud.sleuth.SpanNamer;
+import org.springframework.cloud.sleuth.SpanReporter;
+import org.springframework.cloud.sleuth.TraceKeys;
+import org.springframework.cloud.sleuth.Tracer;
+import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
+import org.springframework.cloud.sleuth.log.SpanLogger;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.context.annotation.Role;
+
+/**
+ * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration} that
+ * allows inter-operation between Sleuth(Brave) and OpenCensus.
+ *
+ * @since 0.16
+ */
+@ExperimentalApi
+@Configuration
+@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+@ConditionalOnProperty(name = "spring.opencensus.sleuth.enabled", matchIfMissing = true)
+@AutoConfigureBefore(TraceAutoConfiguration.class)
+@EnableConfigurationProperties(OpenCensusSleuthProperties.class)
+public class OpenCensusSleuthAutoConfiguration {
+
+ @Bean
+ @Primary
+ Tracer openCensusSleuthTracer(
+ Sampler sampler,
+ Random random,
+ SpanNamer spanNamer,
+ SpanLogger spanLogger,
+ SpanReporter spanReporter,
+ TraceKeys traceKeys) {
+ return new OpenCensusSleuthTracer(
+ sampler, random, spanNamer, spanLogger, spanReporter, traceKeys, /* traceId128= */ true);
+ }
+}
diff --git a/contrib/spring_sleuth/src/main/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthProperties.java b/contrib/spring_sleuth/src/main/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthProperties.java
new file mode 100644
index 00000000..595f4ae0
--- /dev/null
+++ b/contrib/spring_sleuth/src/main/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthProperties.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2018, 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.contrib.spring.sleuth;
+
+import io.opencensus.common.ExperimentalApi;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * Sleuth annotation settings.
+ *
+ * @since 0.16
+ */
+@ExperimentalApi
+@ConfigurationProperties("spring.opencensus.sleuth")
+public class OpenCensusSleuthProperties {
+
+ private boolean enabled = true;
+
+ /** Returns whether OpenCensus trace propagation is enabled. */
+ public boolean isEnabled() {
+ return this.enabled;
+ }
+
+ /** Enables OpenCensus trace propagation. */
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+}
diff --git a/contrib/spring_sleuth/src/main/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthSpan.java b/contrib/spring_sleuth/src/main/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthSpan.java
new file mode 100644
index 00000000..7477f98e
--- /dev/null
+++ b/contrib/spring_sleuth/src/main/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthSpan.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2018, 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.contrib.spring.sleuth;
+
+import io.opencensus.common.ExperimentalApi;
+import io.opencensus.trace.Annotation;
+import io.opencensus.trace.AttributeValue;
+import io.opencensus.trace.EndSpanOptions;
+import io.opencensus.trace.Link;
+import io.opencensus.trace.Span;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.SpanId;
+import io.opencensus.trace.TraceId;
+import io.opencensus.trace.TraceOptions;
+import java.nio.ByteBuffer;
+import java.util.EnumSet;
+import java.util.Map;
+
+/**
+ * Implementaion of Span that is created from a Sleuth Span.
+ *
+ * @since 0.16
+ */
+@ExperimentalApi
+public class OpenCensusSleuthSpan extends Span {
+
+ private static final EnumSet<Options> recordOptions = EnumSet.of(Options.RECORD_EVENTS);
+ private static final EnumSet<Options> notRecordOptions = EnumSet.noneOf(Options.class);
+
+ private static final TraceOptions sampledOptions =
+ TraceOptions.builder().setIsSampled(true).build();
+ private static final TraceOptions notSampledOptions =
+ TraceOptions.builder().setIsSampled(false).build();
+
+ OpenCensusSleuthSpan(org.springframework.cloud.sleuth.Span span) {
+ super(
+ fromSleuthSpan(span),
+ Boolean.TRUE.equals(span.isExportable()) ? recordOptions : notRecordOptions);
+ }
+
+ @Override
+ public void addAnnotation(String s, Map<String, AttributeValue> map) {}
+
+ @Override
+ public void addAnnotation(Annotation annotation) {}
+
+ @Override
+ public void addLink(Link link) {}
+
+ @Override
+ public void end(EndSpanOptions endSpanOptions) {}
+
+ // TODO: upgrade to new SpanContext.create() once it has been released.
+ @SuppressWarnings("deprecation")
+ private static SpanContext fromSleuthSpan(org.springframework.cloud.sleuth.Span span) {
+ return SpanContext.create(
+ TraceId.fromBytes(
+ ByteBuffer.allocate(TraceId.SIZE)
+ .putLong(span.getTraceIdHigh())
+ .putLong(span.getTraceId())
+ .array()),
+ SpanId.fromBytes(ByteBuffer.allocate(SpanId.SIZE).putLong(span.getSpanId()).array()),
+ Boolean.TRUE.equals(span.isExportable()) ? sampledOptions : notSampledOptions);
+ }
+}
diff --git a/contrib/spring_sleuth/src/main/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthSpanContextHolder.java b/contrib/spring_sleuth/src/main/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthSpanContextHolder.java
new file mode 100644
index 00000000..05238cf9
--- /dev/null
+++ b/contrib/spring_sleuth/src/main/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthSpanContextHolder.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2018, 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.contrib.spring.sleuth;
+
+import io.grpc.Context;
+import io.opencensus.common.ExperimentalApi;
+import io.opencensus.trace.unsafe.ContextUtils;
+import org.apache.commons.logging.Log;
+import org.springframework.cloud.sleuth.Span;
+import org.springframework.core.NamedThreadLocal;
+
+/*>>>
+ import org.checkerframework.checker.nullness.qual.Nullable;
+*/
+
+/** Inspired by the Sleuth's {@code SpanContextHolder}. */
+@ExperimentalApi
+final class OpenCensusSleuthSpanContextHolder {
+ private static final Log log =
+ org.apache.commons.logging.LogFactory.getLog(OpenCensusSleuthSpanContextHolder.class);
+ private static final ThreadLocal</*@Nullable*/ SpanContext> CURRENT_SPAN =
+ new NamedThreadLocal</*@Nullable*/ SpanContext>("Trace Context");
+
+ // Get the current span out of the thread context.
+ @javax.annotation.Nullable
+ static Span getCurrentSpan() {
+ SpanContext currentSpanContext = CURRENT_SPAN.get();
+ return currentSpanContext != null ? currentSpanContext.span : null;
+ }
+
+ // Set the current span in the thread context
+ static void setCurrentSpan(Span span) {
+ if (log.isTraceEnabled()) {
+ log.trace("Setting current span " + span);
+ }
+ push(span, /* autoClose= */ false);
+ }
+
+ // Remove all thread context relating to spans (useful for testing).
+ // See close() for a better alternative in instrumetation
+ @SuppressWarnings("CheckReturnValue")
+ static void removeCurrentSpan() {
+ CURRENT_SPAN.remove();
+ Context.current().withValue(ContextUtils.CONTEXT_SPAN_KEY, null).attach();
+ }
+
+ // Check if there is already a span in the current thread.
+ static boolean isTracing() {
+ return CURRENT_SPAN.get() != null;
+ }
+
+ // Close the current span and all parents that can be auto closed. On every iteration a function
+ // will be applied on the closed Span.
+ static void close(SpanFunction spanFunction) {
+ SpanContext current = CURRENT_SPAN.get();
+ removeCurrentSpan();
+ while (current != null) {
+ spanFunction.apply(current.span);
+ current = current.parent;
+ if (current != null) {
+ if (!current.autoClose) {
+ setSpanContext(current);
+ current = null;
+ }
+ }
+ }
+ }
+
+ // Close the current span and all parents that can be auto closed.
+ static void close() {
+ close(NO_OP_FUNCTION);
+ }
+
+ /**
+ * Push a span into the thread context, with the option to have it auto close if any child spans
+ * are themselves closed. Use autoClose=true if you start a new span with a parent that wasn't
+ * already in thread context.
+ */
+ static void push(Span span, boolean autoClose) {
+ if (isCurrent(span)) {
+ return;
+ }
+ setSpanContext(new SpanContext(span, autoClose));
+ }
+
+ interface SpanFunction {
+ void apply(Span span);
+ }
+
+ private static final SpanFunction NO_OP_FUNCTION =
+ new SpanFunction() {
+ @Override
+ public void apply(Span span) {}
+ };
+
+ @SuppressWarnings("CheckReturnValue")
+ private static void setSpanContext(SpanContext spanContext) {
+ CURRENT_SPAN.set(spanContext);
+ Context.current().withValue(ContextUtils.CONTEXT_SPAN_KEY, spanContext.ocSpan).attach();
+ }
+
+ private static boolean isCurrent(Span span) {
+ if (span == null) {
+ return false;
+ }
+ SpanContext currentSpanContext = CURRENT_SPAN.get();
+ return currentSpanContext != null && span.equals(currentSpanContext.span);
+ }
+
+ private static class SpanContext {
+ final Span span;
+ final boolean autoClose;
+ @javax.annotation.Nullable final SpanContext parent;
+ final OpenCensusSleuthSpan ocSpan;
+
+ private SpanContext(Span span, boolean autoClose) {
+ this.span = span;
+ this.autoClose = autoClose;
+ this.parent = CURRENT_SPAN.get();
+ this.ocSpan = new OpenCensusSleuthSpan(span);
+ }
+ }
+
+ private OpenCensusSleuthSpanContextHolder() {}
+}
diff --git a/contrib/spring_sleuth/src/main/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthTracer.java b/contrib/spring_sleuth/src/main/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthTracer.java
new file mode 100644
index 00000000..68d59d71
--- /dev/null
+++ b/contrib/spring_sleuth/src/main/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthTracer.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright 2018, 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.contrib.spring.sleuth;
+
+import io.opencensus.common.ExperimentalApi;
+import java.util.Random;
+import java.util.concurrent.Callable;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.cloud.sleuth.Sampler;
+import org.springframework.cloud.sleuth.Span;
+import org.springframework.cloud.sleuth.SpanNamer;
+import org.springframework.cloud.sleuth.SpanReporter;
+import org.springframework.cloud.sleuth.TraceKeys;
+import org.springframework.cloud.sleuth.Tracer;
+import org.springframework.cloud.sleuth.instrument.async.SpanContinuingTraceCallable;
+import org.springframework.cloud.sleuth.instrument.async.SpanContinuingTraceRunnable;
+import org.springframework.cloud.sleuth.log.SpanLogger;
+import org.springframework.cloud.sleuth.util.ExceptionUtils;
+import org.springframework.cloud.sleuth.util.SpanNameUtil;
+
+/*>>>
+ import org.checkerframework.checker.nullness.qual.Nullable;
+*/
+
+/**
+ * Sleuth Tracer that keeps a synchronized OpenCensus Span. This class is based on Sleuth's {@code
+ * DefaultTracer}.
+ *
+ * @since 0.16
+ */
+@ExperimentalApi
+public class OpenCensusSleuthTracer implements Tracer {
+ private static final Log log = LogFactory.getLog(OpenCensusSleuthTracer.class);
+ private final Sampler defaultSampler;
+ private final Random random;
+ private final SpanNamer spanNamer;
+ private final SpanLogger spanLogger;
+ private final SpanReporter spanReporter;
+ private final TraceKeys traceKeys;
+ private final boolean traceId128;
+
+ /** Basic constructor holding components for implementing Sleuth's {@link Tracer} interface. */
+ public OpenCensusSleuthTracer(
+ Sampler defaultSampler,
+ Random random,
+ SpanNamer spanNamer,
+ SpanLogger spanLogger,
+ SpanReporter spanReporter,
+ TraceKeys traceKeys) {
+ this(
+ defaultSampler,
+ random,
+ spanNamer,
+ spanLogger,
+ spanReporter,
+ traceKeys,
+ /* traceId128= */ false);
+ }
+
+ /** Basic constructor holding components for implementing Sleuth's {@link Tracer} interface. */
+ public OpenCensusSleuthTracer(
+ Sampler defaultSampler,
+ Random random,
+ SpanNamer spanNamer,
+ SpanLogger spanLogger,
+ SpanReporter spanReporter,
+ TraceKeys traceKeys,
+ boolean traceId128) {
+ this.defaultSampler = defaultSampler;
+ this.random = random;
+ this.spanNamer = spanNamer;
+ this.spanLogger = spanLogger;
+ this.spanReporter = spanReporter;
+ this.traceId128 = traceId128;
+ this.traceKeys = traceKeys != null ? traceKeys : new TraceKeys();
+ }
+
+ @Override
+ @javax.annotation.Nullable
+ public Span createSpan(String name, /*@Nullable*/ Span parent) {
+ if (parent == null) {
+ return createSpan(name);
+ }
+ return continueSpan(createChild(parent, name));
+ }
+
+ @Override
+ @javax.annotation.Nullable
+ public Span createSpan(String name) {
+ return this.createSpan(name, this.defaultSampler);
+ }
+
+ @Override
+ @javax.annotation.Nullable
+ public Span createSpan(String name, /*@Nullable*/ Sampler sampler) {
+ String shortenedName = SpanNameUtil.shorten(name);
+ Span span;
+ if (isTracing()) {
+ span = createChild(getCurrentSpan(), shortenedName);
+ } else {
+ long id = createId();
+ span =
+ Span.builder()
+ .name(shortenedName)
+ .traceIdHigh(this.traceId128 ? createTraceIdHigh() : 0L)
+ .traceId(id)
+ .spanId(id)
+ .build();
+ if (sampler == null) {
+ sampler = this.defaultSampler;
+ }
+ span = sampledSpan(span, sampler);
+ this.spanLogger.logStartedSpan(null, span);
+ }
+ return continueSpan(span);
+ }
+
+ @Override
+ @javax.annotation.Nullable
+ public Span detach(/*@Nullable*/ Span span) {
+ if (span == null) {
+ return null;
+ }
+ Span current = OpenCensusSleuthSpanContextHolder.getCurrentSpan();
+ if (current == null) {
+ if (log.isTraceEnabled()) {
+ log.trace(
+ "Span in the context is null so something has already detached the span. "
+ + "Won't do anything about it");
+ }
+ return null;
+ }
+ if (!span.equals(current)) {
+ ExceptionUtils.warn(
+ "Tried to detach trace span but "
+ + "it is not the current span: "
+ + span
+ + ". You may have forgotten to close or detach "
+ + current);
+ } else {
+ OpenCensusSleuthSpanContextHolder.removeCurrentSpan();
+ }
+ return span.getSavedSpan();
+ }
+
+ @Override
+ @javax.annotation.Nullable
+ public Span close(/*@Nullable*/ Span span) {
+ if (span == null) {
+ return null;
+ }
+ final Span savedSpan = span.getSavedSpan();
+ Span current = OpenCensusSleuthSpanContextHolder.getCurrentSpan();
+ if (current == null || !span.equals(current)) {
+ ExceptionUtils.warn(
+ "Tried to close span but it is not the current span: "
+ + span
+ + ". You may have forgotten to close or detach "
+ + current);
+ } else {
+ span.stop();
+ if (savedSpan != null && span.getParents().contains(savedSpan.getSpanId())) {
+ this.spanReporter.report(span);
+ this.spanLogger.logStoppedSpan(savedSpan, span);
+ } else {
+ if (!span.isRemote()) {
+ this.spanReporter.report(span);
+ this.spanLogger.logStoppedSpan(null, span);
+ }
+ }
+ OpenCensusSleuthSpanContextHolder.close(
+ new OpenCensusSleuthSpanContextHolder.SpanFunction() {
+ @Override
+ public void apply(Span closedSpan) {
+ // Note: hasn't this already been done?
+ OpenCensusSleuthTracer.this.spanLogger.logStoppedSpan(savedSpan, closedSpan);
+ }
+ });
+ }
+ return savedSpan;
+ }
+
+ Span createChild(/*@Nullable*/ Span parent, String name) {
+ String shortenedName = SpanNameUtil.shorten(name);
+ long id = createId();
+ if (parent == null) {
+ Span span =
+ Span.builder()
+ .name(shortenedName)
+ .traceIdHigh(this.traceId128 ? createTraceIdHigh() : 0L)
+ .traceId(id)
+ .spanId(id)
+ .build();
+ span = sampledSpan(span, this.defaultSampler);
+ this.spanLogger.logStartedSpan(null, span);
+ return span;
+ } else {
+ if (!isTracing()) {
+ OpenCensusSleuthSpanContextHolder.push(parent, /* autoClose= */ true);
+ }
+ Span span =
+ Span.builder()
+ .name(shortenedName)
+ .traceIdHigh(parent.getTraceIdHigh())
+ .traceId(parent.getTraceId())
+ .parent(parent.getSpanId())
+ .spanId(id)
+ .processId(parent.getProcessId())
+ .savedSpan(parent)
+ .exportable(parent.isExportable())
+ .baggage(parent.getBaggage())
+ .build();
+ this.spanLogger.logStartedSpan(parent, span);
+ return span;
+ }
+ }
+
+ private static Span sampledSpan(Span span, Sampler sampler) {
+ if (!sampler.isSampled(span)) {
+ // Copy everything, except set exportable to false
+ return Span.builder()
+ .begin(span.getBegin())
+ .traceIdHigh(span.getTraceIdHigh())
+ .traceId(span.getTraceId())
+ .spanId(span.getSpanId())
+ .name(span.getName())
+ .exportable(false)
+ .build();
+ }
+ return span;
+ }
+
+ // Encodes a timestamp into the upper 32-bits, so that it can be converted to an Amazon trace ID.
+ // For example, an Amazon trace ID is composed of the following:
+ // |-- 32 bits for epoch seconds -- | -- 96 bits for random data -- |
+ //
+ // To support this, Span#getTraceIdHigh() holds the epoch seconds and first 32 random bits: and
+ // Span#getTraceId() holds the remaining 64 random bits.
+ private long createTraceIdHigh() {
+ long epochSeconds = System.currentTimeMillis() / 1000;
+ int random = this.random.nextInt();
+ return (epochSeconds & 0xffffffffL) << 32 | (random & 0xffffffffL);
+ }
+
+ private long createId() {
+ return this.random.nextLong();
+ }
+
+ @Override
+ @javax.annotation.Nullable
+ public Span continueSpan(/*@Nullable*/ Span span) {
+ if (span != null) {
+ this.spanLogger.logContinuedSpan(span);
+ } else {
+ return null;
+ }
+ Span newSpan = createContinuedSpan(span, OpenCensusSleuthSpanContextHolder.getCurrentSpan());
+ OpenCensusSleuthSpanContextHolder.setCurrentSpan(newSpan);
+ return newSpan;
+ }
+
+ @SuppressWarnings("deprecation")
+ private static Span createContinuedSpan(Span span, /*@Nullable*/ Span saved) {
+ if (saved == null && span.getSavedSpan() != null) {
+ saved = span.getSavedSpan();
+ }
+ return new Span(span, saved);
+ }
+
+ @Override
+ @javax.annotation.Nullable
+ public Span getCurrentSpan() {
+ return OpenCensusSleuthSpanContextHolder.getCurrentSpan();
+ }
+
+ @Override
+ public boolean isTracing() {
+ return OpenCensusSleuthSpanContextHolder.isTracing();
+ }
+
+ @Override
+ public void addTag(String key, String value) {
+ Span s = getCurrentSpan();
+ if (s != null && s.isExportable()) {
+ s.tag(key, value);
+ }
+ }
+
+ /**
+ * Wrap the callable in a TraceCallable, if tracing.
+ *
+ * @return The callable provided, wrapped if tracing, 'callable' if not.
+ */
+ @Override
+ public <V> Callable<V> wrap(Callable<V> callable) {
+ if (isTracing()) {
+ return new SpanContinuingTraceCallable<V>(this, this.traceKeys, this.spanNamer, callable);
+ }
+ return callable;
+ }
+
+ /**
+ * Wrap the runnable in a TraceRunnable, if tracing.
+ *
+ * @return The runnable provided, wrapped if tracing, 'runnable' if not.
+ */
+ @Override
+ public Runnable wrap(Runnable runnable) {
+ if (isTracing()) {
+ return new SpanContinuingTraceRunnable(this, this.traceKeys, this.spanNamer, runnable);
+ }
+ return runnable;
+ }
+}
diff --git a/contrib/spring_sleuth/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/contrib/spring_sleuth/src/main/resources/META-INF/additional-spring-configuration-metadata.json
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/contrib/spring_sleuth/src/main/resources/META-INF/additional-spring-configuration-metadata.json
diff --git a/contrib/spring_sleuth/src/main/resources/META-INF/spring.factories b/contrib/spring_sleuth/src/main/resources/META-INF/spring.factories
new file mode 100644
index 00000000..8028fde3
--- /dev/null
+++ b/contrib/spring_sleuth/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,7 @@
+# Auto Configuration
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+io.opencensus.contrib.spring.sleuth.OpenCensusSleuthAutoConfiguration\
+
+# Environment Post Processor
+org.springframework.boot.env.EnvironmentPostProcessor=\
+org.springframework.cloud.sleuth.autoconfig.TraceEnvironmentPostProcessor
diff --git a/contrib/spring_sleuth/src/test/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthSpanContextHolderTest.java b/contrib/spring_sleuth/src/test/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthSpanContextHolderTest.java
new file mode 100644
index 00000000..aaa80b63
--- /dev/null
+++ b/contrib/spring_sleuth/src/test/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthSpanContextHolderTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2018, 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.contrib.spring.sleuth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.opencensus.trace.Span;
+import io.opencensus.trace.Tracer;
+import io.opencensus.trace.Tracing;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link OpenCensusSleuthSpanContextHolder}. */
+@RunWith(JUnit4.class)
+public class OpenCensusSleuthSpanContextHolderTest {
+ private static final Tracer tracer = Tracing.getTracer();
+
+ @After
+ @Before
+ public void verifyNotTracing() {
+ assertThat(OpenCensusSleuthSpanContextHolder.isTracing()).isFalse();
+ assertThat(tracer.getCurrentSpan().getContext().isValid()).isFalse();
+ }
+
+ @Test
+ public void testFromSleuthSampled() {
+ org.springframework.cloud.sleuth.Span sleuthSpan =
+ createSleuthSpan(21, 22, 23, /* exportable= */ true);
+ OpenCensusSleuthSpanContextHolder.setCurrentSpan(sleuthSpan);
+ assertThat(OpenCensusSleuthSpanContextHolder.isTracing()).isTrue();
+ assertThat(OpenCensusSleuthSpanContextHolder.getCurrentSpan()).isEqualTo(sleuthSpan);
+ assertSpanEquals(tracer.getCurrentSpan(), sleuthSpan);
+ assertThat(tracer.getCurrentSpan().getContext().getTraceOptions().isSampled()).isTrue();
+ OpenCensusSleuthSpanContextHolder.close();
+ }
+
+ @Test
+ public void testFromSleuthUnsampled() {
+ org.springframework.cloud.sleuth.Span sleuthSpan =
+ createSleuthSpan(21, 22, 23, /* exportable= */ false);
+ OpenCensusSleuthSpanContextHolder.setCurrentSpan(sleuthSpan);
+ assertThat(OpenCensusSleuthSpanContextHolder.isTracing()).isTrue();
+ assertThat(OpenCensusSleuthSpanContextHolder.getCurrentSpan()).isEqualTo(sleuthSpan);
+ assertSpanEquals(tracer.getCurrentSpan(), sleuthSpan);
+ assertThat(tracer.getCurrentSpan().getContext().getTraceOptions().isSampled()).isFalse();
+ OpenCensusSleuthSpanContextHolder.close();
+ }
+
+ @Test
+ public void testSpanStackSimple() {
+ org.springframework.cloud.sleuth.Span[] sleuthSpans = createSleuthSpans(4);
+ // push all the spans
+ for (int i = 0; i < sleuthSpans.length; i++) {
+ OpenCensusSleuthSpanContextHolder.push(sleuthSpans[i], /* autoClose= */ false);
+ assertThat(OpenCensusSleuthSpanContextHolder.getCurrentSpan()).isEqualTo(sleuthSpans[i]);
+ assertSpanEquals(tracer.getCurrentSpan(), sleuthSpans[i]);
+ }
+ // pop all the spans
+ for (int i = sleuthSpans.length - 1; i >= 0; i--) {
+ assertThat(OpenCensusSleuthSpanContextHolder.getCurrentSpan()).isEqualTo(sleuthSpans[i]);
+ assertSpanEquals(tracer.getCurrentSpan(), sleuthSpans[i]);
+ OpenCensusSleuthSpanContextHolder.close();
+ }
+ }
+
+ @Test
+ public void testSpanStackAutoClose() {
+ org.springframework.cloud.sleuth.Span[] sleuthSpans = createSleuthSpans(4);
+ // push all the spans
+ for (int i = 0; i < sleuthSpans.length; i++) {
+ // set autoclose for all the spans except 2
+ OpenCensusSleuthSpanContextHolder.push(sleuthSpans[i], /* autoClose= */ i != 2);
+ assertThat(OpenCensusSleuthSpanContextHolder.getCurrentSpan()).isEqualTo(sleuthSpans[i]);
+ assertSpanEquals(tracer.getCurrentSpan(), sleuthSpans[i]);
+ }
+ // verify autoClose pops stack to index 2
+ OpenCensusSleuthSpanContextHolder.close();
+ assertThat(OpenCensusSleuthSpanContextHolder.getCurrentSpan()).isEqualTo(sleuthSpans[2]);
+ assertSpanEquals(tracer.getCurrentSpan(), sleuthSpans[2]);
+ // verify autoClose closes pops rest of stack
+ OpenCensusSleuthSpanContextHolder.close();
+ }
+
+ @Test
+ public void testSpanStackCloseSpanFunction() {
+ final org.springframework.cloud.sleuth.Span[] sleuthSpans = createSleuthSpans(4);
+ // push all the spans
+ for (int i = 0; i < sleuthSpans.length; i++) {
+ OpenCensusSleuthSpanContextHolder.push(sleuthSpans[i], /* autoClose= */ false);
+ }
+ // pop all the spans, verify that given SpanFunction is called on the closed span.
+ for (int i = sleuthSpans.length - 1; i >= 0; i--) {
+ final int index = i;
+ OpenCensusSleuthSpanContextHolder.close(
+ new OpenCensusSleuthSpanContextHolder.SpanFunction() {
+ @Override
+ public void apply(org.springframework.cloud.sleuth.Span span) {
+ assertThat(span).isEqualTo(sleuthSpans[index]);
+ }
+ });
+ }
+ }
+
+ org.springframework.cloud.sleuth.Span[] createSleuthSpans(int len) {
+ org.springframework.cloud.sleuth.Span[] spans = new org.springframework.cloud.sleuth.Span[len];
+ for (int i = 0; i < len; i++) {
+ spans[i] = createSleuthSpan(i * 10 + 1, i * 10 + 2, i * 10 + 3, /* exportable= */ true);
+ }
+ return spans;
+ }
+
+ private static org.springframework.cloud.sleuth.Span createSleuthSpan(
+ long tidHi, long tidLo, long sid, boolean exportable) {
+ return org.springframework.cloud.sleuth.Span.builder()
+ .name("name")
+ .traceIdHigh(tidHi)
+ .traceId(tidLo)
+ .spanId(sid)
+ .exportable(exportable)
+ .build();
+ }
+
+ private static void assertSpanEquals(
+ Span span, org.springframework.cloud.sleuth.Span sleuthSpan) {
+ assertThat(Long.parseLong(span.getContext().getTraceId().toLowerBase16().substring(0, 16), 16))
+ .isEqualTo(sleuthSpan.getTraceIdHigh());
+ assertThat(Long.parseLong(span.getContext().getTraceId().toLowerBase16().substring(16, 32), 16))
+ .isEqualTo(sleuthSpan.getTraceId());
+ assertThat(Long.parseLong(span.getContext().getSpanId().toLowerBase16(), 16))
+ .isEqualTo(sleuthSpan.getSpanId());
+ assertThat(span.getContext().getTraceOptions().isSampled())
+ .isEqualTo(sleuthSpan.isExportable());
+ }
+}
diff --git a/contrib/spring_sleuth/src/test/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthSpanTest.java b/contrib/spring_sleuth/src/test/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthSpanTest.java
new file mode 100644
index 00000000..b24af583
--- /dev/null
+++ b/contrib/spring_sleuth/src/test/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthSpanTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2018, 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.contrib.spring.sleuth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.springframework.cloud.sleuth.Span;
+
+/** Unit tests for {@link OpenCensusSleuthSpan}. */
+@RunWith(JUnit4.class)
+public class OpenCensusSleuthSpanTest {
+ @Test
+ public void testFromSleuthSampled() {
+ Span sleuthSpan =
+ Span.builder()
+ .name("name")
+ .traceIdHigh(12L)
+ .traceId(22L)
+ .spanId(23L)
+ .exportable(true)
+ .build();
+ assertSpanEquals(new OpenCensusSleuthSpan(sleuthSpan), sleuthSpan);
+ }
+
+ @Test
+ public void testFromSleuthNotSampled() {
+ Span sleuthSpan =
+ Span.builder()
+ .name("name")
+ .traceIdHigh(12L)
+ .traceId(22L)
+ .spanId(23L)
+ .exportable(false)
+ .build();
+ assertSpanEquals(new OpenCensusSleuthSpan(sleuthSpan), sleuthSpan);
+ }
+
+ private static final void assertSpanEquals(io.opencensus.trace.Span span, Span sleuthSpan) {
+ assertThat(span.getContext().isValid()).isTrue();
+ assertThat(Long.parseLong(span.getContext().getTraceId().toLowerBase16().substring(0, 16), 16))
+ .isEqualTo(sleuthSpan.getTraceIdHigh());
+ assertThat(Long.parseLong(span.getContext().getTraceId().toLowerBase16().substring(16, 32), 16))
+ .isEqualTo(sleuthSpan.getTraceId());
+ assertThat(Long.parseLong(span.getContext().getSpanId().toLowerBase16(), 16))
+ .isEqualTo(sleuthSpan.getSpanId());
+ assertThat(span.getContext().getTraceOptions().isSampled())
+ .isEqualTo(sleuthSpan.isExportable());
+ }
+}
diff --git a/contrib/spring_sleuth/src/test/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthTracerTest.java b/contrib/spring_sleuth/src/test/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthTracerTest.java
new file mode 100644
index 00000000..38172567
--- /dev/null
+++ b/contrib/spring_sleuth/src/test/java/io/opencensus/contrib/spring/sleuth/OpenCensusSleuthTracerTest.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2018, 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.contrib.spring.sleuth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.util.Random;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.springframework.cloud.sleuth.DefaultSpanNamer;
+import org.springframework.cloud.sleuth.NoOpSpanReporter;
+import org.springframework.cloud.sleuth.Span;
+import org.springframework.cloud.sleuth.TraceKeys;
+import org.springframework.cloud.sleuth.Tracer;
+import org.springframework.cloud.sleuth.log.NoOpSpanLogger;
+import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
+
+/** Unit tests for {@link OpenCensusSleuthTracer}. */
+@RunWith(JUnit4.class)
+public class OpenCensusSleuthTracerTest {
+ private static final Tracer tracer =
+ new OpenCensusSleuthTracer(
+ new AlwaysSampler(),
+ new Random(),
+ new DefaultSpanNamer(),
+ new NoOpSpanLogger(),
+ new NoOpSpanReporter(),
+ new TraceKeys());
+
+ @After
+ @Before
+ public void verifyNotTracing() {
+ assertThat(tracer.isTracing()).isFalse();
+ }
+
+ @Test
+ public void testRootSpanAndClose() {
+ Span root = tracer.createSpan("root");
+ assertCurrentSpanIs(root);
+ assertThat(root.getSavedSpan()).isNull();
+ Span parent = tracer.close(root);
+ assertThat(parent).isNull();
+ }
+
+ @Test
+ public void testSpanStackAndClose() {
+ Span[] spans = createSpansAndAssertCurrent(3);
+ // pop the stack
+ for (int i = spans.length - 1; i >= 0; i--) {
+ assertCurrentSpanIs(spans[i]);
+ Span parent = tracer.close(spans[i]);
+ assertThat(parent).isEqualTo(spans[i].getSavedSpan());
+ }
+ }
+
+ @Test
+ public void testSpanStackAndCloseOutOfOrder() {
+ Span[] spans = createSpansAndAssertCurrent(3);
+ // try to close a non-current span
+ tracer.close(spans[spans.length - 2]);
+ assertCurrentSpanIs(spans[spans.length - 1]);
+ // pop the stack
+ for (int i = spans.length - 1; i >= 0; i--) {
+ tracer.close(spans[i]);
+ }
+ }
+
+ @Test
+ public void testDetachNull() {
+ Span parent = tracer.detach(null);
+ assertThat(parent).isNull();
+ }
+
+ @Test
+ public void testRootSpanAndDetach() {
+ Span root = tracer.createSpan("root");
+ assertCurrentSpanIs(root);
+ assertThat(root.getSavedSpan()).isNull();
+ Span parent = tracer.detach(root);
+ assertThat(parent).isNull();
+ }
+
+ @Test
+ public void testSpanStackAndDetach() {
+ Span[] spans = createSpansAndAssertCurrent(3);
+ Span parent = tracer.detach(spans[spans.length - 1]);
+ assertThat(parent).isEqualTo(spans[spans.length - 2]);
+ }
+
+ @Test
+ public void testSpanStackAndDetachOutOfOrder() {
+ Span[] spans = createSpansAndAssertCurrent(3);
+ // try to detach a non-current span
+ tracer.detach(spans[spans.length - 2]);
+ assertCurrentSpanIs(spans[spans.length - 1]);
+ Span parent = tracer.detach(spans[spans.length - 1]);
+ assertThat(parent).isEqualTo(spans[spans.length - 2]);
+ }
+
+ @Test
+ public void testContinueNull() {
+ Span span = tracer.continueSpan(null);
+ assertThat(span).isNull();
+ }
+
+ @Test
+ public void testRootSpanAndContinue() {
+ Span root = tracer.createSpan("root");
+ assertCurrentSpanIs(root);
+ tracer.detach(root);
+ Span span = tracer.continueSpan(root);
+ assertThat(span).isEqualTo(root);
+ tracer.detach(span);
+ }
+
+ @Test
+ public void testSpanStackAndContinue() {
+ Span[] spans = createSpansAndAssertCurrent(3);
+ Span original = tracer.getCurrentSpan();
+ assertThat(original).isEqualTo(spans[spans.length - 1]);
+ Span parent = tracer.detach(original);
+ assertThat(parent).isEqualTo(spans[spans.length - 2]);
+ assertThat(tracer.getCurrentSpan()).isNull();
+
+ Span continued = tracer.continueSpan(original);
+ assertCurrentSpanIs(continued);
+ assertThat(continued.getSavedSpan()).isEqualTo(parent);
+ assertThat(continued).isEqualTo(original);
+ tracer.detach(continued);
+ }
+
+ @Test
+ public void testSpanStackAndCreateAndContinue() {
+ createSpansAndAssertCurrent(3);
+ Span original = tracer.getCurrentSpan();
+ tracer.detach(original);
+ Span root = tracer.createSpan("root");
+ assertCurrentSpanIs(root);
+ Span continued = tracer.continueSpan(original);
+ assertCurrentSpanIs(continued);
+ assertThat(continued.getSavedSpan()).isEqualTo(root);
+ assertThat(continued).isEqualTo(original);
+ assertThat(continued.getSavedSpan()).isNotEqualTo(original.getSavedSpan());
+ tracer.detach(continued);
+ }
+
+ // Verifies span and associated saved span.
+ private static void assertCurrentSpanIs(Span span) {
+ assertThat(tracer.getCurrentSpan()).isEqualTo(span);
+ assertThat(tracer.getCurrentSpan().getSavedSpan()).isEqualTo(span.getSavedSpan());
+
+ assertThat(OpenCensusSleuthSpanContextHolder.getCurrentSpan()).isEqualTo(span);
+ assertThat(OpenCensusSleuthSpanContextHolder.getCurrentSpan().getSavedSpan())
+ .isEqualTo(span.getSavedSpan());
+ }
+
+ private static Span[] createSpansAndAssertCurrent(int len) {
+ Span[] spans = new Span[len];
+
+ Span current = null;
+ for (int i = 0; i < len; i++) {
+ current = tracer.createSpan("span" + i, current);
+ spans[i] = current;
+ assertCurrentSpanIs(current);
+ }
+ return spans;
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index f1688b60..07f14eb5 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -23,6 +23,7 @@ include ":opencensus-contrib-log-correlation-log4j"
include ":opencensus-contrib-log-correlation-stackdriver"
include ":opencensus-contrib-monitored-resource-util"
include ":opencensus-contrib-spring"
+include ":opencensus-contrib-spring-sleuth"
project(':opencensus-api').projectDir = "$rootDir/api" as File
project(':opencensus-impl-core').projectDir = "$rootDir/impl_core" as File
@@ -43,6 +44,7 @@ project(':opencensus-contrib-log-correlation-stackdriver').projectDir =
project(':opencensus-contrib-monitored-resource-util').projectDir =
"$rootDir/contrib/monitored_resource_util" as File
project(':opencensus-contrib-spring').projectDir = "$rootDir/contrib/spring" as File
+project(':opencensus-contrib-spring-sleuth').projectDir = "$rootDir/contrib/spring_sleuth" as File
project(':opencensus-exporter-trace-instana').projectDir =
"$rootDir/exporters/trace/instana" as File
project(':opencensus-exporter-trace-logging').projectDir =