Merge branch '1.1.x'

This commit is contained in:
Marcin Grzejszczak
2017-04-19 10:46:59 +02:00
13 changed files with 255 additions and 28 deletions

View File

@@ -446,6 +446,26 @@ By default Sleuth assumes that when you send a span to Zipkin, you want the span
spring.zipkin.service.name: foo
----
=== Customization of reported spans
Before reporting spans to e.g. Zipkin you can be interested in modifying that span in some way.
You can achieve that by using the `SpanAdjuster` interface.
Example of usage:
In Sleuth we're generating spans with a fixed name. Some users want to modify the name depending on values
of tags. Implementation of the `SpanAdjuster` interface can be used to alter that name. Example:
[source,yaml]
----
@Bean
SpanAdjuster customSpanAdjuster() {
return span -> span.toBuilder().name(scrub(span.getName())).build();
}
----
This will lead in changing the name of the reported span just before it gets sent to Zipkin.
=== Host locator
In order to define the host that is corresponding to a particular span we need to resolve the host name

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2013-2016 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;
/**
* Span adjuster that does nothing.
*
* @author Marcin Grzejszczak
* @since 1.1.4
*/
public class NoOpSpanAdjuster implements SpanAdjuster {
@Override public Span adjust(Span span) {
return span;
}
}

View File

@@ -534,6 +534,13 @@ public class Span implements SpanContext {
return new String(result);
}
/**
* Converts the span to a {@link SpanBuilder} format
*/
public SpanBuilder toBuilder() {
return builder().from(this);
}
/**
* Represents given long id as 16-character lower-hex string
*
@@ -697,6 +704,7 @@ public class Span implements SpanContext {
}
public Span.SpanBuilder parents(Collection<Long> parents) {
this.parents.clear();
this.parents.addAll(parents);
return this;
}
@@ -707,6 +715,7 @@ public class Span implements SpanContext {
}
public Span.SpanBuilder logs(Collection<Log> logs) {
this.logs.clear();
this.logs.addAll(logs);
return this;
}
@@ -717,6 +726,7 @@ public class Span implements SpanContext {
}
public Span.SpanBuilder tags(Map<String, String> tags) {
this.tags.clear();
this.tags.putAll(tags);
return this;
}
@@ -756,6 +766,14 @@ public class Span implements SpanContext {
return this;
}
public Span.SpanBuilder from(Span span) {
return begin(span.begin).end(span.end).name(span.name)
.traceIdHigh(span.traceIdHigh).traceId(span.traceId)
.parents(span.getParents()).logs(span.logs).tags(span.tags)
.spanId(span.spanId).remote(span.remote).exportable(span.exportable)
.processId(span.processId).savedSpan(span.savedSpan);
}
public Span build() {
return new Span(this);
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2013-2016 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;
/**
* Adds ability to adjust a span before reporting it.
*
* @author Marcin Grzejszczak
* @since 1.1.4
*/
public interface SpanAdjuster {
/**
* You can adjust the {@link Span} by creating a new one using the {@link Span.SpanBuilder}
* before reporting it.
*
* In Sleuth we're generating spans with a fixed name. Some users want to modify the name
* depending on some values of tags. Implementation of this interface can be used to alter
* then name. Example:
*
* {@code span -> span.toBuilder().name(scrub(span.getName())).build();}
*/
Span adjust(Span span);
}

View File

