Added OpenTracing support; fixes #599

This commit is contained in:
Marcin Grzejszczak
2018-01-21 09:28:34 +01:00
parent 266061f0f7
commit 22d91bac24
9 changed files with 438 additions and 5 deletions

View File

@@ -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.*

View File

@@ -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.

View File

@@ -152,6 +152,11 @@
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-instrumentation-spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>io.opentracing.brave</groupId>
<artifactId>brave-opentracing</artifactId>
<optional>true</optional>
</dependency>
<!-- BRAVE -->
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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=\

View File

@@ -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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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();
}
}
}

View File

@@ -18,6 +18,7 @@
<zipkin-reporter.version>1.1.2</zipkin-reporter.version>
<zipkin-reporter2.version>2.3.0</zipkin-reporter2.version>
<brave.version>4.13.3</brave.version>
<brave.opentracing.version>0.25.0</brave.opentracing.version>
</properties>
<dependencyManagement>
<dependencies>
@@ -87,7 +88,7 @@
<artifactId>brave-instrumentation-spring-webmvc</artifactId>
<version>${brave.version}</version>
</dependency>
<!-- BRAVE -->
<!-- ZIPKIN -->
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin</artifactId>
@@ -153,6 +154,11 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.opentracing.brave</groupId>
<artifactId>brave-opentracing</artifactId>
<version>${brave.opentracing.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<profiles>