12 changed files with 997 additions and 73 deletions
@ -0,0 +1,201 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2012-2023 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.boot.actuate.autoconfigure.tracing; |
||||||
|
|
||||||
|
import java.util.Collection; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.List; |
||||||
|
import java.util.stream.Stream; |
||||||
|
|
||||||
|
import brave.internal.propagation.StringPropagationAdapter; |
||||||
|
import brave.propagation.B3Propagation; |
||||||
|
import brave.propagation.Propagation; |
||||||
|
import brave.propagation.TraceContext; |
||||||
|
import brave.propagation.TraceContextOrSamplingFlags; |
||||||
|
import io.micrometer.tracing.BaggageManager; |
||||||
|
import io.micrometer.tracing.brave.bridge.W3CPropagation; |
||||||
|
|
||||||
|
/** |
||||||
|
* {@link Factory} which supports multiple tracing formats. It is able to configure |
||||||
|
* different formats for injecting and for extracting. |
||||||
|
* |
||||||
|
* @author Marcin Grzejszczak |
||||||
|
* @author Moritz Halbritter |
||||||
|
*/ |
||||||
|
class CompositePropagationFactory extends Propagation.Factory implements Propagation<String> { |
||||||
|
|
||||||
|
private final Collection<Propagation.Factory> injectorFactories; |
||||||
|
|
||||||
|
private final Collection<Propagation.Factory> extractorFactories; |
||||||
|
|
||||||
|
private final List<Propagation<String>> injectors; |
||||||
|
|
||||||
|
private final List<Propagation<String>> extractors; |
||||||
|
|
||||||
|
private final boolean supportsJoin; |
||||||
|
|
||||||
|
private final boolean requires128BitTraceId; |
||||||
|
|
||||||
|
private final List<String> keys; |
||||||
|
|
||||||
|
CompositePropagationFactory(Collection<Factory> injectorFactories, Collection<Factory> extractorFactories) { |
||||||
|
this.injectorFactories = injectorFactories; |
||||||
|
this.extractorFactories = extractorFactories; |
||||||
|
this.injectors = this.injectorFactories.stream().map(Factory::get).toList(); |
||||||
|
this.extractors = this.extractorFactories.stream().map(Factory::get).toList(); |
||||||
|
this.supportsJoin = Stream.concat(this.injectorFactories.stream(), this.extractorFactories.stream()) |
||||||
|
.allMatch(Factory::supportsJoin); |
||||||
|
this.requires128BitTraceId = Stream.concat(this.injectorFactories.stream(), this.extractorFactories.stream()) |
||||||
|
.anyMatch(Factory::requires128BitTraceId); |
||||||
|
this.keys = Stream.concat(this.injectors.stream(), this.extractors.stream()) |
||||||
|
.flatMap((entry) -> entry.keys().stream()) |
||||||
|
.distinct() |
||||||
|
.toList(); |
||||||
|
} |
||||||
|
|
||||||
|
Collection<Factory> getInjectorFactories() { |
||||||
|
return this.injectorFactories; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public List<String> keys() { |
||||||
|
return this.keys; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <R> TraceContext.Injector<R> injector(Setter<R, String> setter) { |
||||||
|
return (traceContext, request) -> { |
||||||
|
for (Propagation<String> injector : this.injectors) { |
||||||
|
injector.injector(setter).inject(traceContext, request); |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <R> TraceContext.Extractor<R> extractor(Getter<R, String> getter) { |
||||||
|
return (request) -> { |
||||||
|
for (Propagation<String> extractor : this.extractors) { |
||||||
|
TraceContextOrSamplingFlags extract = extractor.extractor(getter).extract(request); |
||||||
|
if (extract != TraceContextOrSamplingFlags.EMPTY) { |
||||||
|
return extract; |
||||||
|
} |
||||||
|
} |
||||||
|
return TraceContextOrSamplingFlags.EMPTY; |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
@SuppressWarnings("deprecation") |
||||||
|
public <K> Propagation<K> create(KeyFactory<K> keyFactory) { |
||||||
|
return StringPropagationAdapter.create(this, keyFactory); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean supportsJoin() { |
||||||
|
return this.supportsJoin; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean requires128BitTraceId() { |
||||||
|
return this.requires128BitTraceId; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public TraceContext decorate(TraceContext context) { |
||||||
|
for (Factory injectorFactory : this.injectorFactories) { |
||||||
|
TraceContext decorated = injectorFactory.decorate(context); |
||||||
|
if (decorated != context) { |
||||||
|
return decorated; |
||||||
|
} |
||||||
|
} |
||||||
|
for (Factory extractorFactory : this.extractorFactories) { |
||||||
|
TraceContext decorated = extractorFactory.decorate(context); |
||||||
|
if (decorated != context) { |
||||||
|
return decorated; |
||||||
|
} |
||||||
|
} |
||||||
|
return super.decorate(context); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new {@link CompositePropagationFactory}, which uses the given |
||||||
|
* {@code injectionTypes} for injection and {@code extractionTypes} for extraction. |
||||||
|
* @param baggageManager the baggage manager to use, or {@code null} |
||||||
|
* @param injectionTypes the propagation types for injection |
||||||
|
* @param extractionTypes the propagation types for extraction |
||||||
|
* @return the {@link CompositePropagationFactory} |
||||||
|
*/ |
||||||
|
static CompositePropagationFactory create(BaggageManager baggageManager, |
||||||
|
Collection<TracingProperties.Propagation.PropagationType> injectionTypes, |
||||||
|
Collection<TracingProperties.Propagation.PropagationType> extractionTypes) { |
||||||
|
List<Factory> injectors = injectionTypes.stream() |
||||||
|
.map((injection) -> factoryForType(baggageManager, injection)) |
||||||
|
.toList(); |
||||||
|
List<Factory> extractors = extractionTypes.stream() |
||||||
|
.map((extraction) -> factoryForType(baggageManager, extraction)) |
||||||
|
.toList(); |
||||||
|
return new CompositePropagationFactory(injectors, extractors); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new {@link CompositePropagationFactory}, which uses the given |
||||||
|
* {@code injectionTypes} for injection and {@code extractionTypes} for extraction. |
||||||
|
* @param injectionTypes the propagation types for injection |
||||||
|
* @param extractionTypes the propagation types for extraction |
||||||
|
* @return the {@link CompositePropagationFactory} |
||||||
|
*/ |
||||||
|
static CompositePropagationFactory create(Collection<TracingProperties.Propagation.PropagationType> injectionTypes, |
||||||
|
Collection<TracingProperties.Propagation.PropagationType> extractionTypes) { |
||||||
|
return create(null, injectionTypes, extractionTypes); |
||||||
|
} |
||||||
|
|
||||||
|
private static Factory factoryForType(BaggageManager baggageManager, |
||||||
|
TracingProperties.Propagation.PropagationType type) { |
||||||
|
return switch (type) { |
||||||
|
case B3 -> b3Single(); |
||||||
|
case B3_MULTI -> b3Multi(); |
||||||
|
case W3C -> w3c(baggageManager); |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new B3 propagation factory using a single B3 header. |
||||||
|
* @return the B3 propagation factory |
||||||
|
*/ |
||||||
|
private static Factory b3Single() { |
||||||
|
return B3Propagation.newFactoryBuilder().injectFormat(B3Propagation.Format.SINGLE_NO_PARENT).build(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new B3 propagation factory using multiple B3 headers. |
||||||
|
* @return the B3 propagation factory |
||||||
|
*/ |
||||||
|
private static Factory b3Multi() { |
||||||
|
return B3Propagation.newFactoryBuilder().injectFormat(B3Propagation.Format.MULTI).build(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new W3C propagation factory. |
||||||
|
* @param baggageManager baggage manager to use, or {@code null} |
||||||
|
* @return the W3C propagation factory |
||||||
|
*/ |
||||||
|
private static W3CPropagation w3c(BaggageManager baggageManager) { |
||||||
|
return (baggageManager != null) ? new W3CPropagation(baggageManager, Collections.emptyList()) |
||||||
|
: new W3CPropagation(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,194 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2012-2023 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.boot.actuate.autoconfigure.tracing; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collection; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Set; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
import java.util.stream.Stream; |
||||||
|
|
||||||
|
import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator; |
||||||
|
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; |
||||||
|
import io.opentelemetry.context.Context; |
||||||
|
import io.opentelemetry.context.propagation.TextMapGetter; |
||||||
|
import io.opentelemetry.context.propagation.TextMapPropagator; |
||||||
|
import io.opentelemetry.context.propagation.TextMapSetter; |
||||||
|
import io.opentelemetry.extension.trace.propagation.B3Propagator; |
||||||
|
|
||||||
|
/** |
||||||
|
* {@link TextMapPropagator} which supports multiple tracing formats. It is able to |
||||||
|
* configure different formats for injecting and for extracting. |
||||||
|
* |
||||||
|
* @author Moritz Halbritter |
||||||
|
*/ |
||||||
|
class CompositeTextMapPropagator implements TextMapPropagator { |
||||||
|
|
||||||
|
private final Collection<TextMapPropagator> injectors; |
||||||
|
|
||||||
|
private final Collection<TextMapPropagator> mutuallyExclusiveExtractors; |
||||||
|
|
||||||
|
private final Collection<TextMapPropagator> alwaysRunningExtractors; |
||||||
|
|
||||||
|
private final Set<String> fields; |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new {@link CompositeTextMapPropagator}. |
||||||
|
* @param injectors the injectors |
||||||
|
* @param mutuallyExclusiveExtractors the mutually exclusive extractors. They are |
||||||
|
* applied in order, and as soon as an extractor extracts a context, the other |
||||||
|
* extractors after it are no longer invoked |
||||||
|
* @param alwaysRunningExtractors the always running extractors. They always run in |
||||||
|
* order, regardless of the mutually exclusive extractors or whether the extractor |
||||||
|
* before it has already extracted a context |
||||||
|
*/ |
||||||
|
CompositeTextMapPropagator(Collection<TextMapPropagator> injectors, |
||||||
|
Collection<TextMapPropagator> mutuallyExclusiveExtractors, |
||||||
|
Collection<TextMapPropagator> alwaysRunningExtractors) { |
||||||
|
this.injectors = injectors; |
||||||
|
this.mutuallyExclusiveExtractors = mutuallyExclusiveExtractors; |
||||||
|
this.alwaysRunningExtractors = alwaysRunningExtractors; |
||||||
|
this.fields = concat(this.injectors, this.mutuallyExclusiveExtractors, this.alwaysRunningExtractors) |
||||||
|
.flatMap((entry) -> entry.fields().stream()) |
||||||
|
.collect(Collectors.toSet()); |
||||||
|
} |
||||||
|
|
||||||
|
Collection<TextMapPropagator> getInjectors() { |
||||||
|
return this.injectors; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Collection<String> fields() { |
||||||
|
return this.fields; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <C> void inject(Context context, C carrier, TextMapSetter<C> setter) { |
||||||
|
if (context == null || setter == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
for (TextMapPropagator injector : this.injectors) { |
||||||
|
injector.inject(context, carrier, setter); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <C> Context extract(Context context, C carrier, TextMapGetter<C> getter) { |
||||||
|
if (context == null) { |
||||||
|
return Context.root(); |
||||||
|
} |
||||||
|
if (getter == null) { |
||||||
|
return context; |
||||||
|
} |
||||||
|
Context currentContext = context; |
||||||
|
for (TextMapPropagator extractor : this.mutuallyExclusiveExtractors) { |
||||||
|
Context extractedContext = extractor.extract(currentContext, carrier, getter); |
||||||
|
if (extractedContext != currentContext) { |
||||||
|
currentContext = extractedContext; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
for (TextMapPropagator extractor : this.alwaysRunningExtractors) { |
||||||
|
currentContext = extractor.extract(currentContext, carrier, getter); |
||||||
|
} |
||||||
|
return currentContext; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new {@link CompositeTextMapPropagator}, which uses the given |
||||||
|
* {@code injectionTypes} for injection and {@code extractionTypes} for extraction. |
||||||
|
* @param injectionTypes the propagation types for injection |
||||||
|
* @param extractionTypes the propagation types for extraction |
||||||
|
* @return the {@link CompositeTextMapPropagator} |
||||||
|
*/ |
||||||
|
static TextMapPropagator create(Collection<TracingProperties.Propagation.PropagationType> injectionTypes, |
||||||
|
Collection<TracingProperties.Propagation.PropagationType> extractionTypes) { |
||||||
|
return create(null, injectionTypes, extractionTypes); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new {@link CompositeTextMapPropagator}, which uses the given |
||||||
|
* {@code injectionTypes} for injection and {@code extractionTypes} for extraction. |
||||||
|
* @param baggagePropagator the baggage propagator to use, or {@code null} |
||||||
|
* @param injectionTypes the propagation types for injection |
||||||
|
* @param extractionTypes the propagation types for extraction |
||||||
|
* @return the {@link CompositeTextMapPropagator} |
||||||
|
*/ |
||||||
|
static CompositeTextMapPropagator create(TextMapPropagator baggagePropagator, |
||||||
|
Collection<TracingProperties.Propagation.PropagationType> injectionTypes, |
||||||
|
Collection<TracingProperties.Propagation.PropagationType> extractionTypes) { |
||||||
|
List<TextMapPropagator> injectors = injectionTypes.stream() |
||||||
|
.map((injection) -> forType(injection, baggagePropagator != null)) |
||||||
|
.collect(Collectors.toCollection(ArrayList::new)); |
||||||
|
if (baggagePropagator != null) { |
||||||
|
injectors.add(baggagePropagator); |
||||||
|
} |
||||||
|
List<TextMapPropagator> extractors = extractionTypes.stream() |
||||||
|
.map((extraction) -> forType(extraction, baggagePropagator != null)) |
||||||
|
.toList(); |
||||||
|
return new CompositeTextMapPropagator(injectors, extractors, |
||||||
|
(baggagePropagator != null) ? List.of(baggagePropagator) : Collections.emptyList()); |
||||||
|
} |
||||||
|
|
||||||
|
@SafeVarargs |
||||||
|
private static <T> Stream<T> concat(Collection<T>... collections) { |
||||||
|
Stream<T> result = Stream.empty(); |
||||||
|
for (Collection<T> collection : collections) { |
||||||
|
result = Stream.concat(result, collection.stream()); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new B3 propagator using a single B3 header. |
||||||
|
* @return the B3 propagator |
||||||
|
*/ |
||||||
|
private static TextMapPropagator b3Single() { |
||||||
|
return B3Propagator.injectingSingleHeader(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new B3 propagator using multiple B3 headers. |
||||||
|
* @return the B3 propagator |
||||||
|
*/ |
||||||
|
private static TextMapPropagator b3Multi() { |
||||||
|
return B3Propagator.injectingMultiHeaders(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new W3C propagator. |
||||||
|
* @param baggage whether baggage propagation should be supported |
||||||
|
* @return the W3C propagator |
||||||
|
*/ |
||||||
|
private static TextMapPropagator w3c(boolean baggage) { |
||||||
|
if (!baggage) { |
||||||
|
return W3CTraceContextPropagator.getInstance(); |
||||||
|
} |
||||||
|
return TextMapPropagator.composite(W3CTraceContextPropagator.getInstance(), W3CBaggagePropagator.getInstance()); |
||||||
|
} |
||||||
|
|
||||||
|
private static TextMapPropagator forType(TracingProperties.Propagation.PropagationType type, boolean baggage) { |
||||||
|
return switch (type) { |
||||||
|
case B3 -> b3Single(); |
||||||
|
case B3_MULTI -> b3Multi(); |
||||||
|
case W3C -> w3c(baggage); |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,162 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2012-2023 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.boot.actuate.autoconfigure.tracing; |
||||||
|
|
||||||
|
import java.util.Collections; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import brave.internal.propagation.StringPropagationAdapter; |
||||||
|
import brave.propagation.Propagation; |
||||||
|
import brave.propagation.TraceContext; |
||||||
|
import brave.propagation.TraceContextOrSamplingFlags; |
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
import org.mockito.Mockito; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
import static org.assertj.core.api.Assertions.entry; |
||||||
|
import static org.mockito.BDDMockito.given; |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests for {@link CompositePropagationFactory}. |
||||||
|
* |
||||||
|
* @author Moritz Halbritter |
||||||
|
*/ |
||||||
|
class CompositePropagationFactoryTests { |
||||||
|
|
||||||
|
@Test |
||||||
|
void returnsAllKeys() { |
||||||
|
CompositePropagationFactory factory = new CompositePropagationFactory(List.of(field("a")), List.of(field("b"))); |
||||||
|
assertThat(factory.keys()).containsExactly("a", "b"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void supportsJoin() { |
||||||
|
Propagation.Factory supportsJoin = Mockito.mock(Propagation.Factory.class); |
||||||
|
given(supportsJoin.supportsJoin()).willReturn(true); |
||||||
|
given(supportsJoin.get()).willReturn(new DummyPropagation("a")); |
||||||
|
Propagation.Factory doesNotSupportsJoin = Mockito.mock(Propagation.Factory.class); |
||||||
|
given(doesNotSupportsJoin.supportsJoin()).willReturn(false); |
||||||
|
given(doesNotSupportsJoin.get()).willReturn(new DummyPropagation("a")); |
||||||
|
CompositePropagationFactory factory = new CompositePropagationFactory(List.of(supportsJoin), |
||||||
|
List.of(doesNotSupportsJoin)); |
||||||
|
assertThat(factory.supportsJoin()).isFalse(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void requires128BitTraceId() { |
||||||
|
Propagation.Factory requires128BitTraceId = Mockito.mock(Propagation.Factory.class); |
||||||
|
given(requires128BitTraceId.requires128BitTraceId()).willReturn(true); |
||||||
|
given(requires128BitTraceId.get()).willReturn(new DummyPropagation("a")); |
||||||
|
Propagation.Factory doesNotRequire128BitTraceId = Mockito.mock(Propagation.Factory.class); |
||||||
|
given(doesNotRequire128BitTraceId.requires128BitTraceId()).willReturn(false); |
||||||
|
given(doesNotRequire128BitTraceId.get()).willReturn(new DummyPropagation("a")); |
||||||
|
CompositePropagationFactory factory = new CompositePropagationFactory(List.of(requires128BitTraceId), |
||||||
|
List.of(doesNotRequire128BitTraceId)); |
||||||
|
assertThat(factory.requires128BitTraceId()).isTrue(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void inject() { |
||||||
|
CompositePropagationFactory factory = new CompositePropagationFactory(List.of(field("a"), field("b")), |
||||||
|
List.of(field("c"))); |
||||||
|
TraceContext context = context(); |
||||||
|
Map<String, String> request = new HashMap<>(); |
||||||
|
factory.injector(new MapSetter()).inject(context, request); |
||||||
|
assertThat(request).containsOnly(entry("a", "a-value"), entry("b", "b-value")); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void extractorStopsAfterSuccessfulExtraction() { |
||||||
|
CompositePropagationFactory factory = new CompositePropagationFactory(Collections.emptyList(), |
||||||
|
List.of(field("a"), field("b"))); |
||||||
|
Map<String, String> request = Map.of("a", "a-value", "b", "b-value"); |
||||||
|
TraceContextOrSamplingFlags context = factory.extractor(new MapGetter()).extract(request); |
||||||
|
assertThat(context.context().extra()).containsExactly("a"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void returnsEmptyContextWhenNoExtractorMatches() { |
||||||
|
CompositePropagationFactory factory = new CompositePropagationFactory(Collections.emptyList(), |
||||||
|
Collections.emptyList()); |
||||||
|
Map<String, String> request = Collections.emptyMap(); |
||||||
|
TraceContextOrSamplingFlags context = factory.extractor(new MapGetter()).extract(request); |
||||||
|
assertThat(context.context()).isNull(); |
||||||
|
} |
||||||
|
|
||||||
|
private static TraceContext context() { |
||||||
|
return TraceContext.newBuilder().traceId(1).spanId(2).build(); |
||||||
|
} |
||||||
|
|
||||||
|
private static DummyPropagation field(String field) { |
||||||
|
return new DummyPropagation(field); |
||||||
|
} |
||||||
|
|
||||||
|
private static final class MapSetter implements Propagation.Setter<Map<String, String>, String> { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void put(Map<String, String> request, String key, String value) { |
||||||
|
request.put(key, value); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
private static final class MapGetter implements Propagation.Getter<Map<String, String>, String> { |
||||||
|
|
||||||
|
@Override |
||||||
|
public String get(Map<String, String> request, String key) { |
||||||
|
return request.get(key); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
private static final class DummyPropagation extends Propagation.Factory implements Propagation<String> { |
||||||
|
|
||||||
|
private final String field; |
||||||
|
|
||||||
|
private DummyPropagation(String field) { |
||||||
|
this.field = field; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
@SuppressWarnings("deprecation") |
||||||
|
public <K> Propagation<K> create(Propagation.KeyFactory<K> keyFactory) { |
||||||
|
return StringPropagationAdapter.create(this, keyFactory); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public List<String> keys() { |
||||||
|
return List.of(this.field); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <R> TraceContext.Injector<R> injector(Propagation.Setter<R, String> setter) { |
||||||
|
return (traceContext, request) -> setter.put(request, this.field, this.field + "-value"); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <R> TraceContext.Extractor<R> extractor(Propagation.Getter<R, String> getter) { |
||||||
|
return (request) -> { |
||||||
|
TraceContext context = TraceContext.newBuilder().traceId(1).spanId(2).addExtra(this.field).build(); |
||||||
|
return TraceContextOrSamplingFlags.create(context); |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,174 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2012-2023 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.boot.actuate.autoconfigure.tracing; |
||||||
|
|
||||||
|
import java.util.Collection; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import io.opentelemetry.context.Context; |
||||||
|
import io.opentelemetry.context.ContextKey; |
||||||
|
import io.opentelemetry.context.propagation.TextMapGetter; |
||||||
|
import io.opentelemetry.context.propagation.TextMapPropagator; |
||||||
|
import io.opentelemetry.context.propagation.TextMapSetter; |
||||||
|
import org.junit.jupiter.api.BeforeEach; |
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
import org.mockito.InOrder; |
||||||
|
import org.mockito.Mockito; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests for {@link CompositeTextMapPropagator}. |
||||||
|
* |
||||||
|
* @author Moritz Halbritter |
||||||
|
*/ |
||||||
|
class CompositeTextMapPropagatorTests { |
||||||
|
|
||||||
|
private ContextKeyRegistry contextKeyRegistry; |
||||||
|
|
||||||
|
@BeforeEach |
||||||
|
void setUp() { |
||||||
|
this.contextKeyRegistry = new ContextKeyRegistry(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void collectsAllFields() { |
||||||
|
CompositeTextMapPropagator propagator = new CompositeTextMapPropagator(List.of(field("a")), List.of(field("b")), |
||||||
|
List.of(field("c"))); |
||||||
|
assertThat(propagator.fields()).containsExactly("a", "b", "c"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void injectAllFields() { |
||||||
|
CompositeTextMapPropagator propagator = new CompositeTextMapPropagator(List.of(field("a"), field("b")), |
||||||
|
Collections.emptyList(), Collections.emptyList()); |
||||||
|
TextMapSetter<Object> setter = setter(); |
||||||
|
Object carrier = carrier(); |
||||||
|
propagator.inject(context(), carrier, setter); |
||||||
|
InOrder inOrder = Mockito.inOrder(setter); |
||||||
|
inOrder.verify(setter).set(carrier, "a", "a-value"); |
||||||
|
inOrder.verify(setter).set(carrier, "b", "b-value"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void extractMutuallyExclusive() { |
||||||
|
CompositeTextMapPropagator propagator = new CompositeTextMapPropagator(Collections.emptyList(), |
||||||
|
List.of(field("a"), field("b")), Collections.emptyList()); |
||||||
|
Context context = context(); |
||||||
|
Map<String, String> carrier = Map.of("a", "a-value", "b", "b-value"); |
||||||
|
context = propagator.extract(context, carrier, new MapTextMapGetter()); |
||||||
|
Object a = context.get(getObjectContextKey("a")); |
||||||
|
assertThat(a).isEqualTo("a-value"); |
||||||
|
Object b = context.get(getObjectContextKey("b")); |
||||||
|
assertThat(b).isNull(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void extractAlwaysRunning() { |
||||||
|
CompositeTextMapPropagator propagator = new CompositeTextMapPropagator(Collections.emptyList(), |
||||||
|
List.of(field("a"), field("b")), List.of(field("c"))); |
||||||
|
Context context = context(); |
||||||
|
Map<String, String> carrier = Map.of("a", "a-value", "b", "b-value", "c", "c-value"); |
||||||
|
context = propagator.extract(context, carrier, new MapTextMapGetter()); |
||||||
|
Object c = context.get(getObjectContextKey("c")); |
||||||
|
assertThat(c).isEqualTo("c-value"); |
||||||
|
} |
||||||
|
|
||||||
|
private DummyTextMapPropagator field(String field) { |
||||||
|
return new DummyTextMapPropagator(field, this.contextKeyRegistry); |
||||||
|
} |
||||||
|
|
||||||
|
private ContextKey<Object> getObjectContextKey(String name) { |
||||||
|
return this.contextKeyRegistry.get(name); |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
private static <T> TextMapSetter<T> setter() { |
||||||
|
return Mockito.mock(TextMapSetter.class); |
||||||
|
} |
||||||
|
|
||||||
|
private static Object carrier() { |
||||||
|
return new Object(); |
||||||
|
} |
||||||
|
|
||||||
|
private static Context context() { |
||||||
|
return Context.current(); |
||||||
|
} |
||||||
|
|
||||||
|
private static final class ContextKeyRegistry { |
||||||
|
|
||||||
|
private final Map<String, ContextKey<Object>> contextKeys = new HashMap<>(); |
||||||
|
|
||||||
|
private ContextKey<Object> get(String name) { |
||||||
|
return this.contextKeys.computeIfAbsent(name, (ignore) -> ContextKey.named(name)); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
private static final class MapTextMapGetter implements TextMapGetter<Map<String, String>> { |
||||||
|
|
||||||
|
@Override |
||||||
|
public Iterable<String> keys(Map<String, String> carrier) { |
||||||
|
return carrier.keySet(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String get(Map<String, String> carrier, String key) { |
||||||
|
if (carrier == null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
return carrier.get(key); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
private static final class DummyTextMapPropagator implements TextMapPropagator { |
||||||
|
|
||||||
|
private final String field; |
||||||
|
|
||||||
|
private final ContextKeyRegistry contextKeyRegistry; |
||||||
|
|
||||||
|
private DummyTextMapPropagator(String field, ContextKeyRegistry contextKeyRegistry) { |
||||||
|
this.field = field; |
||||||
|
this.contextKeyRegistry = contextKeyRegistry; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Collection<String> fields() { |
||||||
|
return List.of(this.field); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <C> void inject(Context context, C carrier, TextMapSetter<C> setter) { |
||||||
|
setter.set(carrier, this.field, this.field + "-value"); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <C> Context extract(Context context, C carrier, TextMapGetter<C> getter) { |
||||||
|
String value = getter.get(carrier, this.field); |
||||||
|
if (value != null) { |
||||||
|
return context.with(this.contextKeyRegistry.get(this.field), value); |
||||||
|
} |
||||||
|
return context; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,66 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2012-2023 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.boot.actuate.autoconfigure.tracing; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests for {@link TracingProperties}. |
||||||
|
* |
||||||
|
* @author Moritz Halbritter |
||||||
|
*/ |
||||||
|
class TracingPropertiesTests { |
||||||
|
|
||||||
|
@Test |
||||||
|
void propagationTypeShouldOverrideProduceTypes() { |
||||||
|
TracingProperties.Propagation propagation = new TracingProperties.Propagation(); |
||||||
|
propagation.setProduce(List.of(TracingProperties.Propagation.PropagationType.W3C)); |
||||||
|
propagation.setType(List.of(TracingProperties.Propagation.PropagationType.B3)); |
||||||
|
assertThat(propagation.getEffectiveProducedTypes()) |
||||||
|
.containsExactly(TracingProperties.Propagation.PropagationType.B3); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void propagationTypeShouldOverrideConsumeTypes() { |
||||||
|
TracingProperties.Propagation propagation = new TracingProperties.Propagation(); |
||||||
|
propagation.setConsume(List.of(TracingProperties.Propagation.PropagationType.W3C)); |
||||||
|
propagation.setType(List.of(TracingProperties.Propagation.PropagationType.B3)); |
||||||
|
assertThat(propagation.getEffectiveConsumedTypes()) |
||||||
|
.containsExactly(TracingProperties.Propagation.PropagationType.B3); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void getEffectiveConsumeTypes() { |
||||||
|
TracingProperties.Propagation propagation = new TracingProperties.Propagation(); |
||||||
|
propagation.setConsume(List.of(TracingProperties.Propagation.PropagationType.W3C)); |
||||||
|
assertThat(propagation.getEffectiveConsumedTypes()) |
||||||
|
.containsExactly(TracingProperties.Propagation.PropagationType.W3C); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void getEffectiveProduceTypes() { |
||||||
|
TracingProperties.Propagation propagation = new TracingProperties.Propagation(); |
||||||
|
propagation.setProduce(List.of(TracingProperties.Propagation.PropagationType.W3C)); |
||||||
|
assertThat(propagation.getEffectiveProducedTypes()) |
||||||
|
.containsExactly(TracingProperties.Propagation.PropagationType.W3C); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue