Spring Cloud Deployer feature (#1925)
* Spring Cloud Deployer; fixes gh-1905 * Polish
This commit is contained in:
committed by
GitHub
parent
c4ab9dcd49
commit
0f23f9d52f
@@ -19,22 +19,16 @@ package org.springframework.cloud.sleuth.instrument.circuitbreaker;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.context.Context;
|
||||
|
||||
import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreaker;
|
||||
import org.springframework.cloud.sleuth.CurrentTraceContext;
|
||||
import org.springframework.cloud.sleuth.Span;
|
||||
import org.springframework.cloud.sleuth.TraceContext;
|
||||
import org.springframework.cloud.sleuth.Tracer;
|
||||
import org.springframework.cloud.sleuth.instrument.reactor.ReactorSleuth;
|
||||
|
||||
class TraceReactiveCircuitBreaker implements ReactiveCircuitBreaker {
|
||||
|
||||
private static final Log log = LogFactory.getLog(TraceReactiveCircuitBreaker.class);
|
||||
|
||||
private final ReactiveCircuitBreaker delegate;
|
||||
|
||||
private final Tracer tracer;
|
||||
@@ -71,68 +65,11 @@ class TraceReactiveCircuitBreaker implements ReactiveCircuitBreaker {
|
||||
}
|
||||
|
||||
private <T> Mono<T> runAndTraceMono(Supplier<Mono<T>> mono) {
|
||||
return Mono.deferContextual(contextView -> {
|
||||
Span span = contextView.get(Span.class);
|
||||
Tracer.SpanInScope scope = contextView.get(Tracer.SpanInScope.class);
|
||||
return mono.get().doOnError(span::error).doFinally(signalType -> {
|
||||
span.end();
|
||||
scope.close();
|
||||
});
|
||||
}).contextWrite(this::enhanceContext);
|
||||
return ReactorSleuth.tracedMono(this.tracer, this.currentTraceContext, "function", mono);
|
||||
}
|
||||
|
||||
private <T> Flux<T> runAndTraceFlux(Supplier<Flux<T>> flux) {
|
||||
return Flux.deferContextual(contextView -> {
|
||||
Span span = contextView.get(Span.class);
|
||||
Tracer.SpanInScope scope = contextView.get(Tracer.SpanInScope.class);
|
||||
return flux.get().doOnError(span::error).doFinally(signalType -> {
|
||||
span.end();
|
||||
scope.close();
|
||||
});
|
||||
}).contextWrite(this::enhanceContext);
|
||||
}
|
||||
|
||||
private Span spanFromContext(reactor.util.context.Context context) {
|
||||
TraceContext traceContext = context.getOrDefault(TraceContext.class, null);
|
||||
Span span = null;
|
||||
if (traceContext == null) {
|
||||
span = context.getOrDefault(Span.class, null);
|
||||
}
|
||||
if (traceContext == null && span == null) {
|
||||
span = this.tracer.nextSpan();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("There was no previous span in reactor context, created a new one [" + span + "]");
|
||||
}
|
||||
}
|
||||
else if (traceContext != null) {
|
||||
// there was a previous span - we create a child one
|
||||
try (CurrentTraceContext.Scope scope = this.currentTraceContext.maybeScope(traceContext)) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Found a trace context in reactor context [" + traceContext + "]");
|
||||
}
|
||||
span = this.tracer.nextSpan();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Created a child span [" + span + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Found a span in reactor context [" + span + "]");
|
||||
}
|
||||
span = this.tracer.nextSpan(span);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Created a child span [" + span + "]");
|
||||
}
|
||||
}
|
||||
// TODO: Better name?
|
||||
return span.name("function");
|
||||
}
|
||||
|
||||
private Context enhanceContext(Context context) {
|
||||
Span span = spanFromContext(context);
|
||||
return context.put(Span.class, span).put(TraceContext.class, span.context()).put(Tracer.SpanInScope.class,
|
||||
this.tracer.withSpan(span));
|
||||
return ReactorSleuth.tracedFlux(this.tracer, this.currentTraceContext, "function", flux);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* Copyright 2018-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.instrument.deployer;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.cloud.deployer.spi.app.AppDeployer;
|
||||
import org.springframework.cloud.deployer.spi.app.AppScaleRequest;
|
||||
import org.springframework.cloud.deployer.spi.app.AppStatus;
|
||||
import org.springframework.cloud.deployer.spi.app.DeploymentState;
|
||||
import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;
|
||||
import org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;
|
||||
import org.springframework.cloud.sleuth.CurrentTraceContext;
|
||||
import org.springframework.cloud.sleuth.Span;
|
||||
import org.springframework.cloud.sleuth.Tracer;
|
||||
import org.springframework.cloud.sleuth.instrument.reactor.ReactorSleuth;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/**
|
||||
* Trace representation of an {@link AppDeployer}.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public class TraceAppDeployer implements AppDeployer {
|
||||
|
||||
private static final Log log = LogFactory.getLog(TraceAppDeployer.class);
|
||||
|
||||
private final AppDeployer delegate;
|
||||
|
||||
private final BeanFactory beanFactory;
|
||||
|
||||
private final Environment environment;
|
||||
|
||||
private Tracer tracer;
|
||||
|
||||
private CurrentTraceContext currentTraceContext;
|
||||
|
||||
private Long pollDelay;
|
||||
|
||||
public TraceAppDeployer(AppDeployer delegate, BeanFactory beanFactory, Environment environment) {
|
||||
this.delegate = delegate;
|
||||
this.beanFactory = beanFactory;
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@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");
|
||||
String id = this.delegate.deploy(request);
|
||||
span.tag("id", id);
|
||||
registerListener(span, id);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
private void registerListener(Span span, String id) {
|
||||
PreviousAndCurrentStatus previousAndCurrentStatus = new PreviousAndCurrentStatus(span);
|
||||
// @formatter:off
|
||||
this.delegate.statusReactive(id)
|
||||
.map(previousAndCurrentStatus::updateCurrent)
|
||||
.repeatWhen(repeat -> repeat.flatMap(i -> Mono.delay(Duration.ofMillis(pollDelay()))))
|
||||
.takeUntil(PreviousAndCurrentStatus::isFinished)
|
||||
.last()
|
||||
.doOnNext(PreviousAndCurrentStatus::annotateSpan)
|
||||
.doOnError(span::error)
|
||||
// we will close the span in the reactive part
|
||||
.doFinally(signalType -> span.end()).subscribe();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undeploy(String id) {
|
||||
Span span = tracer().nextSpan().name("undeploy");
|
||||
span.tag("id", id);
|
||||
try (Tracer.SpanInScope spanInScope = tracer().withSpan(span.start())) {
|
||||
span.event("start");
|
||||
this.delegate.undeploy(id);
|
||||
registerListener(span, id);
|
||||
}
|
||||
finally {
|
||||
span.end();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AppStatus status(String id) {
|
||||
Span span = tracer().nextSpan().name("status");
|
||||
span.tag("id", id);
|
||||
try (Tracer.SpanInScope spanInScope = tracer().withSpan(span.start())) {
|
||||
return this.delegate.status(id);
|
||||
}
|
||||
finally {
|
||||
span.end();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<AppStatus> statusReactive(String id) {
|
||||
return ReactorSleuth.tracedMono(tracer(), currentTraceContext(), "status",
|
||||
() -> this.delegate.statusReactive(id), span -> span.tag("id", id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<AppStatus> statusesReactive(String... ids) {
|
||||
return ReactorSleuth.tracedFlux(tracer(), currentTraceContext(), "statuses",
|
||||
() -> this.delegate.statusesReactive(ids), span -> span.tag("ids", Arrays.toString(ids)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuntimeEnvironmentInfo environmentInfo() {
|
||||
return this.delegate.environmentInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLog(String id) {
|
||||
Span span = tracer().nextSpan().name("getLog");
|
||||
span.tag("id", id);
|
||||
try (Tracer.SpanInScope spanInScope = tracer().withSpan(span.start())) {
|
||||
return this.delegate.getLog(id);
|
||||
}
|
||||
finally {
|
||||
span.end();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scale(AppScaleRequest appScaleRequest) {
|
||||
Span span = tracer().nextSpan().name("scale");
|
||||
span.tag("deploymentId", appScaleRequest.getDeploymentId());
|
||||
span.tag("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);
|
||||
}
|
||||
finally {
|
||||
span.end();
|
||||
}
|
||||
}
|
||||
|
||||
private Tracer tracer() {
|
||||
if (this.tracer == null) {
|
||||
this.tracer = this.beanFactory.getBean(Tracer.class);
|
||||
}
|
||||
return this.tracer;
|
||||
}
|
||||
|
||||
private CurrentTraceContext currentTraceContext() {
|
||||
if (this.currentTraceContext == null) {
|
||||
this.currentTraceContext = this.beanFactory.getBean(CurrentTraceContext.class);
|
||||
}
|
||||
return this.currentTraceContext;
|
||||
}
|
||||
|
||||
private long pollDelay() {
|
||||
if (this.pollDelay == null) {
|
||||
this.pollDelay = this.environment.getProperty("spring.sleuth.deployer.status-poll-delay", Long.class, 500L);
|
||||
}
|
||||
return this.pollDelay;
|
||||
}
|
||||
|
||||
private static final class PreviousAndCurrentStatus {
|
||||
|
||||
private final Span span;
|
||||
|
||||
private AppStatus current;
|
||||
|
||||
private AppStatus previous;
|
||||
|
||||
private PreviousAndCurrentStatus(Span span) {
|
||||
this.span = span;
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Current span is [" + span + "]");
|
||||
}
|
||||
}
|
||||
|
||||
private PreviousAndCurrentStatus updateCurrent(AppStatus current) {
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("State before change: current [" + this.current + "], previous [" + this.previous + "]");
|
||||
}
|
||||
this.previous = this.current;
|
||||
this.current = current;
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("State after change: current [" + this.current + "], previous [" + this.previous + "]");
|
||||
}
|
||||
if (statusChanged()) {
|
||||
annotateSpan();
|
||||
}
|
||||
else if (log.isTraceEnabled()) {
|
||||
log.trace("State has not changed, will not annotate the span");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private void annotateSpan() {
|
||||
String name = this.current.getState().name();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Will annotate its state with [" + name + "]");
|
||||
}
|
||||
this.span.event(name);
|
||||
}
|
||||
|
||||
private boolean statusChanged() {
|
||||
if (this.previous == null && this.current != null) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Previous is null, current is not null");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (this.current == null) {
|
||||
throw new IllegalStateException("Current state can't be null");
|
||||
}
|
||||
DeploymentState currentState = this.current.getState();
|
||||
DeploymentState previousState = this.previous.getState();
|
||||
return currentState != previousState;
|
||||
}
|
||||
|
||||
private boolean isFinished() {
|
||||
boolean finished = this.current.getState() == DeploymentState.deployed
|
||||
|| this.current.getState() == DeploymentState.undeployed
|
||||
|| this.current.getState() == DeploymentState.failed
|
||||
|| this.current.getState() == DeploymentState.error
|
||||
|| this.current.getState() == DeploymentState.unknown;
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("Status is finished [" + finished + "]");
|
||||
}
|
||||
return finished;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.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.core.env.Environment;
|
||||
|
||||
/**
|
||||
* {@link BeanPostProcessor} to wrap a {@link AppDeployer} instance into its trace
|
||||
* representation.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class TraceAppDeployerBeanPostProcessor implements BeanPostProcessor {
|
||||
|
||||
private final BeanFactory beanFactory;
|
||||
|
||||
private final Environment environment;
|
||||
|
||||
public TraceAppDeployerBeanPostProcessor(BeanFactory beanFactory, Environment environment) {
|
||||
this.beanFactory = beanFactory;
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) {
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) {
|
||||
if (bean instanceof AppDeployer && !(bean instanceof TraceAppDeployer)) {
|
||||
return new TraceAppDeployer((AppDeployer) bean, this.beanFactory, this.environment);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -60,13 +60,13 @@ import org.springframework.util.Assert;
|
||||
* .scan((l, r) -> l + r) // (-)
|
||||
* .doOnNext(it -> { // (-)
|
||||
* //log
|
||||
* })
|
||||
* })
|
||||
* .doFirst(() -> { // (-)
|
||||
* //log
|
||||
* })
|
||||
* })
|
||||
* .doFinally(signalType -> { // (-)
|
||||
* //log
|
||||
* })
|
||||
* })
|
||||
* .subscribeOn(Schedulers.parallel()) //(+)
|
||||
* .subscribe();//(*)
|
||||
* (*) - captures tracing context if it differs from what was captured before at subscription and propagates it.
|
||||
@@ -84,7 +84,7 @@ import org.springframework.util.Assert;
|
||||
* .map(it -> ...) // (+) is SYNC but should add hook as previous Processor/operator does not use hooks
|
||||
* .doOnNext(it -> { // (-) is SYNC no need to wrap
|
||||
* //log
|
||||
* })
|
||||
* })
|
||||
* .subscribe();
|
||||
*}</pre>
|
||||
*/
|
||||
@@ -93,6 +93,7 @@ final class ReactorHooksHelper {
|
||||
// need a way to determine SYNC sources to not add redundant scope passing decorator
|
||||
// most of reactor-core SYNC sources are marked with SourceProducer interface
|
||||
static final Class<?> sourceProducerClass;
|
||||
|
||||
static {
|
||||
Class<?> c;
|
||||
try {
|
||||
|
||||
@@ -17,8 +17,10 @@
|
||||
package org.springframework.cloud.sleuth.instrument.reactor;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -28,16 +30,20 @@ import org.reactivestreams.Subscription;
|
||||
import reactor.core.CoreSubscriber;
|
||||
import reactor.core.Fuseable;
|
||||
import reactor.core.Scannable;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Hooks;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.publisher.Operators;
|
||||
import reactor.util.annotation.Nullable;
|
||||
import reactor.util.context.Context;
|
||||
|
||||
import org.springframework.cloud.sleuth.CurrentTraceContext;
|
||||
import org.springframework.cloud.sleuth.Span;
|
||||
import org.springframework.cloud.sleuth.TraceContext;
|
||||
import org.springframework.cloud.sleuth.Tracer;
|
||||
import org.springframework.cloud.sleuth.internal.LazyBean;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Reactive Span pointcuts factories.
|
||||
@@ -314,6 +320,132 @@ public abstract class ReactorSleuth {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 <T> - type returned by the Mono
|
||||
* @param spanCustomizer - customizer for the child span
|
||||
* @return traced Mono
|
||||
*/
|
||||
public static <T> Mono<T> tracedMono(@NonNull Tracer tracer, @NonNull CurrentTraceContext currentTraceContext,
|
||||
@NonNull String childSpanName, @NonNull Supplier<Mono<T>> supplier,
|
||||
@NonNull Consumer<Span> spanCustomizer) {
|
||||
return Mono.deferContextual(contextView -> {
|
||||
Span span = contextView.get(Span.class);
|
||||
spanCustomizer.accept(span);
|
||||
Tracer.SpanInScope scope = contextView.get(Tracer.SpanInScope.class);
|
||||
return supplier.get().doOnError(span::error).doFinally(signalType -> {
|
||||
span.end();
|
||||
scope.close();
|
||||
});
|
||||
}).contextWrite(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 <T> - type returned by the Mono
|
||||
* @return traced Mono
|
||||
*/
|
||||
public static <T> Mono<T> tracedMono(@NonNull Tracer tracer, @NonNull CurrentTraceContext currentTraceContext,
|
||||
@NonNull String childSpanName, @NonNull Supplier<Mono<T>> supplier) {
|
||||
return tracedMono(tracer, currentTraceContext, childSpanName, supplier, span -> {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 <T> - type returned by the Flux
|
||||
* @param spanCustomizer - customizer for the child span
|
||||
* @return traced Flux
|
||||
*/
|
||||
public static <T> Flux<T> tracedFlux(@NonNull Tracer tracer, @NonNull CurrentTraceContext currentTraceContext,
|
||||
@NonNull String childSpanName, @NonNull Supplier<Flux<T>> supplier,
|
||||
@NonNull Consumer<Span> spanCustomizer) {
|
||||
return Flux.deferContextual(contextView -> {
|
||||
Span span = contextView.get(Span.class);
|
||||
spanCustomizer.accept(span);
|
||||
Tracer.SpanInScope scope = contextView.get(Tracer.SpanInScope.class);
|
||||
return supplier.get().doOnError(span::error).doFinally(signalType -> {
|
||||
span.end();
|
||||
scope.close();
|
||||
});
|
||||
}).contextWrite(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 <T> - type returned by the Flux
|
||||
* @return traced Flux
|
||||
*/
|
||||
public static <T> Flux<T> tracedFlux(@NonNull Tracer tracer, @NonNull CurrentTraceContext currentTraceContext,
|
||||
@NonNull String childSpanName, @NonNull Supplier<Flux<T>> supplier) {
|
||||
return tracedFlux(tracer, currentTraceContext, childSpanName, supplier, span -> {
|
||||
});
|
||||
}
|
||||
|
||||
private static Span spanFromContext(Tracer tracer, CurrentTraceContext currentTraceContext,
|
||||
reactor.util.context.Context context, String childSpanName) {
|
||||
TraceContext traceContext = context.getOrDefault(TraceContext.class, null);
|
||||
Span span = null;
|
||||
if (traceContext == null) {
|
||||
span = context.getOrDefault(Span.class, null);
|
||||
}
|
||||
if (traceContext == null && span == null) {
|
||||
span = tracer.nextSpan();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("There was no previous span in reactor context, created a new one [" + span + "]");
|
||||
}
|
||||
}
|
||||
else if (traceContext != null) {
|
||||
// there was a previous span - we create a child one
|
||||
try (CurrentTraceContext.Scope scope = currentTraceContext.maybeScope(traceContext)) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Found a trace context in reactor context [" + traceContext + "]");
|
||||
}
|
||||
span = tracer.nextSpan();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Created a child span [" + span + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Found a span in reactor context [" + span + "]");
|
||||
}
|
||||
span = tracer.nextSpan(span);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Created a child span [" + span + "]");
|
||||
}
|
||||
}
|
||||
return span.name(childSpanName).start();
|
||||
}
|
||||
|
||||
private static Context enhanceContext(Tracer tracer, CurrentTraceContext currentTraceContext,
|
||||
reactor.util.context.Context context, String childSpanName) {
|
||||
Span span = spanFromContext(tracer, currentTraceContext, context, childSpanName);
|
||||
return context.put(Span.class, span).put(TraceContext.class, span.context()).put(Tracer.SpanInScope.class,
|
||||
tracer.withSpan(span));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SleuthContextOperator<T> implements Subscription, CoreSubscriber<T>, Scannable {
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.instrument.deployer;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import org.springframework.cloud.sleuth.CurrentTraceContext;
|
||||
import org.springframework.cloud.sleuth.TraceContext;
|
||||
|
||||
/**
|
||||
* A noop implementation. Does nothing.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 3.0.0
|
||||
*/
|
||||
class NoOpCurrentTraceContext implements CurrentTraceContext {
|
||||
|
||||
@Override
|
||||
public TraceContext context() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scope newScope(TraceContext context) {
|
||||
return () -> {
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scope maybeScope(TraceContext context) {
|
||||
return () -> {
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public <C> Callable<C> wrap(Callable<C> task) {
|
||||
return task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Runnable wrap(Runnable task) {
|
||||
return task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Executor wrap(Executor delegate) {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutorService wrap(ExecutorService delegate) {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.instrument.deployer;
|
||||
|
||||
import org.springframework.cloud.sleuth.Tracer;
|
||||
|
||||
/**
|
||||
* A noop implementation. Does nothing.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 3.0.0
|
||||
*/
|
||||
class NoOpSpanInScope implements Tracer.SpanInScope {
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.instrument.deployer;
|
||||
|
||||
import org.springframework.cloud.sleuth.TraceContext;
|
||||
|
||||
/**
|
||||
* A noop implementation. Does nothing.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 3.0.0
|
||||
*/
|
||||
class NoOpTraceContext implements TraceContext {
|
||||
|
||||
@Override
|
||||
public String traceId() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String parentId() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String spanId() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean sampled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.instrument.deployer;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.cloud.sleuth.Span;
|
||||
import org.springframework.cloud.sleuth.TraceContext;
|
||||
|
||||
/**
|
||||
* A noop implementation. Does nothing.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 3.0.0
|
||||
*/
|
||||
class SimpleSpan implements Span {
|
||||
|
||||
Map<String, String> tags = new HashMap<>();
|
||||
|
||||
boolean started;
|
||||
|
||||
boolean ended;
|
||||
|
||||
@Override
|
||||
public boolean isNoop() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceContext context() {
|
||||
return new NoOpTraceContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Span start() {
|
||||
this.started = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Span name(String name) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Span event(String value) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Span tag(String key, String value) {
|
||||
this.tags.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Span error(Throwable throwable) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
this.ended = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void abandon() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Span remoteServiceName(String remoteServiceName) {
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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.instrument.deployer;
|
||||
|
||||
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
|
||||
*/
|
||||
class SimpleTracer implements Tracer {
|
||||
|
||||
List<SimpleSpan> spans = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public Span nextSpan(Span parent) {
|
||||
return new SimpleSpan();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpanInScope withSpan(Span span) {
|
||||
return new NoOpSpanInScope();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpanCustomizer currentSpanCustomizer() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Span currentSpan() {
|
||||
return new SimpleSpan();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Span 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 null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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.instrument.deployer;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.assertj.core.api.BDDAssertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.BDDMockito;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.support.StaticListableBeanFactory;
|
||||
import org.springframework.cloud.deployer.spi.app.AppDeployer;
|
||||
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.core.env.Environment;
|
||||
import org.springframework.core.io.PathResource;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
|
||||
class TraceAppDeployerTests {
|
||||
|
||||
SimpleTracer simpleTracer = new SimpleTracer();
|
||||
|
||||
AppDeployer delegate = BDDMockito.mock(AppDeployer.class);
|
||||
|
||||
TraceAppDeployer traceAppDeployer = new TraceAppDeployer(this.delegate, beanFactory(), environment());
|
||||
|
||||
@Test
|
||||
void should_trace_deploy() {
|
||||
BDDMockito.given(this.delegate.statusReactive(BDDMockito.any()))
|
||||
.willReturn(Mono.just(AppStatus.of("asd").build()));
|
||||
|
||||
this.traceAppDeployer.deploy(deploymentRequest());
|
||||
|
||||
BDDAssertions.then(this.simpleTracer.getOnlySpan().tags).isNotEmpty();
|
||||
BDDMockito.then(this.delegate).should().deploy(BDDMockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_trace_undeploy() {
|
||||
BDDMockito.given(this.delegate.statusReactive(BDDMockito.any()))
|
||||
.willReturn(Mono.just(AppStatus.of("asd").build()));
|
||||
|
||||
this.traceAppDeployer.undeploy("asd");
|
||||
|
||||
BDDAssertions.then(this.simpleTracer.getOnlySpan().tags).isNotEmpty();
|
||||
BDDMockito.then(this.delegate).should().undeploy(BDDMockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_trace_status() {
|
||||
this.traceAppDeployer.status("asd");
|
||||
|
||||
BDDAssertions.then(this.simpleTracer.getOnlySpan().tags).isNotEmpty();
|
||||
BDDMockito.then(this.delegate).should().status(BDDMockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_trace_status_reactive() {
|
||||
BDDMockito.given(this.delegate.statusReactive(BDDMockito.any()))
|
||||
.willReturn(Mono.just(AppStatus.of("asd").build()));
|
||||
|
||||
this.traceAppDeployer.statusReactive("asd").block();
|
||||
|
||||
BDDAssertions.then(this.simpleTracer.getOnlySpan().tags).isNotEmpty();
|
||||
BDDMockito.then(this.delegate).should().statusReactive(BDDMockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_trace_statuses_reactive() {
|
||||
BDDMockito.given(this.delegate.statusesReactive(BDDMockito.any()))
|
||||
.willReturn(Flux.just(AppStatus.of("asd").build()));
|
||||
|
||||
this.traceAppDeployer.statusesReactive("asd").blockFirst();
|
||||
|
||||
BDDAssertions.then(this.simpleTracer.getOnlySpan().tags).isNotEmpty();
|
||||
BDDMockito.then(this.delegate).should().statusesReactive(BDDMockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_trace_log() {
|
||||
this.traceAppDeployer.getLog("id");
|
||||
|
||||
BDDAssertions.then(this.simpleTracer.getOnlySpan().tags).isNotEmpty();
|
||||
BDDMockito.then(this.delegate).should().getLog(BDDMockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_trace_scale() {
|
||||
this.traceAppDeployer.scale(new AppScaleRequest("asd", 2));
|
||||
|
||||
BDDAssertions.then(this.simpleTracer.getOnlySpan().tags).isNotEmpty();
|
||||
BDDMockito.then(this.delegate).should().scale(BDDMockito.any());
|
||||
}
|
||||
|
||||
private AppDeploymentRequest deploymentRequest() {
|
||||
return new AppDeploymentRequest(new AppDefinition("foo", new HashMap<>()), new PathResource("/"),
|
||||
deploymentProps(), commandLineArgs());
|
||||
}
|
||||
|
||||
private List<String> commandLineArgs() {
|
||||
return Arrays.asList("foo=bar1", "baz=bar2");
|
||||
}
|
||||
|
||||
private Map<String, String> deploymentProps() {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("deployment1", "prop1");
|
||||
map.put("deployment2", "prop2");
|
||||
return map;
|
||||
}
|
||||
|
||||
private BeanFactory beanFactory() {
|
||||
StaticListableBeanFactory beanFactory = new StaticListableBeanFactory();
|
||||
beanFactory.addBean("tracer", this.simpleTracer);
|
||||
beanFactory.addBean("currentTraceContext", new NoOpCurrentTraceContext());
|
||||
return beanFactory;
|
||||
}
|
||||
|
||||
private Environment environment() {
|
||||
return new MockEnvironment();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user