diff options
author | Dino Oliva <dinooliva@users.noreply.github.com> | 2018-09-05 12:09:44 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-09-05 12:09:44 -0700 |
commit | 30c10cc77cd3a6d9583c2339ade689d06585aba4 (patch) | |
tree | 147471d49a23a7fbefc65330b8c2026dc287d853 | |
parent | 0ecdfd01dbe325690c60b124709cba4b04167172 (diff) | |
download | opencensus-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.
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 = |