Skip to content

Commit 890ba28

Browse files
MultiTracePropagator implementation (#1339)
1 parent 50a424c commit 890ba28

File tree

11 files changed

+454
-0
lines changed

11 files changed

+454
-0
lines changed

api/src/main/java/io/opentelemetry/trace/propagation/HttpTraceContext.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ private static <C> void injectImpl(SpanContext spanContext, C carrier, Setter<C>
125125
checkNotNull(getter, "getter");
126126

127127
SpanContext spanContext = extractImpl(carrier, getter);
128+
if (!spanContext.isValid()) {
129+
return context;
130+
}
131+
128132
return TracingContextUtils.withSpan(DefaultSpan.create(spanContext), context);
129133
}
130134

api/src/test/java/io/opentelemetry/trace/propagation/HttpTraceContextTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import io.opentelemetry.trace.TraceId;
3131
import io.opentelemetry.trace.TraceState;
3232
import io.opentelemetry.trace.TracingContextUtils;
33+
import java.util.Collections;
3334
import java.util.HashMap;
3435
import java.util.LinkedHashMap;
3536
import java.util.Map;
@@ -162,6 +163,15 @@ public void inject_NotSampledContext_WithTraceState() {
162163
TRACESTATE_NOT_DEFAULT_ENCODING);
163164
}
164165

