diff --git a/docs/src/main/asciidoc/features.adoc b/docs/src/main/asciidoc/features.adoc
index b5733768d..a35940028 100644
--- a/docs/src/main/asciidoc/features.adoc
+++ b/docs/src/main/asciidoc/features.adoc
@@ -39,14 +39,14 @@ a baggage element then it will be sent downstream either via HTTP or messaging t
* Provides a way to create / continue spans and add tags and logs via annotations.
-* Provides simple metrics of accepted / dropped spans.
-
* If `spring-cloud-sleuth-zipkin` then the app will generate and collect Zipkin-compatible traces.
By default it sends them via HTTP to a Zipkin server on localhost (port 9411).
Configure the location of the service using `spring.zipkin.baseUrl`.
- If you depend on `spring-rabbit` or `spring-kafka` your app will send traces to a broker instead of http.
- Note: `spring-cloud-sleuth-stream` is deprecated and should no longer be used.
+* Spring Cloud Sleuth is http://opentracing.io/[OpenTracing] compatible
+
IMPORTANT: If using Zipkin, configure the percentage of spans exported using `spring.sleuth.sampler.percentage`
(default 0.1, i.e. 10%). *Otherwise you might think that Sleuth is not working cause it's omitting some spans.*
diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc
index 5c4acebf9..2f5f92eb4 100644
--- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc
+++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc
@@ -931,6 +931,12 @@ on how to create a Stream Zipkin server.
== Integrations
+=== OpenTracing
+
+Spring Cloud Sleuth is http://opentracing.io/[OpenTracing] compatible. If you have
+OpenTracing on the classpath we will automatically register the OpenTracing
+`Tracer` bean. If you wish to disable this just set `spring.sleuth.opentracing.enabled` to `false`
+
=== Runnable and Callable
If you're wrapping your logic in `Runnable` or `Callable` it's enough to wrap those classes in their Sleuth representative.
diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml
index 52a8bbee2..0efd9ca73 100644
--- a/spring-cloud-sleuth-core/pom.xml
+++ b/spring-cloud-sleuth-core/pom.xml
@@ -152,6 +152,11 @@
io.zipkin.brave
brave-instrumentation-spring-webmvc
+
+ io.opentracing.brave
+ brave-opentracing
+ true
+
org.springframework.boot
diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/opentracing/OpentracingAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/opentracing/OpentracingAutoConfiguration.java
new file mode 100644
index 000000000..b15f80a2d
--- /dev/null
+++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/opentracing/OpentracingAutoConfiguration.java
@@ -0,0 +1,34 @@
+package org.springframework.cloud.sleuth.instrument.opentracing;
+
+import brave.Tracing;
+import brave.opentracing.BraveTracer;
+import io.opentracing.Tracer;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration}
+ * to enable tracing via Opentracing.
+ *
+ * @author Spencer Gibb
+ * @author Marcin Grzejszczak
+ * @since 2.0.0
+ */
+@Configuration
+@ConditionalOnProperty(value="spring.sleuth.opentracing.enabled", matchIfMissing=true)
+@ConditionalOnBean(Tracing.class)
+@ConditionalOnClass(Tracer.class)
+@EnableConfigurationProperties(SleuthOpentracingProperties.class)
+public class OpentracingAutoConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ Tracer sleuthOpenTracing(brave.Tracing braveTracing) {
+ return BraveTracer.create(braveTracing);
+ }
+}
diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/opentracing/SleuthOpentracingProperties.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/opentracing/SleuthOpentracingProperties.java
new file mode 100644
index 000000000..a8c874d8b
--- /dev/null
+++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/opentracing/SleuthOpentracingProperties.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013-2018 the original author or 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 org.springframework.cloud.sleuth.instrument.opentracing;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * Sleuth Opentracing settings
+ *
+ * @since 2.0.0
+ */
+@ConfigurationProperties("spring.sleuth.opentracing")
+public class SleuthOpentracingProperties {
+
+ private boolean enabled = true;
+
+ public boolean isEnabled() {
+ return this.enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+}
diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebFilter.java
index 6768bd452..9ea6b85cc 100644
--- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebFilter.java
+++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebFilter.java
@@ -32,7 +32,7 @@ import org.springframework.web.server.WebFilterChain;
* @author Marcin Grzejszczak
* @since 2.0.0
*/
-public class TraceWebFilter implements WebFilter, Ordered {
+public final class TraceWebFilter implements WebFilter, Ordered {
private static final Log log = LogFactory.getLog(TraceWebFilter.class);
diff --git a/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories b/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories
index c69d34344..6ade66073 100644
--- a/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories
+++ b/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories
@@ -18,7 +18,8 @@ org.springframework.cloud.sleuth.instrument.reactor.TraceReactorAutoConfiguratio
org.springframework.cloud.sleuth.instrument.web.TraceWebFluxAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.zuul.TraceZuulAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.messaging.TraceSpringIntegrationAutoConfiguration,\
-org.springframework.cloud.sleuth.instrument.messaging.websocket.TraceWebSocketAutoConfiguration
+org.springframework.cloud.sleuth.instrument.messaging.websocket.TraceWebSocketAutoConfiguration,\
+org.springframework.cloud.sleuth.instrument.opentracing.OpentracingAutoConfiguration
# Environment Post Processor
org.springframework.boot.env.EnvironmentPostProcessor=\
diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/opentracing/BraveTracerTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/opentracing/BraveTracerTest.java
new file mode 100644
index 000000000..e84c58d4f
--- /dev/null
+++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/opentracing/BraveTracerTest.java
@@ -0,0 +1,342 @@
+/**
+ * Copyright 2016-2018 The OpenZipkin 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 org.springframework.cloud.sleuth.instrument.opentracing;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import brave.Span;
+import brave.Tracer;
+import brave.Tracer.SpanInScope;
+import brave.Tracing;
+import brave.opentracing.BraveSpan;
+import brave.opentracing.BraveSpanContext;
+import brave.opentracing.BraveTracer;
+import brave.propagation.B3Propagation;
+import brave.propagation.CurrentTraceContext;
+import brave.propagation.ExtraFieldPropagation;
+import brave.propagation.Propagation;
+import brave.propagation.StrictCurrentTraceContext;
+import brave.propagation.TraceContext;
+import brave.sampler.Sampler;
+import io.opentracing.Scope;
+import io.opentracing.propagation.Format;
+import io.opentracing.propagation.TextMap;
+import io.opentracing.propagation.TextMapExtractAdapter;
+import io.opentracing.propagation.TextMapInjectAdapter;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.sleuth.util.ArrayListSpanReporter;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.junit4.SpringRunner;
+import zipkin2.Annotation;
+import zipkin2.Endpoint;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.data.MapEntry.entry;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * This shows how one might make an OpenTracing adapter for Brave, and how to navigate in and out of
+ * the core concepts.
+ *
+ * Adopted from: https://github.com/openzipkin-contrib/brave-opentracing/tree/master/src/test/java/brave/opentracing
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE,
+properties = "spring.sleuth.baggage-keys=country-code,user-id")
+public class BraveTracerTest {
+
+ @Autowired ArrayListSpanReporter spans;
+ @Autowired Tracing brave;
+ @Autowired BraveTracer opentracing;
+
+ @Test public void startWithOpenTracingAndFinishWithBrave() {
+ io.opentracing.Span openTracingSpan = opentracing.buildSpan("encode")
+ .withTag("lc", "codec")
+ .withStartTimestamp(1L)
+ .start();
+
+ Span braveSpan = ((BraveSpan) openTracingSpan).unwrap();
+
+ braveSpan.annotate(2L, "pump fake");
+ braveSpan.finish(3L);
+
+ checkSpanReportedToZipkin();
+ }
+
+ @Test public void extractTraceContext() throws Exception {
+ Map map = new LinkedHashMap<>();
+ map.put("X-B3-TraceId", "0000000000000001");
+ map.put("X-B3-SpanId", "0000000000000002");
+ map.put("X-B3-Sampled", "1");
+
+ BraveSpanContext openTracingContext =
+ (BraveSpanContext) opentracing.extract(Format.Builtin.HTTP_HEADERS,
+ new TextMapExtractAdapter(map));
+
+ assertThat(openTracingContext.unwrap())
+ .isEqualTo(TraceContext.newBuilder()
+ .traceId(1L)
+ .spanId(2L)
+ .sampled(true).build());
+ }
+
+ @Test public void extractBaggage() throws Exception {
+ Map map = new LinkedHashMap<>();
+ map.put("X-B3-TraceId", "0000000000000001");
+ map.put("X-B3-SpanId", "0000000000000002");
+ map.put("X-B3-Sampled", "1");
+ map.put("baggage-country-code", "FO");
+
+ BraveSpanContext openTracingContext = opentracing.extract(Format.Builtin.HTTP_HEADERS,
+ new TextMapExtractAdapter(map));
+
+ assertThat(openTracingContext.baggageItems())
+ .containsExactly(entry("country-code", "FO"));
+ }
+
+ @Test public void extractTraceContextTextMap() throws Exception {
+ Map map = new LinkedHashMap<>();
+ map.put("X-B3-TraceId", "0000000000000001");
+ map.put("X-B3-SpanId", "0000000000000002");
+ map.put("X-B3-Sampled", "1");
+
+ BraveSpanContext openTracingContext =
+ (BraveSpanContext) opentracing.extract(Format.Builtin.TEXT_MAP,
+ new TextMapExtractAdapter(map));
+
+ assertThat(openTracingContext.unwrap())
+ .isEqualTo(TraceContext.newBuilder()
+ .traceId(1L)
+ .spanId(2L)
+ .sampled(true).build());
+ }
+
+ @Test public void extractTraceContextCaseInsensitive() throws Exception {
+ Map map = new LinkedHashMap<>();
+ map.put("X-B3-TraceId", "0000000000000001");
+ map.put("x-b3-spanid", "0000000000000002");
+ map.put("x-b3-SaMpLeD", "1");
+ map.put("other", "1");
+
+ BraveSpanContext openTracingContext =
+ (BraveSpanContext) opentracing.extract(Format.Builtin.HTTP_HEADERS,
+ new TextMapExtractAdapter(map));
+
+ assertThat(openTracingContext.unwrap())
+ .isEqualTo(TraceContext.newBuilder()
+ .traceId(1L)
+ .spanId(2L)
+ .sampled(true).build());
+ }
+
+ @Test public void extractTraceContextReturnsNull() throws Exception {
+ Map map = new LinkedHashMap<>();
+ map.put("other", "1");
+
+ BraveSpanContext openTracingContext = opentracing.extract(Format.Builtin.HTTP_HEADERS,
+ new TextMapExtractAdapter(map));
+
+ assertThat(openTracingContext).isNull();
+ }
+
+ @Test public void injectTraceContext_baggage() throws Exception {
+ BraveSpan span = opentracing.buildSpan("foo").start();
+ span.setBaggageItem("country-code", "FO");
+
+ Map map = new LinkedHashMap<>();
+ TextMapInjectAdapter carrier = new TextMapInjectAdapter(map);
+ opentracing.inject(span.context(), Format.Builtin.HTTP_HEADERS, carrier);
+
+ assertThat(map).containsEntry("baggage-country-code", "FO");
+ }
+
+ void checkSpanReportedToZipkin() {
+ assertThat(spans.getSpans()).first().satisfies(s -> {
+ assertThat(s.name()).isEqualTo("encode");
+ assertThat(s.timestamp()).isEqualTo(1L);
+ assertThat(s.annotations())
+ .containsExactly(Annotation.create(2L, "pump fake"));
+ assertThat(s.tags())
+ .containsExactly(entry("lc", "codec"));
+ assertThat(s.duration()).isEqualTo(2L);
+ }
+ );
+ }
+
+ @Test public void subsequentChildrenNestProperly_OTStyle() {
+ // this test is semantically identical to subsequentChildrenNestProperly_BraveStyle, but uses
+ // the OpenTracingAPI instead of the Brave API.
+
+ Long idOfSpanA;
+ Long shouldBeIdOfSpanA;
+ Long idOfSpanB;
+ Long shouldBeIdOfSpanB;
+ Long parentIdOfSpanB;
+ Long parentIdOfSpanC;
+
+ try (Scope scopeA = opentracing.buildSpan("spanA").startActive(false)) {
+ idOfSpanA = getTraceContext(scopeA).spanId();
+ try (Scope scopeB = opentracing.buildSpan("spanB").startActive(false)) {
+ idOfSpanB = getTraceContext(scopeB).spanId();
+ parentIdOfSpanB = getTraceContext(scopeB).parentId();
+ shouldBeIdOfSpanB = getTraceContext(opentracing.scopeManager().active()).spanId();
+ }
+ shouldBeIdOfSpanA = getTraceContext(opentracing.scopeManager().active()).spanId();
+ try (Scope scopeC = opentracing.buildSpan("spanC").startActive(false)) {
+ parentIdOfSpanC = getTraceContext(scopeC).parentId();
+ }
+ }
+
+ assertEquals("SpanA should have been active again after closing B", idOfSpanA,
+ shouldBeIdOfSpanA);
+ assertEquals("SpanB should have been active prior to its closure", idOfSpanB,
+ shouldBeIdOfSpanB);
+ assertEquals("SpanB's parent should be SpanA", idOfSpanA, parentIdOfSpanB);
+ assertEquals("SpanC's parent should be SpanA", idOfSpanA, parentIdOfSpanC);
+ }
+
+ @Test public void subsequentChildrenNestProperly_BraveStyle() {
+ // this test is semantically identical to subsequentChildrenNestProperly_OTStyle, but uses
+ // the Brave API instead of the OpenTracing API.
+
+ Long shouldBeIdOfSpanA;
+ Long idOfSpanB;
+ Long shouldBeIdOfSpanB;
+ Long parentIdOfSpanB;
+ Long parentIdOfSpanC;
+
+ Span spanA = brave.tracer().newTrace().name("spanA").start();
+ Long idOfSpanA = spanA.context().spanId();
+ try (SpanInScope scopeA = brave.tracer().withSpanInScope(spanA)) {
+
+ Span spanB = brave.tracer().newChild(spanA.context()).name("spanB").start();
+ idOfSpanB = spanB.context().spanId();
+ parentIdOfSpanB = spanB.context().parentId();
+ try (SpanInScope scopeB = brave.tracer().withSpanInScope(spanB)) {
+ shouldBeIdOfSpanB = brave.currentTraceContext().get().spanId();
+ } finally {
+ spanB.finish();
+ }
+
+ shouldBeIdOfSpanA = brave.currentTraceContext().get().spanId();
+
+ Span spanC = brave.tracer().newChild(spanA.context()).name("spanC").start();
+ parentIdOfSpanC = spanC.context().parentId();
+ try (SpanInScope scopeC = brave.tracer().withSpanInScope(spanC)) {
+ // nothing to do here
+ } finally {
+ spanC.finish();
+ }
+ } finally {
+ spanA.finish();
+ }
+
+ assertEquals("SpanA should have been active again after closing B", idOfSpanA,
+ shouldBeIdOfSpanA);
+ assertEquals("SpanB should have been active prior to its closure", idOfSpanB,
+ shouldBeIdOfSpanB);
+ assertEquals("SpanB's parent should be SpanA", idOfSpanA, parentIdOfSpanB);
+ assertEquals("SpanC's parent should be SpanA", idOfSpanA, parentIdOfSpanC);
+ }
+
+ @Test public void implicitParentFromSpanManager_startActive() {
+ try (Scope scopeA = opentracing.buildSpan("spanA").startActive(true)) {
+ try (Scope scopeB = opentracing.buildSpan("spanA").startActive(true)) {
+ assertThat(getTraceContext(scopeB).parentId())
+ .isEqualTo(getTraceContext(scopeA).spanId());
+ }
+ }
+ }
+
+ @Test public void implicitParentFromSpanManager_start() {
+ try (Scope scopeA = opentracing.buildSpan("spanA").startActive(true)) {
+ BraveSpan span = opentracing.buildSpan("spanB").start();
+ assertThat(span.unwrap().context().parentId())
+ .isEqualTo(getTraceContext(scopeA).spanId());
+ }
+ }
+
+ @Test public void implicitParentFromSpanManager_startActive_ignoreActiveSpan() {
+ try (Scope scopeA = opentracing.buildSpan("spanA").startActive(true)) {
+ try (Scope scopeB = opentracing.buildSpan("spanA")
+ .ignoreActiveSpan().startActive(true)) {
+ assertThat(getTraceContext(scopeB).parentId())
+ .isNull(); // new trace
+ }
+ }
+ }
+
+ @Test public void implicitParentFromSpanManager_start_ignoreActiveSpan() {
+ try (Scope scopeA = opentracing.buildSpan("spanA").startActive(true)) {
+ BraveSpan span = opentracing.buildSpan("spanB")
+ .ignoreActiveSpan().start();
+ assertThat(span.unwrap().context().parentId())
+ .isNull(); // new trace
+ }
+ }
+
+ @Test public void ignoresErrorFalseTag_beforeStart() {
+ opentracing.buildSpan("encode")
+ .withTag("error", false)
+ .start().finish();
+
+ assertThat(spans.getSpans().get(0).tags())
+ .isEmpty();
+ }
+
+ @Test public void ignoresErrorFalseTag_afterStart() {
+ opentracing.buildSpan("encode")
+ .start()
+ .setTag("error", false)
+ .finish();
+
+ assertThat(spans.getSpans().get(0).tags())
+ .isEmpty();
+ }
+
+ private static TraceContext getTraceContext(Scope scope) {
+ return ((BraveSpanContext) scope.span().context()).unwrap();
+ }
+
+ @Before public void clear() {
+ this.spans.clear();
+ }
+
+ @Configuration
+ @EnableAutoConfiguration
+ static class Config {
+ @Bean Sampler sampler() {
+ return Sampler.ALWAYS_SAMPLE;
+ }
+
+ @Bean ArrayListSpanReporter reporter() {
+ return new ArrayListSpanReporter();
+ }
+
+ @Bean CurrentTraceContext currentTraceContext() {
+ return new StrictCurrentTraceContext();
+ }
+ }
+}
diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml
index 58dc830b9..71b18b8db 100644
--- a/spring-cloud-sleuth-dependencies/pom.xml
+++ b/spring-cloud-sleuth-dependencies/pom.xml
@@ -18,6 +18,7 @@
1.1.2
2.3.0
4.13.3
+ 0.25.0
@@ -87,7 +88,7 @@
brave-instrumentation-spring-webmvc
${brave.version}
-
+
io.zipkin.java
zipkin
@@ -153,6 +154,11 @@
+
+ io.opentracing.brave
+ brave-opentracing
+ ${brave.opentracing.version}
+