diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/deployer/TraceAppDeployerBeanPostProcessor.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/deployer/TraceAppDeployerBeanPostProcessor.java similarity index 91% rename from spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/deployer/TraceAppDeployerBeanPostProcessor.java rename to spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/deployer/TraceAppDeployerBeanPostProcessor.java index 616ba68df..3229300ec 100644 --- a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/deployer/TraceAppDeployerBeanPostProcessor.java +++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/deployer/TraceAppDeployerBeanPostProcessor.java @@ -14,11 +14,12 @@ * limitations under the License. */ -package org.springframework.cloud.sleuth.instrument.deployer; +package org.springframework.cloud.sleuth.autoconfig.instrument.deployer; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.cloud.deployer.spi.app.AppDeployer; +import org.springframework.cloud.sleuth.instrument.deployer.TraceAppDeployer; import org.springframework.core.env.Environment; /** diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/deployer/TraceDeployerAutoConfiguration.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/deployer/TraceDeployerAutoConfiguration.java index 45b7b577c..8c4905724 100644 --- a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/deployer/TraceDeployerAutoConfiguration.java +++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/deployer/TraceDeployerAutoConfiguration.java @@ -24,7 +24,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.deployer.spi.app.AppDeployer; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.autoconfig.brave.BraveAutoConfiguration; -import org.springframework.cloud.sleuth.instrument.deployer.TraceAppDeployerBeanPostProcessor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/deployer/TraceAppDeployer.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/deployer/TraceAppDeployer.java index 6e2ba7e38..ee63aa998 100644 --- a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/deployer/TraceAppDeployer.java +++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/deployer/TraceAppDeployer.java @@ -18,6 +18,7 @@ package org.springframework.cloud.sleuth.instrument.deployer; import java.time.Duration; import java.util.Arrays; +import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -67,17 +68,11 @@ public class TraceAppDeployer implements AppDeployer { @Override public String deploy(AppDeploymentRequest request) { - Span span = tracer().nextSpan().name("deploy"); - // TODO: Is this secure to pass? - // TODO: Does it make sense? - // if (!request.getCommandlineArguments().isEmpty()) { - // span.tag("commandlineArguments", request.getCommandlineArguments().toString()); - // } - // if (!request.getDeploymentProperties().isEmpty()) { - // span.tag("deploymentProperties", request.getDeploymentProperties().toString()); - // } - try (Tracer.SpanInScope spanInScope = tracer().withSpan(span.start())) { - span.event("start"); + Span.Builder spanBuilder = clientSpan("deploy"); + Span span = spanBuilder.start(); + // Span span = tracer().nextSpan().name("deploy"); + try (Tracer.SpanInScope spanInScope = tracer().withSpan(span)) { + span.event("deployer.start"); String id = this.delegate.deploy(request); span.tag("deployer.app.id", id); registerListener(span, id); @@ -85,6 +80,56 @@ public class TraceAppDeployer implements AppDeployer { } } + private Span.Builder clientSpan(String name) { + Span.Builder spanBuilder = tracer().spanBuilder(); + Span currentSpan = tracer().currentSpan(); + if (currentSpan != null) { + spanBuilder.setParent(currentSpan.context()); + } + return clientSpanKind(name, spanBuilder); + } + + private Span.Builder clientSpanKind(String name, Span.Builder spanBuilder) { + return spanBuilder.kind(Span.Kind.CLIENT).name(name).remoteServiceName(remoteServiceName()); + } + + private Span.Builder clientSpan(String name, Span parentSpan) { + Span.Builder spanBuilder = tracer().spanBuilder(); + Span currentSpan = parentSpan != null ? parentSpan : tracer().currentSpan(); + if (currentSpan != null) { + spanBuilder.setParent(currentSpan.context()); + } + Map platformSpecificInfo = environmentInfo().getPlatformSpecificInfo(); + addCfTags(spanBuilder, platformSpecificInfo); + addK8sTags(spanBuilder, platformSpecificInfo); + return clientSpanKind(name, spanBuilder); + } + + private void addCfTags(Span.Builder spanBuilder, Map platformSpecificInfo) { + if (platformSpecificInfo.containsKey("API Endpoint")) { + spanBuilder.tag("deployer.platform.cf.url", platformSpecificInfo.get("API Endpoint")); + } + if (platformSpecificInfo.containsKey("Organization")) { + spanBuilder.tag("deployer.platform.cf.org", platformSpecificInfo.get("Organization")); + } + if (platformSpecificInfo.containsKey("Space")) { + spanBuilder.tag("deployer.platform.cf.space", platformSpecificInfo.get("Space")); + } + } + + private void addK8sTags(Span.Builder spanBuilder, Map platformSpecificInfo) { + if (platformSpecificInfo.containsKey("master-url")) { + spanBuilder.tag("deployer.platform.k8s.url", platformSpecificInfo.get("master-url")); + } + if (platformSpecificInfo.containsKey("namespace")) { + spanBuilder.tag("deployer.platform.k8s.namespace", platformSpecificInfo.get("namespace")); + } + } + + private String remoteServiceName() { + return environmentInfo().getPlatformType(); + } + private void registerListener(Span span, String id) { PreviousAndCurrentStatus previousAndCurrentStatus = new PreviousAndCurrentStatus(span); // @formatter:off @@ -102,10 +147,11 @@ public class TraceAppDeployer implements AppDeployer { @Override public void undeploy(String id) { - Span span = tracer().nextSpan().name("undeploy"); + Span.Builder spanBuilder = clientSpan("undeploy"); + Span span = spanBuilder.start(); span.tag("deployer.app.id", id); - try (Tracer.SpanInScope spanInScope = tracer().withSpan(span.start())) { - span.event("start"); + try (Tracer.SpanInScope spanInScope = tracer().withSpan(span)) { + span.event("deployer.start"); this.delegate.undeploy(id); registerListener(span, id); } @@ -116,7 +162,8 @@ public class TraceAppDeployer implements AppDeployer { @Override public AppStatus status(String id) { - Span span = tracer().nextSpan().name("status"); + Span.Builder spanBuilder = clientSpan("status"); + Span span = spanBuilder.start(); span.tag("deployer.app.id", id); try (Tracer.SpanInScope spanInScope = tracer().withSpan(span.start())) { return this.delegate.status(id); @@ -129,14 +176,16 @@ public class TraceAppDeployer implements AppDeployer { @Override public Mono statusReactive(String id) { return ReactorSleuth.tracedMono(tracer(), currentTraceContext(), "status", - () -> this.delegate.statusReactive(id), (o, span) -> span.tag("deployer.app.id", id)); + () -> this.delegate.statusReactive(id), (o, span) -> span.tag("deployer.app.id", id), + span -> clientSpan("status", span).start()); } @Override public Flux statusesReactive(String... ids) { return ReactorSleuth.tracedFlux(tracer(), currentTraceContext(), "statuses", () -> this.delegate.statusesReactive(ids), - (o, span) -> span.tag("deployer.app.ids", Arrays.toString(ids))); + (o, span) -> span.tag("deployer.app.ids", Arrays.toString(ids)), + span -> clientSpan("statuses", span).start()); } @Override @@ -146,9 +195,10 @@ public class TraceAppDeployer implements AppDeployer { @Override public String getLog(String id) { - Span span = tracer().nextSpan().name("getLog"); + Span.Builder spanBuilder = clientSpan("getLog"); + Span span = spanBuilder.start(); span.tag("deployer.app.id", id); - try (Tracer.SpanInScope spanInScope = tracer().withSpan(span.start())) { + try (Tracer.SpanInScope spanInScope = tracer().withSpan(span)) { return this.delegate.getLog(id); } finally { @@ -158,15 +208,10 @@ public class TraceAppDeployer implements AppDeployer { @Override public void scale(AppScaleRequest appScaleRequest) { - Span span = tracer().nextSpan().name("scale"); + Span.Builder spanBuilder = clientSpan("scale"); + Span span = spanBuilder.start(); span.tag("deployer.scale.deploymentId", appScaleRequest.getDeploymentId()); span.tag("deployer.scale.count", String.valueOf(appScaleRequest.getCount())); - // TODO: Is this secure to pass? - // TODO: Does it make sense? - // if (appScaleRequest.getProperties().isPresent() && - // !appScaleRequest.getProperties().get().isEmpty()) { - // span.tag("properties", appScaleRequest.getProperties().get().toString()); - // } try (Tracer.SpanInScope spanInScope = tracer().withSpan(span.start())) { this.delegate.scale(appScaleRequest); } diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/ReactorSleuth.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/ReactorSleuth.java index 45965a763..d81da13ac 100644 --- a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/ReactorSleuth.java +++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/ReactorSleuth.java @@ -338,6 +338,25 @@ public abstract class ReactorSleuth { context -> ReactorSleuth.enhanceContext(tracer, currentTraceContext, context, childSpanName)); } + /** + * Wraps the given Mono in a trace representation. Retrieves the span from context, + * creates a child span with the given name. + * @param tracer - Tracer bean + * @param currentTraceContext - CurrentTraceContext bean + * @param childSpanName - name of the created child span + * @param supplier - supplier of a {@link Mono} to be wrapped in tracing + * @param - type returned by the Mono + * @param spanCustomizer - customizer for the child span + * @param spanFunction - function that creates a new or child span + * @return traced Mono + */ + public static Mono tracedMono(@NonNull Tracer tracer, @NonNull CurrentTraceContext currentTraceContext, + @NonNull String childSpanName, @NonNull Supplier> supplier, + @NonNull BiConsumer spanCustomizer, @NonNull Function spanFunction) { + return runMonoSupplierInScope(supplier, spanCustomizer).contextWrite(context -> ReactorSleuth + .enhanceContext(tracer, currentTraceContext, context, childSpanName, spanFunction)); + } + private static Mono runMonoSupplierInScope(Supplier> supplier, BiConsumer spanCustomizer) { return Mono.deferContextual(contextView -> { Span span = contextView.get(Span.class); @@ -407,6 +426,25 @@ public abstract class ReactorSleuth { context -> ReactorSleuth.enhanceContext(tracer, currentTraceContext, context, childSpanName)); } + /** + * Wraps the given Flux in a trace representation. Retrieves the span from context, + * creates a child span with the given name. + * @param tracer - Tracer bean + * @param currentTraceContext - CurrentTraceContext bean + * @param childSpanName - name of the created child span + * @param supplier - supplier of a {@link Flux} to be wrapped in tracing + * @param - type returned by the Flux + * @param spanCustomizer - customizer for the child span + * @param spanFunction - function that creates a new or child span + * @return traced Flux + */ + public static Flux tracedFlux(@NonNull Tracer tracer, @NonNull CurrentTraceContext currentTraceContext, + @NonNull String childSpanName, @NonNull Supplier> supplier, + @NonNull BiConsumer spanCustomizer, @NonNull Function spanFunction) { + return runFluxSupplierInScope(supplier, spanCustomizer).contextWrite(context -> ReactorSleuth + .enhanceContext(tracer, currentTraceContext, context, childSpanName, spanFunction)); + } + /** * Wraps the given Flux in a trace representation. Retrieves the span from context, * creates a child span with the given name. @@ -461,10 +499,16 @@ public abstract class ReactorSleuth { private static Span childSpanFromContext(Tracer tracer, CurrentTraceContext currentTraceContext, reactor.util.context.Context context, String childSpanName) { + return childSpanFromContext(currentTraceContext, context, childSpanName, + span -> span == null ? tracer.nextSpan() : tracer.nextSpan(span)); + } + + private static Span childSpanFromContext(CurrentTraceContext currentTraceContext, + reactor.util.context.Context context, String childSpanName, Function spanSupplier) { TraceContext traceContext = context.getOrDefault(TraceContext.class, null); Span span = context.getOrDefault(Span.class, null); if (traceContext == null && span == null) { - span = tracer.nextSpan(); + span = spanSupplier.apply(null); if (log.isDebugEnabled()) { log.debug("There was no previous span in reactor context, created a new one [" + span + "]"); } @@ -475,7 +519,7 @@ public abstract class ReactorSleuth { if (log.isDebugEnabled()) { log.debug("Found a trace context in reactor context [" + traceContext + "]"); } - span = tracer.nextSpan(); + span = spanSupplier.apply(null); if (log.isDebugEnabled()) { log.debug("Created a child span [" + span + "]"); } @@ -485,7 +529,7 @@ public abstract class ReactorSleuth { if (log.isDebugEnabled()) { log.debug("Found a span in reactor context [" + span + "]"); } - span = tracer.nextSpan(span); + span = spanSupplier.apply(span); if (log.isDebugEnabled()) { log.debug("Created a child span [" + span + "]"); } @@ -493,6 +537,22 @@ public abstract class ReactorSleuth { return span.name(childSpanName).start(); } + /** + * Updates the Reactor context with tracing information. Creates a new span if there + * is no current span. Creates a child span if there was an entry in the context + * already. + * @param currentTraceContext current trace context + * @param context Reactor context + * @param childSpanName child span name when there is no span in context + * @param spanSupplier function that creates a new or child span + * @return updated Reactor context + */ + public static Context enhanceContext(Tracer tracer, CurrentTraceContext currentTraceContext, + reactor.util.context.Context context, String childSpanName, Function spanSupplier) { + Span span = childSpanFromContext(currentTraceContext, context, childSpanName, spanSupplier); + return putSpanInScope(tracer, context, span); + } + /** * Updates the Reactor context with tracing information. Creates a new span if there * is no current span. Creates a child span if there was an entry in the context diff --git a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/deployer/TraceAppDeployerTests.java b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/deployer/TraceAppDeployerTests.java index 008ff5b59..208abe3f3 100644 --- a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/deployer/TraceAppDeployerTests.java +++ b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/deployer/TraceAppDeployerTests.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.Map; import org.assertj.core.api.BDDAssertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.BDDMockito; import reactor.core.publisher.Flux; @@ -34,6 +35,7 @@ import org.springframework.cloud.deployer.spi.app.AppScaleRequest; import org.springframework.cloud.deployer.spi.app.AppStatus; import org.springframework.cloud.deployer.spi.core.AppDefinition; import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest; +import org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo; import org.springframework.cloud.sleuth.tracer.NoOpCurrentTraceContext; import org.springframework.cloud.sleuth.tracer.SimpleTracer; import org.springframework.core.env.Environment; @@ -48,6 +50,14 @@ class TraceAppDeployerTests { TraceAppDeployer traceAppDeployer = new TraceAppDeployer(this.delegate, beanFactory(), environment()); + @BeforeEach + void setup() { + BDDMockito.given(this.delegate.environmentInfo()) + .willReturn(new RuntimeEnvironmentInfo.Builder().spiClass(Object.class).implementationName("asd") + .implementationVersion("asd").platformType("asd").platformApiVersion("asd") + .platformClientVersion("asd").platformHostVersion("asd").build()); + } + @Test void should_trace_deploy() { BDDMockito.given(this.delegate.statusReactive(BDDMockito.any())) diff --git a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/SimpleTracer.java b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/SimpleTracer.java index d03c15227..beeb01728 100644 --- a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/SimpleTracer.java +++ b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/SimpleTracer.java @@ -1,128 +1,128 @@ -/* - * Copyright 2013-2021 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 - * - * https://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.tracer; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.assertj.core.api.BDDAssertions; - -import org.springframework.cloud.sleuth.BaggageInScope; -import org.springframework.cloud.sleuth.ScopedSpan; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanCustomizer; -import org.springframework.cloud.sleuth.TraceContext; -import org.springframework.cloud.sleuth.Tracer; - -/** - * A noop implementation. Does nothing. - * - * @author Marcin Grzejszczak - * @since 3.0.0 - */ -public class SimpleTracer implements Tracer { - - public List spans = new ArrayList<>(); - - @Override - public Span nextSpan(Span parent) { - return new SimpleSpan(); - } - - public SimpleSpan getOnlySpan() { - BDDAssertions.then(this.spans).hasSize(1); - SimpleSpan span = this.spans.get(0); - BDDAssertions.then(span.started).as("Span must be started").isTrue(); - BDDAssertions.then(span.ended).as("Span must be finished").isTrue(); - return span; - } - - public SimpleSpan getLastSpan() { - BDDAssertions.then(this.spans).isNotEmpty(); - SimpleSpan span = this.spans.get(this.spans.size() - 1); - BDDAssertions.then(span.started).as("Span must be started").isTrue(); - return span; - } - - @Override - public SpanInScope withSpan(Span span) { - return new NoOpSpanInScope(); - } - - @Override - public SpanCustomizer currentSpanCustomizer() { - return null; - } - - @Override - public Span currentSpan() { - if (this.spans.isEmpty()) { - return null; - } - return this.spans.get(spans.size() - 1); - } - - @Override - public SimpleSpan nextSpan() { - final SimpleSpan span = new SimpleSpan(); - this.spans.add(span); - return span; - } - - @Override - public ScopedSpan startScopedSpan(String name) { - return null; - } - - @Override - public Span.Builder spanBuilder() { - return new SimpleSpanBuilder(); - } - - @Override - public TraceContext.Builder traceContextBuilder() { - return null; - } - - @Override - public Map getAllBaggage() { - return new HashMap<>(); - } - - @Override - public BaggageInScope getBaggage(String name) { - return null; - } - - @Override - public BaggageInScope getBaggage(TraceContext traceContext, String name) { - return null; - } - - @Override - public BaggageInScope createBaggage(String name) { - return null; - } - - @Override - public BaggageInScope createBaggage(String name, String value) { - return null; - } - -} +/* + * Copyright 2013-2021 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 + * + * https://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.tracer; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.assertj.core.api.BDDAssertions; + +import org.springframework.cloud.sleuth.BaggageInScope; +import org.springframework.cloud.sleuth.ScopedSpan; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.SpanCustomizer; +import org.springframework.cloud.sleuth.TraceContext; +import org.springframework.cloud.sleuth.Tracer; + +/** + * A noop implementation. Does nothing. + * + * @author Marcin Grzejszczak + * @since 3.0.0 + */ +public class SimpleTracer implements Tracer { + + public List spans = new ArrayList<>(); + + @Override + public Span nextSpan(Span parent) { + return new SimpleSpan(); + } + + public SimpleSpan getOnlySpan() { + BDDAssertions.then(this.spans).hasSize(1); + SimpleSpan span = this.spans.get(0); + BDDAssertions.then(span.started).as("Span must be started").isTrue(); + BDDAssertions.then(span.ended).as("Span must be finished").isTrue(); + return span; + } + + public SimpleSpan getLastSpan() { + BDDAssertions.then(this.spans).isNotEmpty(); + SimpleSpan span = this.spans.get(this.spans.size() - 1); + BDDAssertions.then(span.started).as("Span must be started").isTrue(); + return span; + } + + @Override + public SpanInScope withSpan(Span span) { + return new NoOpSpanInScope(); + } + + @Override + public SpanCustomizer currentSpanCustomizer() { + return null; + } + + @Override + public Span currentSpan() { + if (this.spans.isEmpty()) { + return null; + } + return this.spans.get(spans.size() - 1); + } + + @Override + public SimpleSpan nextSpan() { + final SimpleSpan span = new SimpleSpan(); + this.spans.add(span); + return span; + } + + @Override + public ScopedSpan startScopedSpan(String name) { + return null; + } + + @Override + public Span.Builder spanBuilder() { + return new SimpleSpanBuilder(); + } + + @Override + public TraceContext.Builder traceContextBuilder() { + return null; + } + + @Override + public Map getAllBaggage() { + return new HashMap<>(); + } + + @Override + public BaggageInScope getBaggage(String name) { + return null; + } + + @Override + public BaggageInScope getBaggage(TraceContext traceContext, String name) { + return null; + } + + @Override + public BaggageInScope createBaggage(String name) { + return null; + } + + @Override + public BaggageInScope createBaggage(String name, String value) { + return null; + } + +}