@@ -23,8 +23,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.sleuth.DefaultSpanNamer;
import org.springframework.cloud.sleuth.NoOpSpanAdjuster;
import org.springframework.cloud.sleuth.NoOpSpanReporter;
import org.springframework.cloud.sleuth.Sampler;
import org.springframework.cloud.sleuth.SpanAdjuster;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.SpanReporter;
import org.springframework.cloud.sleuth.TraceKeys;
@@ -83,4 +85,10 @@ public class TraceAutoConfiguration {
return new NoOpSpanReporter();
}
@Bean
@ConditionalOnMissingBean
public SpanAdjuster defaultSpanAdjuster() {
return new NoOpSpanAdjuster();
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2013-2016 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;
import org.junit.Test;
import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then;
/**
* @author Marcin Grzejszczak
*/
public class NoOpSpanAdjusterTests {
@Test
public void should_return_same_span() {
Span span = Span.builder().spanId(1).build();
then(new NoOpSpanAdjuster().adjust(span)).isSameAs(span);
}
}

View File

@@ -253,4 +253,29 @@ public class SpanTests {
// We round so that we don't confuse "not started" with a short span.
assertThat(span.getAccumulatedMicros()).isEqualTo(1L);
}
@Test
public void should_build_a_span_from_provided_span() throws IOException {
Span span = builder().build();
Span builtSpan = Span.builder().from(span).build();
assertThat(builtSpan).isEqualTo(span);
}
@Test
public void should_convert_a_span_to_builder() throws IOException {
Span.SpanBuilder spanBuilder = builder();
Span span = spanBuilder.build();
Span span2 = span.toBuilder().build();
assertThat(span).isEqualTo(span2);
}
private Span.SpanBuilder builder() {
return Span.builder().name("http:name").traceId(1L).spanId(2L).parent(3L)
.begin(1L).end(2L).traceId(3L).exportable(true).parent(4L)
.remote(true).tag("tag", "tag").log(new Log(System.currentTimeMillis(), "log"));
}
}

View File

