-
Notifications
You must be signed in to change notification settings - Fork 834
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: jack-berg <34418638+jack-berg@users.noreply.github.com> Co-authored-by: Jack Berg <jberg@newrelic.com>
- Loading branch information
1 parent
c8e1e36
commit 2b33f9f
Showing
9 changed files
with
734 additions
and
109 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
# ExtendedTracer | ||
|
||
Utility methods to make it easier to use the OpenTelemetry tracer. | ||
|
||
## Usage Examples | ||
|
||
Here are some examples how the utility methods can help reduce boilerplate code. | ||
|
||
### Tracing a function | ||
|
||
Before: | ||
|
||
<!-- markdownlint-disable --> | ||
```java | ||
Span span = tracer.spanBuilder("reset_checkout").startSpan(); | ||
String transactionId; | ||
try (Scope scope = span.makeCurrent()) { | ||
transactionId = resetCheckout(cartId); | ||
} catch (Throwable e) { | ||
span.setStatus(StatusCode.ERROR); | ||
span.recordException(e); | ||
throw e; // or throw new RuntimeException(e) - depending on your error handling strategy | ||
} finally { | ||
span.end(); | ||
} | ||
``` | ||
<!-- markdownlint-enable --> | ||
|
||
After: | ||
|
||
```java | ||
import io.opentelemetry.extension.incubator.trace.ExtendedTracer; | ||
|
||
ExtendedTracer extendedTracer = ExtendedTracer.create(tracer); | ||
String transactionId = extendedTracer.spanBuilder("reset_checkout").startAndCall(() -> resetCheckout(cartId)); | ||
``` | ||
|
||
If you want to set attributes on the span, you can use the `startAndCall` method on the span builder: | ||
|
||
```java | ||
import io.opentelemetry.extension.incubator.trace.ExtendedTracer; | ||
|
||
ExtendedTracer extendedTracer = ExtendedTracer.create(tracer); | ||
String transactionId = extendedTracer.spanBuilder("reset_checkout") | ||
.setAttribute("foo", "bar") | ||
.startAndCall(() -> resetCheckout(cartId)); | ||
``` | ||
|
||
Note: | ||
|
||
- Use `startAndRun` instead of `startAndCall` if the function returns `void` (both on the tracer and span builder). | ||
- Exceptions are re-thrown without modification - see [Exception handling](#exception-handling) | ||
for more details. | ||
|
||
### Trace context propagation | ||
|
||
Before: | ||
|
||
```java | ||
Map<String, String> propagationHeaders = new HashMap<>(); | ||
openTelemetry | ||
.getPropagators() | ||
.getTextMapPropagator() | ||
.inject( | ||
Context.current(), | ||
propagationHeaders, | ||
(map, key, value) -> { | ||
if (map != null) { | ||
map.put(key, value); | ||
} | ||
}); | ||
|
||
// add propagationHeaders to request headers and call checkout service | ||
``` | ||
|
||
<!-- markdownlint-disable --> | ||
```java | ||
// in checkout service: get request headers into a Map<String, String> requestHeaders | ||
Map<String, String> requestHeaders = new HashMap<>(); | ||
String cartId = "cartId"; | ||
|
||
SpanBuilder spanBuilder = tracer.spanBuilder("checkout_cart"); | ||
|
||
TextMapGetter<Map<String, String>> TEXT_MAP_GETTER = | ||
new TextMapGetter<Map<String, String>>() { | ||
@Override | ||
public Set<String> keys(Map<String, String> carrier) { | ||
return carrier.keySet(); | ||
} | ||
|
||
@Override | ||
@Nullable | ||
public String get(@Nullable Map<String, String> carrier, String key) { | ||
return carrier == null ? null : carrier.get(key); | ||
} | ||
}; | ||
|
||
Map<String, String> normalizedTransport = | ||
requestHeaders.entrySet().stream() | ||
.collect( | ||
Collectors.toMap( | ||
entry -> entry.getKey().toLowerCase(Locale.ROOT), Map.Entry::getValue)); | ||
Context newContext = openTelemetry | ||
.getPropagators() | ||
.getTextMapPropagator() | ||
.extract(Context.current(), normalizedTransport, TEXT_MAP_GETTER); | ||
String transactionId; | ||
try (Scope ignore = newContext.makeCurrent()) { | ||
Span span = spanBuilder.setSpanKind(SERVER).startSpan(); | ||
try (Scope scope = span.makeCurrent()) { | ||
transactionId = processCheckout(cartId); | ||
} catch (Throwable e) { | ||
span.setStatus(StatusCode.ERROR); | ||
span.recordException(e); | ||
throw e; // or throw new RuntimeException(e) - depending on your error handling strategy | ||
} finally { | ||
span.end(); | ||
} | ||
} | ||
``` | ||
<!-- markdownlint-enable --> | ||
|
||
After: | ||
|
||
```java | ||
import io.opentelemetry.extension.incubator.propagation.ExtendedContextPropagators; | ||
|
||
Map<String, String> propagationHeaders = | ||
ExtendedContextPropagators.getTextMapPropagationContext(openTelemetry.getPropagators()); | ||
// add propagationHeaders to request headers and call checkout service | ||
``` | ||
|
||
```java | ||
import io.opentelemetry.api.trace.SpanKind; | ||
import io.opentelemetry.extension.incubator.trace.ExtendedTracer; | ||
|
||
// in checkout service: get request headers into a Map<String, String> requestHeaders | ||
Map<String, String> requestHeaders = new HashMap<>(); | ||
String cartId = "cartId"; | ||
|
||
ExtendedTracer extendedTracer = ExtendedTracer.create(tracer); | ||
String transactionId = extendedTracer.spanBuilder("checkout_cart") | ||
.setSpanKind(SpanKind.SERVER) | ||
.setParentFrom(openTelemetry.getPropagators(), requestHeaders) | ||
.startAndCall(() -> processCheckout(cartId)); | ||
``` | ||
|
||
## Exception handling | ||
|
||
`ExtendedTracer` re-throws exceptions without modification. This means you can | ||
catch exceptions around `ExtendedTracer` calls and handle them as you would without `ExtendedTracer`. | ||
|
||
When an exception is encountered during an `ExtendedTracer` call, the span is marked as error and | ||
the exception is recorded. | ||
|
||
If you want to customize this behaviour, e.g. to only record the exception, because you are | ||
able to recover from the error, you can call the overloaded method of `startAndCall` or | ||
`startAndRun` that takes an exception handler: | ||
|
||
```java | ||
import io.opentelemetry.api.trace.Span; | ||
import io.opentelemetry.extension.incubator.trace.ExtendedTracer; | ||
|
||
ExtendedTracer extendedTracer = ExtendedTracer.create(tracer); | ||
String transactionId = extendedTracer.spanBuilder("checkout_cart") | ||
.startAndCall(() -> processCheckout(cartId), Span::recordException); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
...or/src/main/java/io/opentelemetry/extension/incubator/propagation/CaseInsensitiveMap.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.extension.incubator.propagation; | ||
|
||
import java.util.HashMap; | ||
import java.util.Locale; | ||
import java.util.Map; | ||
import javax.annotation.Nullable; | ||
|
||
class CaseInsensitiveMap extends HashMap<String, String> { | ||
|
||
private static final long serialVersionUID = -4202518750189126871L; | ||
|
||
CaseInsensitiveMap(Map<String, String> carrier) { | ||
super(carrier); | ||
} | ||
|
||
@Override | ||
public String put(String key, String value) { | ||
return super.put(key.toLowerCase(Locale.ROOT), value); | ||
} | ||
|
||
@Override | ||
@Nullable | ||
public String get(Object key) { | ||
return super.get(((String) key).toLowerCase(Locale.ROOT)); | ||
} | ||
} |
81 changes: 81 additions & 0 deletions
81
...ain/java/io/opentelemetry/extension/incubator/propagation/ExtendedContextPropagators.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.extension.incubator.propagation; | ||
|
||
import io.opentelemetry.api.OpenTelemetry; | ||
import io.opentelemetry.context.Context; | ||
import io.opentelemetry.context.propagation.ContextPropagators; | ||
import io.opentelemetry.context.propagation.TextMapGetter; | ||
import java.util.Collections; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import javax.annotation.Nullable; | ||
|
||
/** | ||
* Utility class to simplify context propagation. | ||
* | ||
* <p>The <a | ||
* href="https://github.com/open-telemetry/opentelemetry-java-contrib/blob/main/extended-tracer/README.md">README</a> | ||
* explains the use cases in more detail. | ||
*/ | ||
public final class ExtendedContextPropagators { | ||
|
||
private ExtendedContextPropagators() {} | ||
|
||
private static final TextMapGetter<Map<String, String>> TEXT_MAP_GETTER = | ||
new TextMapGetter<Map<String, String>>() { | ||
@Override | ||
public Set<String> keys(Map<String, String> carrier) { | ||
return carrier.keySet(); | ||
} | ||
|
||
@Override | ||
@Nullable | ||
public String get(@Nullable Map<String, String> carrier, String key) { | ||
return carrier == null ? null : carrier.get(key); | ||
} | ||
}; | ||
|
||
/** | ||
* Injects the current context into a string map, which can then be added to HTTP headers or the | ||
* metadata of a message. | ||
* | ||
* @param propagators provide the propagators from {@link OpenTelemetry#getPropagators()} | ||
*/ | ||
public static Map<String, String> getTextMapPropagationContext(ContextPropagators propagators) { | ||
Map<String, String> carrier = new HashMap<>(); | ||
propagators | ||
.getTextMapPropagator() | ||
.inject( | ||
Context.current(), | ||
carrier, | ||
(map, key, value) -> { | ||
if (map != null) { | ||
map.put(key, value); | ||
} | ||
}); | ||
|
||
return Collections.unmodifiableMap(carrier); | ||
} | ||
|
||
/** | ||
* Extract the context from a string map, which you get from HTTP headers of the metadata of a | ||
* message you're processing. | ||
* | ||
* @param carrier the string map | ||
* @param propagators provide the propagators from {@link OpenTelemetry#getPropagators()} | ||
*/ | ||
public static Context extractTextMapPropagationContext( | ||
Map<String, String> carrier, ContextPropagators propagators) { | ||
Context current = Context.current(); | ||
if (carrier == null) { | ||
return current; | ||
} | ||
CaseInsensitiveMap caseInsensitiveMap = new CaseInsensitiveMap(carrier); | ||
return propagators.getTextMapPropagator().extract(current, caseInsensitiveMap, TEXT_MAP_GETTER); | ||
} | ||
} |
Oops, something went wrong.