This commit is contained in:
Marcin Grzejszczak
2021-09-07 16:47:28 +02:00
parent 5f5f29fd6d
commit e898793683
9 changed files with 53 additions and 279 deletions

View File

@@ -25,10 +25,19 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
import org.springframework.cloud.gateway.config.GatewayAutoConfiguration;
import org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration;
import org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration;
import org.springframework.cloud.sleuth.DisableSecurity;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ActiveProfiles;
@@ -62,7 +71,7 @@ public class BraveAutoConfigurationWithDisabledSleuthTests {
public void shouldNotContainAnyTracingInfoInTheLogs(CapturedOutput capture) {
log.info("hello");
// prove bootstrap-disabled.yml loaded
// prove application-disabled.yml loaded
assertThat(applicationName).isEqualTo("disabledapplication");
// spring.application.name is put in the log format by
@@ -71,7 +80,11 @@ public class BraveAutoConfigurationWithDisabledSleuthTests {
BDDAssertions.then(capture.toString()).doesNotContain("[disabledapplication");
}
@EnableAutoConfiguration
@EnableAutoConfiguration(exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class,
GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class,
MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class,
RedisAutoConfiguration.class, CassandraAutoConfiguration.class },
excludeName = "org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration")
@Configuration(proxyBeanMethods = false)
@DisableSecurity
static class Config {

View File

@@ -30,8 +30,16 @@ import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.gateway.config.GatewayAutoConfiguration;
import org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration;
import org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration;
import static brave.propagation.CurrentTraceContext.Scope.NOOP;
import static org.assertj.core.api.Assertions.assertThat;
@@ -42,9 +50,12 @@ import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE,
properties = { "spring.sleuth.baggage.remote-fields=x-vcap-request-id,country-code",
"spring.sleuth.baggage.local-fields=bp", "spring.sleuth.baggage.correlation-fields=country-code,bp",
"spring.sleuth.tracer.mode=BRAVE" })
"spring.sleuth.tracer.mode=BRAVE", "debug=true" })
@SpringBootConfiguration
@EnableAutoConfiguration
@EnableAutoConfiguration(exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class,
GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class,
MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class, CassandraAutoConfiguration.class },
excludeName = "org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration")
public class CorrelationScopeDecoratorTest {
static final BaggageField COUNTRY_CODE = BaggageField.create("country-code");

View File

@@ -27,8 +27,16 @@ import brave.test.TestSpanHandler;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.gateway.config.GatewayAutoConfiguration;
import org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration;
import org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration;
import org.springframework.cloud.sleuth.brave.instrument.messaging.ConsumerSampler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -49,7 +57,10 @@ public class BraveMessagingAutoConfigurationIntegrationTests {
then(this.sampler).isNotNull();
}
@EnableAutoConfiguration
@EnableAutoConfiguration(exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class,
GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class,
MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class, CassandraAutoConfiguration.class },
excludeName = "org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration")
@Configuration(proxyBeanMethods = false)
public static class Config {

View File

@@ -36,8 +36,16 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.gateway.config.GatewayAutoConfiguration;
import org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration;
import org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -251,7 +259,10 @@ public class OpenTracingTest {
}
@Configuration(proxyBeanMethods = false)
@EnableAutoConfiguration
@EnableAutoConfiguration(exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class,
GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class,
MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class, CassandraAutoConfiguration.class},
excludeName = "org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration")
static class Config {
@Bean

View File

@@ -34,7 +34,7 @@ import org.springframework.stereotype.Service;
* @author Marcin Grzejszczak
*/
@SpringBootTest(classes = { EndpointWithCyclicDependenciesTests.ClientConfig.class },
properties = "spring.sleuth.tracer.mode=BRAVE")
properties = { "spring.sleuth.tracer.mode=BRAVE", "spring.main.allow-circular-references=true" })
public class EndpointWithCyclicDependenciesTests {
@Test

View File

@@ -1,92 +0,0 @@
/*
* 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.kotlin;
import kotlin.coroutines.CoroutineContext;
import kotlin.jvm.functions.Function2;
import kotlinx.coroutines.ThreadContextElement;
import org.jetbrains.annotations.Nullable;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanAndScope;
import org.springframework.cloud.sleuth.Tracer;
/**
* {@link ThreadContextElement} for synchronizing a {@link SpanAndScope} across coroutine
* suspension and resumption.
*
* Inspired by OpenTelemetry's KotlinContextElement.
*
* @since 3.1.0
*/
class KotlinContextElement implements ThreadContextElement<SpanAndScope> {
static final CoroutineContext.Key<KotlinContextElement> KEY = new CoroutineContext.Key<KotlinContextElement>() {
};
private final Span span;
private final Tracer tracer;
KotlinContextElement(Tracer tracer) {
this.tracer = tracer;
this.span = tracer.currentSpan();
}
Span getSpan() {
return this.span;
}
@Override
public CoroutineContext.Key<?> getKey() {
return KEY;
}
@Override
@SuppressWarnings("MustBeClosedChecker")
public SpanAndScope updateThreadContext(CoroutineContext coroutineContext) {
Tracer.SpanInScope spanInScope = this.tracer.withSpan(this.span);
return new SpanAndScope(this.span, spanInScope);
}
@Override
public void restoreThreadContext(CoroutineContext coroutineContext, SpanAndScope spanAndScope) {
spanAndScope.close();
}
@Override
public CoroutineContext plus(CoroutineContext coroutineContext) {
return CoroutineContext.DefaultImpls.plus(this, coroutineContext);
}
@Override
public <R> R fold(R initial, Function2<? super R, ? super CoroutineContext.Element, ? extends R> operation) {
return CoroutineContext.Element.DefaultImpls.fold(this, initial, operation);
}
@Nullable
@Override
public <E extends CoroutineContext.Element> E get(CoroutineContext.Key<E> key) {
return CoroutineContext.Element.DefaultImpls.get(this, key);
}
@Override
public CoroutineContext minusKey(CoroutineContext.Key<?> key) {
return CoroutineContext.Element.DefaultImpls.minusKey(this, key);
}
}

View File

@@ -1,69 +0,0 @@
/*
* 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.kotlin
import kotlinx.coroutines.reactor.ReactorContext
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.util.ClassUtils
import kotlin.coroutines.CoroutineContext
import kotlin.reflect.jvm.internal.impl.load.kotlin.KotlinClassFinder
/**
* Returns a [CoroutineContext] which will make this [Context] current when resuming a coroutine
* and restores the previous [Context] on suspension.
*
* Inspired by OpenTelemetry's asContextElement.
* @since 3.1.0
*/
fun Tracer.asContextElement(): CoroutineContext {
return KotlinContextElement(this)
}
/**
* Returns the [Span] in this [CoroutineContext] if present, or null otherwise.
*
* Inspired by OpenTelemetry's asContextElement.
* @since 3.1.0
*/
fun CoroutineContext.currentSpan(): Span? {
val element = get(KotlinContextElement.KEY)
if (element is KotlinContextElement) {
return element.span
}
if (!ClassUtils.isPresent("kotlinx.coroutines.reactor.ReactorContext", null)) {
return null
}
val reactorContext = get(ReactorContext.Key)
if (reactorContext != null) {
if (reactorContext.context.hasKey(Span::class.java)) {
return reactorContext.context.get(Span::class.java)
}
else if (reactorContext.context.hasKey(TraceContext::class.java) && reactorContext.context.hasKey(Tracer::class.java) && reactorContext.context.hasKey(CurrentTraceContext::class.java)) {
val traceContext = reactorContext.context.get(TraceContext::class.java)
reactorContext.context.get(CurrentTraceContext::class.java).maybeScope(traceContext).use {
return reactorContext.context.get(Tracer::class.java).currentSpan()
}
}
else if (reactorContext.context.hasKey(Tracer::class.java)) {
return reactorContext.context.get(Tracer::class.java).currentSpan()
}
}
return null
}

View File

@@ -1,111 +0,0 @@
/*
* 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.kotlin
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.reactor.ReactorContext
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.BDDAssertions.then
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.FilteredClassLoader
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.tracer.SimpleCurrentTraceContext
import org.springframework.cloud.sleuth.tracer.SimpleTracer
import reactor.util.context.Context
internal class AsContextElementKtTests {
@Test
fun `should return current span from context`(): Unit = runBlocking {
val simpleTracer = SimpleTracer()
val nextSpan = simpleTracer.nextSpan().start()
var spanInGlobalScopeLaunch: Span? = null
var spanInGlobalScopeAsync: Span? = null
val asContextElement = simpleTracer.asContextElement()
GlobalScope.launch(asContextElement) {
spanInGlobalScopeLaunch = coroutineContext.currentSpan()
}
GlobalScope.async(asContextElement) {
spanInGlobalScopeAsync = coroutineContext.currentSpan()
}.await()
then(spanInGlobalScopeLaunch).isSameAs(nextSpan)
then(spanInGlobalScopeAsync).isSameAs(nextSpan)
}
@Test
fun `should return span from coroutine context when KotlinContextElement present`(): Unit = runBlocking {
val simpleTracer = SimpleTracer()
val nextSpan = simpleTracer.nextSpan().start()
val element = KotlinContextElement(simpleTracer)
then(element.currentSpan()).isSameAs(nextSpan)
}
@Test
fun `should return null from coroutine context when KotlinContextElement and Reactor extensions are missing`(): Unit = runBlocking {
val contextClassLoader = Thread.currentThread().contextClassLoader
try {
Thread.currentThread().contextClassLoader = FilteredClassLoader("kotlinx.coroutines.reactor.ReactorContext")
then(coroutineContext.currentSpan()).isNull()
} finally {
Thread.currentThread().contextClassLoader = contextClassLoader
}
}
@Test
fun `should return Span from Reactor extensions when KotlinContextElement missing`(): Unit = runBlocking {
val simpleTracer = SimpleTracer()
val nextSpan = simpleTracer.nextSpan().start()
val reactorContext = ReactorContext(Context.of(Span::class.java, nextSpan))
then(reactorContext.currentSpan()).isSameAs(nextSpan);
}
@Test
fun `should return Span from Reactor extensions CurrentTraceContext when KotlinContextElement missing and there is TraceContext in Reactor context`(): Unit = runBlocking {
val currentTraceContext = SimpleCurrentTraceContext()
val simpleTracer = SimpleTracer()
val nextSpan = simpleTracer.nextSpan().start()
val reactorContext = ReactorContext(Context.of(Tracer::class.java, simpleTracer, CurrentTraceContext::class.java, currentTraceContext, TraceContext::class.java, nextSpan.context()))
then(reactorContext.currentSpan()).isSameAs(nextSpan);
}
@Test
fun `should return Span from Reactor extensions Tracer when KotlinContextElement missing and there is no Span in context`(): Unit = runBlocking {
val simpleTracer = SimpleTracer()
val nextSpan = simpleTracer.nextSpan().start()
val reactorContext = ReactorContext(Context.of(Tracer::class.java, simpleTracer))
then(reactorContext.currentSpan()).isSameAs(nextSpan);
}
@Test
fun `should return null when no span is found`(): Unit = runBlocking {
val reactorContext = ReactorContext(Context.empty())
then(reactorContext.currentSpan()).isNull()
}
}