@@ -28,6 +28,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.commons.util.InetUtils;
import org.springframework.cloud.sleuth.Sampler;
import org.springframework.cloud.sleuth.SpanAdjuster;
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
import org.springframework.cloud.sleuth.metric.SpanMetricReporter;
import org.springframework.cloud.sleuth.metric.TraceMetricsAutoConfiguration;
@@ -57,9 +58,9 @@ import org.springframework.scheduling.support.PeriodicTrigger;
* @since 1.0.0
*/
@Configuration
@EnableConfigurationProperties({SleuthStreamProperties.class, SamplerProperties.class, ZipkinProperties.class})
@EnableConfigurationProperties({ SleuthStreamProperties.class, SamplerProperties.class, ZipkinProperties.class })
@AutoConfigureAfter(TraceMetricsAutoConfiguration.class)
@AutoConfigureBefore({ChannelBindingAutoConfiguration.class, TraceAutoConfiguration.class, ChannelsEndpointAutoConfiguration.class})
@AutoConfigureBefore({ ChannelBindingAutoConfiguration.class, TraceAutoConfiguration.class, ChannelsEndpointAutoConfiguration.class })
@EnableBinding(SleuthSource.class)
@ConditionalOnProperty(value = "spring.sleuth.stream.enabled", matchIfMissing = true)
public class SleuthStreamAutoConfiguration {
@@ -79,8 +80,8 @@ public class SleuthStreamAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public StreamSpanReporter sleuthStreamSpanReporter(HostLocator endpointLocator,
SpanMetricReporter spanMetricReporter, Environment environment) {
return new StreamSpanReporter(endpointLocator, spanMetricReporter, environment);
SpanMetricReporter spanMetricReporter, Environment environment, SpanAdjuster spanAdjuster) {
return new StreamSpanReporter(endpointLocator, spanMetricReporter, environment, spanAdjuster);
}
@Bean(name = StreamSpanReporter.POLLER)

View File

@@ -27,7 +27,9 @@ import java.util.concurrent.LinkedBlockingQueue;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.commons.util.IdUtils;
import org.springframework.cloud.sleuth.Log;
import org.springframework.cloud.sleuth.NoOpSpanAdjuster;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanAdjuster;
import org.springframework.cloud.sleuth.SpanReporter;
import org.springframework.cloud.sleuth.metric.SpanMetricReporter;
import org.springframework.core.env.Environment;
@@ -61,6 +63,7 @@ public class StreamSpanReporter implements SpanReporter {
private final HostLocator endpointLocator;
private final SpanMetricReporter spanMetricReporter;
private final Environment environment;
private final SpanAdjuster spanAdjuster;
@Deprecated
public StreamSpanReporter(HostLocator endpointLocator,
@@ -68,11 +71,18 @@ public class StreamSpanReporter implements SpanReporter {
this(endpointLocator, spanMetricReporter, null);
}
@Deprecated
public StreamSpanReporter(HostLocator endpointLocator,
SpanMetricReporter spanMetricReporter, Environment environment) {
this(endpointLocator, spanMetricReporter, environment, new NoOpSpanAdjuster());
}
public StreamSpanReporter(HostLocator endpointLocator,
SpanMetricReporter spanMetricReporter, Environment environment, SpanAdjuster spanAdjuster) {
this.endpointLocator = endpointLocator;
this.spanMetricReporter = spanMetricReporter;
this.environment = environment;
this.spanAdjuster = spanAdjuster;
}
public void setQueue(BlockingQueue<Span> queue) {
@@ -101,21 +111,23 @@ public class StreamSpanReporter implements SpanReporter {
@Override
public void report(Span span) {
if (span.isExportable()) {
Span spanToReport = span;
if (spanToReport.isExportable()) {
try {
if (this.environment != null) {
processLogs(span);
processLogs(spanToReport);
}
this.queue.add(span);
spanToReport = this.spanAdjuster.adjust(spanToReport);
this.queue.add(spanToReport);
} catch (Exception e) {
this.spanMetricReporter.incrementDroppedSpans(1);
if (log.isDebugEnabled()) {
log.debug("The span " + span + " will not be sent to Zipkin due to [" + e + "]");
log.debug("The span " + spanToReport + " will not be sent to Zipkin due to [" + e + "]");
}
}
} else {
if (log.isDebugEnabled()) {
log.debug("The span " + span + " will not be sent to Zipkin due to sampling");
log.debug("The span " + spanToReport + " will not be sent to Zipkin due to sampling");
}
}
}

View File

@@ -92,4 +92,23 @@ public class StreamSpanReporterTests {
.isNullOrEmpty();
}
@Test
@SuppressWarnings("unchecked")
public void should_adjust_span_before_reporting_it() throws Exception {
this.reporter = new StreamSpanReporter(this.endpointLocator, this.spanMetricReporter, null,
span -> Span.builder().from(span).name("foo").build());
LinkedBlockingQueue<Span> queue = new LinkedBlockingQueue<>(1000);
this.reporter.setQueue(queue);
Span span = Span.builder().name("bar").exportable(true).build();
span.logEvent(Span.CLIENT_SEND);
this.reporter.report(span);
assertThat(queue).isNotEmpty();
assertThat(queue.poll())
.extracting(Span::getName)
.filteredOn(o -> o.equals("foo"))
.isNotEmpty();
}
}

View File

@@ -27,6 +27,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.commons.util.InetUtils;
import org.springframework.cloud.sleuth.Sampler;
import org.springframework.cloud.sleuth.SpanAdjuster;
import org.springframework.cloud.sleuth.SpanReporter;
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
import org.springframework.cloud.sleuth.metric.SpanMetricReporter;
@@ -83,8 +84,8 @@ public class ZipkinAutoConfiguration {
@Bean
public SpanReporter zipkinSpanListener(ZipkinSpanReporter reporter, EndpointLocator endpointLocator,
Environment environment) {
return new ZipkinSpanListener(reporter, endpointLocator, environment);
Environment environment, SpanAdjuster spanAdjuster) {
return new ZipkinSpanListener(reporter, endpointLocator, environment, spanAdjuster);
}
@Configuration

View File

@@ -23,7 +23,9 @@ import java.util.Map;
import org.springframework.cloud.commons.util.IdUtils;
import org.springframework.cloud.sleuth.Log;
import org.springframework.cloud.sleuth.NoOpSpanAdjuster;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanAdjuster;
import org.springframework.cloud.sleuth.SpanReporter;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
@@ -54,6 +56,7 @@ public class ZipkinSpanListener implements SpanReporter {
private final ZipkinSpanReporter reporter;
private final Environment environment;
private final SpanAdjuster spanAdjuster;
/**
* Endpoint is the visible IP address of this service, the port it is listening on and
* the service name from discovery.
@@ -66,11 +69,18 @@ public class ZipkinSpanListener implements SpanReporter {
this(reporter, endpointLocator, null);
}
@Deprecated
public ZipkinSpanListener(ZipkinSpanReporter reporter, EndpointLocator endpointLocator,
Environment environment) {
this(reporter, endpointLocator, environment, new NoOpSpanAdjuster());
}
public ZipkinSpanListener(ZipkinSpanReporter reporter, EndpointLocator endpointLocator,
Environment environment, SpanAdjuster spanAdjuster) {
this.reporter = reporter;
this.endpointLocator = endpointLocator;
this.environment = environment;
this.spanAdjuster = spanAdjuster;
}
/**
@@ -88,34 +98,34 @@ public class ZipkinSpanListener implements SpanReporter {
// Visible for testing
zipkin.Span convert(Span span) {
//TODO: Consider adding support for the debug flag (related to #496)
Span convertedSpan = this.spanAdjuster.adjust(span);
zipkin.Span.Builder zipkinSpan = zipkin.Span.builder();
Endpoint endpoint = this.endpointLocator.local();
processLogs(span, zipkinSpan, endpoint);
addZipkinAnnotations(zipkinSpan, span, endpoint);
addZipkinBinaryAnnotations(zipkinSpan, span, endpoint);
processLogs(convertedSpan, zipkinSpan, endpoint);
addZipkinAnnotations(zipkinSpan, convertedSpan, endpoint);
addZipkinBinaryAnnotations(zipkinSpan, convertedSpan, endpoint);
// In the RPC span model, the client owns the timestamp and duration of the span. If we
// were propagated an id, we can assume that we shouldn't report timestamp or duration,
// rather let the client do that. Worst case we were propagated an unreported ID and
// Zipkin backfills timestamp and duration.
if (!span.isRemote()) {
zipkinSpan.timestamp(span.getBegin() * 1000L);
if (!span.isRunning()) { // duration is authoritative, only write when the span stopped
zipkinSpan.duration(calculateDurationInMicros(span));
if (!convertedSpan.isRemote()) {
zipkinSpan.timestamp(convertedSpan.getBegin() * 1000L);
if (!convertedSpan.isRunning()) { // duration is authoritative, only write when the span stopped
zipkinSpan.duration(calculateDurationInMicros(convertedSpan));
}
}
zipkinSpan.traceIdHigh(span.getTraceIdHigh());
zipkinSpan.traceId(span.getTraceId());
if (span.getParents().size() > 0) {
if (span.getParents().size() > 1) {
zipkinSpan.traceIdHigh(convertedSpan.getTraceIdHigh());
zipkinSpan.traceId(convertedSpan.getTraceId());
if (convertedSpan.getParents().size() > 0) {
if (convertedSpan.getParents().size() > 1) {
log.error("Zipkin doesn't support spans with multiple parents. Omitting "
+ "other parents for " + span);
+ "other parents for " + convertedSpan);
}
zipkinSpan.parentId(span.getParents().get(0));
zipkinSpan.parentId(convertedSpan.getParents().get(0));
}
zipkinSpan.id(span.getSpanId());
if (StringUtils.hasText(span.getName())) {
zipkinSpan.name(span.getName());
zipkinSpan.id(convertedSpan.getSpanId());
if (StringUtils.hasText(convertedSpan.getName())) {
zipkinSpan.name(convertedSpan.getName());
}
return zipkinSpan.build();
}

View File

@@ -27,6 +27,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.sleuth.Sampler;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanAdjuster;
import org.springframework.cloud.sleuth.SpanReporter;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.sampler.AlwaysSampler;
@@ -292,6 +293,19 @@ public class ZipkinSpanListenerTests {
.isEmpty();
}
@Test
public void should_adjust_span_before_reporting_it() {
this.parent.logEvent(Span.CLIENT_RECV);
ZipkinSpanListener spanListener = new ZipkinSpanListener(this.spanReporter,
this.endpointLocator, null, span -> Span.builder().from(span)
.name("foo")
.build());
zipkin.Span result = spanListener.convert(this.parent);
assertThat(result.name).isEqualTo("foo");
}
@Configuration
@EnableAutoConfiguration
protected static class TestConfiguration {