Skip to content

Commit 2b33f9f

Browse files
Extended tracer (#6017)
Co-authored-by: jack-berg <[email protected]> Co-authored-by: Jack Berg <[email protected]>
1 parent c8e1e36 commit 2b33f9f

File tree

9 files changed

+734
-109
lines changed

9 files changed

+734
-109
lines changed

extensions/incubator/README.md

+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# ExtendedTracer
2+
3+
Utility methods to make it easier to use the OpenTelemetry tracer.
4+
5+
## Usage Examples
6+
7+
Here are some examples how the utility methods can help reduce boilerplate code.
8+
9+
### Tracing a function
10+
11+
Before:
12+
13+
<!-- markdownlint-disable -->
14+
```java
15+
Span span = tracer.spanBuilder("reset_checkout").startSpan();
16+
String transactionId;
17+
try (Scope scope = span.makeCurrent()) {
18+
transactionId = resetCheckout(cartId);
19+
} catch (Throwable e) {
20+
span.setStatus(StatusCode.ERROR);
21+
span.recordException(e);
22+
throw e; // or throw new RuntimeException(e) - depending on your error handling strategy
23+
} finally {
24+
span.end();
25+
}
26+
```
27+
<!-- markdownlint-enable -->
28+
29+
After:
30+
31+
```java
32+
import io.opentelemetry.extension.incubator.trace.ExtendedTracer;
33+
34+
ExtendedTracer extendedTracer = ExtendedTracer.create(tracer);
35+
String transactionId = extendedTracer.spanBuilder("reset_checkout").startAndCall(() -> resetCheckout(cartId));
36+
```
37+
38+
If you want to set attributes on the span, you can use the `startAndCall` method on the span builder:
39+
40+
```java
41+
import io.opentelemetry.extension.incubator.trace.ExtendedTracer;
42+
43+
ExtendedTracer extendedTracer = ExtendedTracer.create(tracer);
44+
String transactionId = extendedTracer.spanBuilder("reset_checkout")
45+
.setAttribute("foo", "bar")
46+
.startAndCall(() -> resetCheckout(cartId));
47+
```
48+
49+
Note:
50+
51+
- Use `startAndRun` instead of `startAndCall` if the function returns `void` (both on the tracer and span builder).
52+
- Exceptions are re-thrown without modification - see [Exception handling](#exception-handling)
53+
for more details.
54+
55+
### Trace context propagation
56+
57+
Before:
58+
59+
```java
60+
Map<String, String> propagationHeaders = new HashMap<>();
61+
openTelemetry
62+
.getPropagators()
63+
.getTextMapPropagator()
64+
.inject(
65+
Context.current(),
66+
propagationHeaders,
67+
(map, key, value) -> {
68+
if (map != null) {
69+
map.put(key, value);
70+
}
71+
});
72+
73+
// add propagationHeaders to request headers and call checkout service
74+
```
75+
76+
<!-- markdownlint-disable -->
77+
```java
78+
// in checkout service: get request headers into a Map<String, String> requestHeaders
79+
Map<String, String> requestHeaders = new HashMap<>();
80+
String cartId = "cartId";
81+
82+
SpanBuilder spanBuilder = tracer.spanBuilder("checkout_cart");
83+
84+
TextMapGetter<Map<String, String>> TEXT_MAP_GETTER =
85+
new TextMapGetter<Map<String, String>>() {
86+
@Override
87+
public Set<String> keys(Map<String, String> carrier) {
88+
return carrier.keySet();
89+
}
90+
91+
@Override
92+
@Nullable
93+
public String get(@Nullable Map<String, String> carrier, String key) {
94+
return carrier == null ? null : carrier.get(key);
95+
}
96+
};
97+
98+
Map<String, String> normalizedTransport =
99+
requestHeaders.entrySet().stream()
100+
.collect(
101+
Collectors.toMap(
102+
entry -> entry.getKey().toLowerCase(Locale.ROOT), Map.Entry::getValue));
103+
Context newContext = openTelemetry
104+
.getPropagators()
105+
.getTextMapPropagator()
106+
.extract(Context.current(), normalizedTransport, TEXT_MAP_GETTER);
107+
String transactionId;
108+
try (Scope ignore = newContext.makeCurrent()) {
109+
Span span = spanBuilder.setSpanKind(SERVER).startSpan();
110+
try (Scope scope = span.makeCurrent()) {
111+
transactionId = processCheckout(cartId);
112+
} catch (Throwable e) {
113+
span.setStatus(StatusCode.ERROR);
114+
span.recordException(e);
115+
throw e; // or throw new RuntimeException(e) - depending on your error handling strategy
116+
} finally {
117+
span.end();
118+
}
119+
}
120+
```
121+
<!-- markdownlint-enable -->
122+
123+
After:
124+
125+
```java
126+
import io.opentelemetry.extension.incubator.propagation.ExtendedContextPropagators;
127+
128+
Map<String, String> propagationHeaders =
129+
ExtendedContextPropagators.getTextMapPropagationContext(openTelemetry.getPropagators());
130+
// add propagationHeaders to request headers and call checkout service
131+
```
132+
133+
```java
134+
import io.opentelemetry.api.trace.SpanKind;
135+
import io.opentelemetry.extension.incubator.trace.ExtendedTracer;
136+
137+
// in checkout service: get request headers into a Map<String, String> requestHeaders
138+
Map<String, String> requestHeaders = new HashMap<>();
139+
String cartId = "cartId";
140+
141+
ExtendedTracer extendedTracer = ExtendedTracer.create(tracer);
142+
String transactionId = extendedTracer.spanBuilder("checkout_cart")
143+
.setSpanKind(SpanKind.SERVER)
144+
.setParentFrom(openTelemetry.getPropagators(), requestHeaders)
145+
.startAndCall(() -> processCheckout(cartId));
146+
```
147+
148+
## Exception handling
149+
150+
`ExtendedTracer` re-throws exceptions without modification. This means you can
151+
catch exceptions around `ExtendedTracer` calls and handle them as you would without `ExtendedTracer`.
152+
153+
When an exception is encountered during an `ExtendedTracer` call, the span is marked as error and
154+
the exception is recorded.
155+
156+
If you want to customize this behaviour, e.g. to only record the exception, because you are
157+
able to recover from the error, you can call the overloaded method of `startAndCall` or
158+
`startAndRun` that takes an exception handler:
159+
160+
```java
161+
import io.opentelemetry.api.trace.Span;
162+
import io.opentelemetry.extension.incubator.trace.ExtendedTracer;
163+
164+
ExtendedTracer extendedTracer = ExtendedTracer.create(tracer);
165+
String transactionId = extendedTracer.spanBuilder("checkout_cart")
166+
.startAndCall(() -> processCheckout(cartId), Span::recordException);
167+
```

extensions/incubator/build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ dependencies {
1414

1515
annotationProcessor("com.google.auto.value:auto-value")
1616

17+
testImplementation("io.opentelemetry.semconv:opentelemetry-semconv:1.21.0-alpha")
1718
testImplementation(project(":sdk:testing"))
1819
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.extension.incubator.propagation;
7+
8+
import java.util.HashMap;
9+
import java.util.Locale;
10+
import java.util.Map;
11+
import javax.annotation.Nullable;
12+
13+
class CaseInsensitiveMap extends HashMap<String, String> {
14+
15+
private static final long serialVersionUID = -4202518750189126871L;
16+
17+
CaseInsensitiveMap(Map<String, String> carrier) {
18+
super(carrier);
19+
}
20+
21+
@Override
22+
public String put(String key, String value) {
23+
return super.put(key.toLowerCase(Locale.ROOT), value);
24+
}
25+
26+
@Override
27+
@Nullable
28+
public String get(Object key) {
29+
return super.get(((String) key).toLowerCase(Locale.ROOT));
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.extension.incubator.propagation;
7+
8+
import io.opentelemetry.api.OpenTelemetry;
9+
import io.opentelemetry.context.Context;
10+
import io.opentelemetry.context.propagation.ContextPropagators;
11+
import io.opentelemetry.context.propagation.TextMapGetter;
12+
import java.util.Collections;
13+
import java.util.HashMap;
14+
import java.util.Map;
15+
import java.util.Set;
16+
import javax.annotation.Nullable;
17+
18+
/**
19+
* Utility class to simplify context propagation.
20+
*
21+
* <p>The <a
22+
* href="https://github.com/open-telemetry/opentelemetry-java-contrib/blob/main/extended-tracer/README.md">README</a>
23+
* explains the use cases in more detail.
24+
*/
25+
public final class ExtendedContextPropagators {
26+
27+
private ExtendedContextPropagators() {}
28+
29+
private static final TextMapGetter<Map<String, String>> TEXT_MAP_GETTER =
30+
new TextMapGetter<Map<String, String>>() {
31+
@Override
32+
public Set<String> keys(Map<String, String> carrier) {
33+
return carrier.keySet();
34+
}
35+
36+
@Override
37+
@Nullable
38+
public String get(@Nullable Map<String, String> carrier, String key) {
39+
return carrier == null ? null : carrier.get(key);
40+
}
41+
};
42+
43+
/**
44+
* Injects the current context into a string map, which can then be added to HTTP headers or the
45+
* metadata of a message.
46+
*
47+
* @param propagators provide the propagators from {@link OpenTelemetry#getPropagators()}
48+
*/
49+
public static Map<String, String> getTextMapPropagationContext(ContextPropagators propagators) {
50+
Map<String, String> carrier = new HashMap<>();
51+
propagators
52+
.getTextMapPropagator()
53+
.inject(
54+
Context.current(),
55+
carrier,
56+
(map, key, value) -> {
57+
if (map != null) {
58+
map.put(key, value);
59+
}
60+
});
61+
62+
return Collections.unmodifiableMap(carrier);
63+
}
64+
65+
/**
66+
* Extract the context from a string map, which you get from HTTP headers of the metadata of a
67+
* message you're processing.
68+
*
69+
* @param carrier the string map
70+
* @param propagators provide the propagators from {@link OpenTelemetry#getPropagators()}
71+
*/
72+
public static Context extractTextMapPropagationContext(
73+
Map<String, String> carrier, ContextPropagators propagators) {
74+
Context current = Context.current();
75+
if (carrier == null) {
76+
return current;
77+
}
78+
CaseInsensitiveMap caseInsensitiveMap = new CaseInsensitiveMap(carrier);
79+
return propagators.getTextMapPropagator().extract(current, caseInsensitiveMap, TEXT_MAP_GETTER);
80+
}
81+
}

0 commit comments

Comments
 (0)