166+
@Test
167+
public void extract_Nothing() {
168+
// Context remains untouched.
169+
assertThat(
170+
httpTraceContext.extract(
171+
Context.current(), Collections.<String, String>emptyMap(), Map::get))
172+
.isSameInstanceAs(Context.current());
173+
}
174+
165175
@Test
166176
public void extract_SampledContext() {
167177
Map<String, String> carrier = new LinkedHashMap<>();

extensions/trace_propagators/src/main/java/io/opentelemetry/extensions/trace/propagation/AwsXRayPropagator.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ public <C> Context extract(Context context, C carrier, Getter<C> getter) {
125125
Objects.requireNonNull(getter, "getter");
126126

127127
SpanContext spanContext = getSpanContextFromHeader(carrier, getter);
128+
if (!spanContext.isValid()) {
129+
return context;
130+
}
131+
128132
return TracingContextUtils.withSpan(DefaultSpan.create(spanContext), context);
129133
}
130134

extensions/trace_propagators/src/main/java/io/opentelemetry/extensions/trace/propagation/B3PropagatorExtractorMultipleHeaders.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ public <C> Context extract(Context context, C carrier, HttpTextFormat.Getter<C>
3939
Objects.requireNonNull(carrier, "carrier");
4040
Objects.requireNonNull(getter, "getter");
4141
SpanContext spanContext = getSpanContextFromMultipleHeaders(carrier, getter);
42+
if (!spanContext.isValid()) {
43+
return context;
44+
}
45+
4246
return TracingContextUtils.withSpan(DefaultSpan.create(spanContext), context);
4347
}
4448

extensions/trace_propagators/src/main/java/io/opentelemetry/extensions/trace/propagation/B3PropagatorExtractorSingleHeader.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ public <C> Context extract(Context context, C carrier, HttpTextFormat.Getter<C>
3838
Objects.requireNonNull(carrier, "carrier");
3939
Objects.requireNonNull(getter, "getter");
4040
SpanContext spanContext = getSpanContextFromSingleHeader(carrier, getter);
41+
if (!spanContext.isValid()) {
42+
return context;
43+
}
44+
4145
return TracingContextUtils.withSpan(DefaultSpan.create(spanContext), context);
4246
}
4347

extensions/trace_propagators/src/main/java/io/opentelemetry/extensions/trace/propagation/JaegerPropagator.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ public <C> Context extract(Context context, C carrier, Getter<C> getter) {
110110
Objects.requireNonNull(getter, "getter");
111111

112112
SpanContext spanContext = getSpanContextFromHeader(carrier, getter);
113+
if (!spanContext.isValid()) {
114+
return context;
115+
}
113116

114117
return TracingContextUtils.withSpan(DefaultSpan.create(spanContext), context);
115118
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
/*
2+
* Copyright 2020, OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.opentelemetry.extensions.trace.propagation;
18+
19+
import io.grpc.Context;
20+
import io.opentelemetry.context.propagation.HttpTextFormat;
21+
import io.opentelemetry.trace.TracingContextUtils;
22+
import java.util.ArrayList;
23+
import java.util.Collections;
24+
import java.util.List;
25+
import java.util.Objects;
26+
import javax.annotation.concurrent.Immutable;
27+
28+
/**
29+
* A propagator designed to inject and extract multiple trace {@code HttpTextFormat} propagators,
30+
* intendended for backwards compatibility with existing services using different formats. It works
31+
* in a stack-fashion, starting with the last registered propagator, to the first one.
32+
*
33+
* <p>Upon injection, this propagator invokes {@code HttpTextFormat#inject()} for every registered
34+
* trace propagator. This will result in the carrier containing all the registered formats.
35+
*
36+
* <p>Upon extraction, this propagator invokes {@code HttpTextFormat#extract()} for every registered
37+
* trace propagator, returning immediately when a successful extraction happened.
38+
*
39+
* <pre>{@code
40+
* HttpTextFormat traceFormats = TraceMultiPropagator.builder()
41+
* .addPropagator(new MyCustomTracePropagator())
42+
* .addPropagator(new JaegerPropagator())
43+
* .addPropagator(new HttpTraceContext())
44+
* .build();
45+
* // Register it in the global propagators:
46+
* OpenTelemetry.setPropagators(
47+
* DefaultContextPropagators.builder()
48+
* .addHttpTextFormat(traceFormats)
49+
* .build());
50+
* ...
51+
* // Extraction will be performed in reverse order, i.e. starting with the last
52+
* // registered propagator (HttpTraceContext in this example).
53+
* Context context = OpenTelemetry.getPropagators().getHttpTextFormat()
54+
* .extract(context, carrier, carrierGetter);
55+
* }</pre>
56+
*
57+
* @since 0.6.0
58+
*/
59+
@Immutable
60+
public class TraceMultiPropagator implements HttpTextFormat {
61+
private final HttpTextFormat[] propagators;
62+
private final List<String> propagatorsFields;
63+
64+
private TraceMultiPropagator(List<HttpTextFormat> propagatorList) {
65+
this.propagators = new HttpTextFormat[propagatorList.size()];
66+
propagatorList.toArray(this.propagators);
67+
68+
List<String> fields = new ArrayList<>();
69+
for (HttpTextFormat propagator : propagators) {
70+
fields.addAll(propagator.fields());
71+
}
72+
this.propagatorsFields = Collections.unmodifiableList(fields);
73+
}
74+
75+
/**
76+
* Returns a {@link TraceMultiPropagator.Builder} to create a new {@link TraceMultiPropagator}
77+
* object.
78+
*
79+
* @return a {@link TraceMultiPropagator.Builder}.
80+
* @since 0.6.0
81+
*/
82+
public static Builder builder() {
83+
return new Builder();
84+
}
85+
86+
/**
87+
* The propagation fields defined in all the registered propagators. The returned list will be
88+
* read-only.
89+
*
90+
* @return list of fields defined in all the registered propagators.
91+
* @since 0.6.0
92+
*/
93+
@Override
94+
public List<String> fields() {
95+
return propagatorsFields;
96+
}
97+
98+
/**
99+
* Injects the value downstream invoking all the registered propagators, starting with the last
100+
* one.
101+
*
102+
* @param context the {@code Context} containing the value to be injected.
103+
* @param carrier holds propagation fields. For example, an outgoing message or http request.
104+
* @param setter invoked for each propagation key to add or remove.
105+
* @param <C> carrier of propagation fields, such as an http request
106+
* @since 0.6.0
107+
*/
108+
@Override
109+
public <C> void inject(Context context, C carrier, Setter<C> setter) {
110+
for (int i = propagators.length - 1; i >= 0; i--) {
111+
propagators[i].inject(context, carrier, setter);
112+
}
113+
}
114+
115+
/**
116+
* Extracts the value from upstream invoking all the registered propagators, starting with the
117+
* last one. Iterating over the propagators will stop and return immediately upon the first
118+
* successful extraction.
119+
*
120+
* @param context the {@code Context} used to store the extracted value.
121+
* @param carrier holds propagation fields. For example, an outgoing message or http request.
122+
* @param getter invoked for each propagation key to get.
123+
* @param <C> carrier of propagation fields, such as an http request.
124+
* @return the {@code Context} containing the extracted value.
125+
* @since 0.6.0
126+
*/
127+
@Override
128+
public <C> Context extract(Context context, C carrier, Getter<C> getter) {
129+
for (int i = propagators.length - 1; i >= 0; i--) {
130+
context = propagators[i].extract(context, carrier, getter);
131+
if (isSpanContextExtracted(context)) {
132+
break;
133+
}
134+
}
135+
136+
return context;
137+
}
138+
139+
private static boolean isSpanContextExtracted(Context context) {
140+
return TracingContextUtils.getSpanWithoutDefault(context) != null;
141+
}
142+
143+
/**
144+
* {@link Builder} is used to construct a new {@code TraceMultiPropagator} object with the
145+
* specified propagators.
146+
*
147+
* @since 0.6.0
148+
*/
149+
public static class Builder {
150+
private final List<HttpTextFormat> propagators;
151+
152+
private Builder() {
153+
propagators = new ArrayList<>();
154+
}
155+
156+
/**
157+
* Adds a {@link HttpTextFormat} trace propagator.
158+
*
159+
* <p>Registered propagators will be invoked in reverse order, starting with the last propagator
160+
* to the first one.
161+
*
162+
* @param propagator the propagator to be added.
163+
* @return this.
164+
* @throws NullPointerException if {@code propagator} is {@code null}.
165+
* @since 0.6.0
166+
*/
167+
public Builder addPropagator(HttpTextFormat propagator) {
168+
Objects.requireNonNull(propagator, "propagator");
169+
170+
propagators.add(propagator);
171+
return this;
172+
}
173+
174+
/**
175+
* Builds a new {@code TraceMultiPropagator} with the specified propagators.
176+
*
177+
* @return the newly created {@code TraceMultiPropagator} instance.
178+
* @since 0.6.0
179+
*/
180+
public TraceMultiPropagator build() {
181+
return new TraceMultiPropagator(propagators);
182+
}
183+
}
184+
}

extensions/trace_propagators/src/test/java/io/opentelemetry/extensions/trace/propagation/AwsXRayPropagatorTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import io.opentelemetry.trace.TraceId;
2929
import io.opentelemetry.trace.TraceState;
3030
import io.opentelemetry.trace.TracingContextUtils;
31+
import java.util.Collections;
3132
import java.util.LinkedHashMap;
3233
import java.util.Map;
3334
import javax.annotation.Nullable;
@@ -111,6 +112,15 @@ public void inject_WithTraceState() {
111112
"Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=0");
112113
}
113114

115+
@Test
116+
public void extract_Nothing() {
117+
// Context remains untouched.
118+
assertThat(
119+
xrayPropagator.extract(
120+
Context.current(), Collections.<String, String>emptyMap(), Map::get))
121+
.isSameInstanceAs(Context.current());
122+
}
123+
114124
@Test
115125
public void extract_SampledContext() {
116126
Map<String, String> carrier = new LinkedHashMap<>();

extensions/trace_propagators/src/test/java/io/opentelemetry/extensions/trace/propagation/B3PropagatorTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import io.opentelemetry.trace.TraceId;
2929
import io.opentelemetry.trace.TraceState;
3030
import io.opentelemetry.trace.TracingContextUtils;
31+
import java.util.Collections;
3132
import java.util.HashMap;
3233
import java.util.LinkedHashMap;
3334
import java.util.Map;
@@ -132,6 +133,15 @@ public void inject_NotSampledContext() {
132133
assertThat(carrier).containsEntry(B3Propagator.SAMPLED_HEADER, "0");
133134
}
134135

136+
@Test
137+
public void extract_Nothing() {
138+
// Context remains untouched.
139+
assertThat(
140+
b3Propagator.extract(
141+
Context.current(), Collections.<String, String>emptyMap(), Map::get))
142+
.isSameInstanceAs(Context.current());
143+
}
144+
135145
@Test
136146
public void extract_SampledContext_Int() {
137147
Map<String, String> carrier = new LinkedHashMap<>();
@@ -296,6 +306,15 @@ public void inject_NotSampledContext_SingleHeader() {
296306
B3Propagator.COMBINED_HEADER, TRACE_ID_BASE16 + "-" + SPAN_ID_BASE16 + "-" + "0");
297307
}
298308

309+
@Test
310+
public void extract_Nothing_SingleHeader() {
311+
// Context remains untouched.
312+
assertThat(
313+
b3PropagatorSingleHeader.extract(
314+
Context.current(), Collections.<String, String>emptyMap(), Map::get))
315+
.isSameInstanceAs(Context.current());
316+
}
317+
299318
@Test
300319
public void extract_SampledContext_Int_SingleHeader() {
301320
Map<String, String> carrier = new LinkedHashMap<>();

extensions/trace_propagators/src/test/java/io/opentelemetry/extensions/trace/propagation/JaegerPropagatorTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import io.opentelemetry.trace.TracingContextUtils;
3636
import java.io.UnsupportedEncodingException;
3737
import java.net.URLEncoder;
38+
import java.util.Collections;
3839
import java.util.LinkedHashMap;
3940
import java.util.Map;
4041
import javax.annotation.Nullable;
@@ -152,6 +153,15 @@ public void inject_NotSampledContext() {
152153
TRACE_ID_BASE16, SPAN_ID_BASE16, DEPRECATED_PARENT_SPAN, "0"));
153154
}
154155

156+
@Test
157+
public void extract_Nothing() {
158+
// Context remains untouched.
159+
assertThat(
160+
jaegerPropagator.extract(
161+
Context.current(), Collections.<String, String>emptyMap(), Map::get))
162+
.isSameInstanceAs(Context.current());
163+
}
164+
155165
@Test
156166
public void extract_EmptyHeaderValue() {
157167
Map<String, String> invalidHeaders = new LinkedHashMap<>();

0 commit comments

Comments
 (0)