-
Notifications
You must be signed in to change notification settings - Fork 131
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: Trask Stalnaker <[email protected]>
- Loading branch information
1 parent
1b7d0c6
commit 2983ea7
Showing
9 changed files
with
827 additions
and
0 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
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,168 @@ | ||
# extended-tracer | ||
|
||
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: | ||
|
||
```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(); | ||
} | ||
``` | ||
|
||
After: | ||
|
||
```java | ||
Tracing tracing = new Tracing(openTelemetry, "service"); | ||
String transactionId = tracing.call("reset_checkout", () -> resetCheckout(cartId)); | ||
``` | ||
|
||
Note: | ||
|
||
- Use `run` instead of `call` if the function returns `void`. | ||
- 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 | ||
``` | ||
|
||
```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"); | ||
String transactionId; | ||
|
||
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) { | ||
//noinspection ConstantConditions | ||
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); | ||
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(); | ||
} | ||
} | ||
``` | ||
|
||
After: | ||
|
||
```java | ||
Tracing tracing = new Tracing(openTelemetry, "service"); | ||
Map<String, String> propagationHeaders = tracing.getTextMapPropagationContext(); | ||
// add propagationHeaders to request headers and call checkout service | ||
``` | ||
|
||
```java | ||
// in checkout service: get request headers into a Map<String, String> requestHeaders | ||
Map<String, String> requestHeaders = new HashMap<>(); | ||
String cartId = "cartId"; | ||
|
||
Tracing tracing = new Tracing(openTelemetry, "service"); | ||
String transactionId = tracing.traceServerSpan(requestHeaders, | ||
tracer.spanBuilder("checkout_cart"), () -> processCheckout(cartId)); | ||
``` | ||
|
||
Note: | ||
|
||
- You can use `traceConsumerSpan` if you want to trace a consumer (e.g. from a message queue) | ||
instead of a server. | ||
- Exceptions are re-thrown without modification - see [Exception handling](#exception-handling) | ||
for more details. | ||
|
||
### Setting baggage entries | ||
|
||
Before: | ||
|
||
```java | ||
BaggageBuilder builder = Baggage.current().toBuilder(); | ||
builder.put("key", "value"); | ||
Context context = builder.build().storeInContext(Context.current()); | ||
try (Scope ignore = context.makeCurrent()) { | ||
String value = Baggage.current().getEntryValue("key"); | ||
} | ||
``` | ||
|
||
After: | ||
|
||
```java | ||
Tracing tracing = new Tracing(openTelemetry, "service"); | ||
String value = Tracing.callWithBaggage( | ||
Collections.singletonMap("key", "value"), | ||
() -> Baggage.current().getEntryValue("key")) | ||
``` | ||
|
||
## Exception handling | ||
|
||
`Tracing` re-throws exceptions without modification. This means you can catch exceptions around | ||
`Tracing` calls and handle them as you would without `Tracing`. | ||
|
||
Note that the `Tracing` methods do not declare any checked exceptions | ||
(the idea is taken from [@SneakyThrows](https://projectlombok.org/features/SneakyThrows)). | ||
Declaring a checked exception would force callers to handle it, which would create a lot of | ||
boilerplate code. Instead, `Tracing` re-throws checked exceptions as unchecked exceptions. | ||
|
||
## Component owners | ||
|
||
- [Gregor Zeitlinger](https://github.com/zeitlinger), Grafana Labs | ||
|
||
Learn more about component owners in [component_owners.yml](../.github/component_owners.yml). |
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,12 @@ | ||
plugins { | ||
id("otel.java-conventions") | ||
} | ||
|
||
description = "Extended Tracer" | ||
otelJava.moduleName.set("io.opentelemetry.contrib.extended-tracer") | ||
|
||
dependencies { | ||
api("io.opentelemetry:opentelemetry-api") | ||
testImplementation("io.opentelemetry:opentelemetry-sdk-testing") | ||
testImplementation("io.opentelemetry.semconv:opentelemetry-semconv") | ||
} |
91 changes: 91 additions & 0 deletions
91
extended-tracer/src/main/java/io/opentelemetry/contrib/tracer/Propagation.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,91 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.contrib.tracer; | ||
|
||
import io.opentelemetry.api.OpenTelemetry; | ||
import io.opentelemetry.context.Context; | ||
import io.opentelemetry.context.propagation.TextMapGetter; | ||
import java.util.HashMap; | ||
import java.util.Locale; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
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 Propagation { | ||
|
||
private Propagation() {} | ||
|
||
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) { | ||
//noinspection ConstantConditions | ||
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 an event. | ||
*/ | ||
public static Map<String, String> getTextMapPropagationContext(OpenTelemetry openTelemetry) { | ||
Map<String, String> carrier = new HashMap<>(); | ||
//noinspection ConstantConditions | ||
openTelemetry | ||
.getPropagators() | ||
.getTextMapPropagator() | ||
.inject( | ||
Context.current(), | ||
carrier, | ||
(map, key, value) -> { | ||
if (map != null) { | ||
map.put(key, value); | ||
} | ||
}); | ||
|
||
return carrier; | ||
} | ||
|
||
/** | ||
* Extract the context from a string map, which you get from HTTP headers of the metadata of an | ||
* event you're processing. | ||
* | ||
* @param carrier the string map | ||
*/ | ||
static Context extractTextMapPropagationContext( | ||
OpenTelemetry openTelemetry, Map<String, String> carrier) { | ||
Context current = Context.current(); | ||
//noinspection ConstantConditions | ||
if (carrier == null) { | ||
return current; | ||
} | ||
// HTTP headers are case-insensitive. As we're using Map, which is case-sensitive, we need to | ||
// normalize all the keys | ||
Map<String, String> normalizedCarrier = | ||
carrier.entrySet().stream() | ||
.collect( | ||
Collectors.toMap( | ||
entry -> entry.getKey().toLowerCase(Locale.ROOT), Map.Entry::getValue)); | ||
return openTelemetry | ||
.getPropagators() | ||
.getTextMapPropagator() | ||
.extract(current, normalizedCarrier, TEXT_MAP_GETTER); | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
extended-tracer/src/main/java/io/opentelemetry/contrib/tracer/SpanCallback.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,18 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.contrib.tracer; | ||
|
||
/** | ||
* An interface for creating a lambda that is wrapped in a span, returns a value, and that may | ||
* throw, similar to <a | ||
* href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/support/TransactionCallback.html">TransactionCallback</a>. | ||
* | ||
* @param <E> Thrown exception type. | ||
*/ | ||
@FunctionalInterface | ||
public interface SpanCallback<T, E extends Throwable> { | ||
T doInSpan() throws E; | ||
} |
17 changes: 17 additions & 0 deletions
17
extended-tracer/src/main/java/io/opentelemetry/contrib/tracer/SpanRunnable.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,17 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.contrib.tracer; | ||
|
||
/** | ||
* An interface for creating a lambda that is wrapped in a span and that may throw, similar to <a | ||
* href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/support/TransactionCallback.html">TransactionCallback</a>. | ||
* | ||
* @param <E> Thrown exception type. | ||
*/ | ||
@FunctionalInterface | ||
public interface SpanRunnable<E extends Throwable> { | ||
void doInSpan() throws E; | ||
} |
Oops, something went wrong.