Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extended tracer #6017

Merged
merged 21 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
3c5b9df
copy extended tracer from contrib
zeitlinger Nov 27, 2023
5d88915
use ExtendedTracer instead of Tracing and implement Tracer interface
zeitlinger Nov 27, 2023
27fb8d3
- provide propagators as params instead of otel instance
zeitlinger Dec 1, 2023
1a1a082
make extractTextMapPropagationContext public, because it's in a separ…
zeitlinger Dec 1, 2023
da6b31f
move Propagation to io.opentelemetry.extension.incubator.propagation
zeitlinger Dec 1, 2023
a7aa285
remove unneeded suppressions
zeitlinger Dec 1, 2023
2d1ea95
Update extensions/incubator/src/main/java/io/opentelemetry/extension/…
zeitlinger Dec 1, 2023
a326af1
Apply suggestions from code review
zeitlinger Dec 1, 2023
5a7b787
fix build
zeitlinger Dec 1, 2023
86620f0
- use case insensitive map
zeitlinger Dec 1, 2023
55ae7f2
review comments
zeitlinger Dec 5, 2023
bfa3fd4
cleanup
zeitlinger Dec 5, 2023
a86016f
simplify extended tracer by moving most methods to the new ExtendedSp…
zeitlinger Dec 7, 2023
6debd1b
simplify extended tracer by moving most methods to the new ExtendedSp…
zeitlinger Dec 7, 2023
8d3ffa4
- fix propagation
zeitlinger Dec 7, 2023
ed829f6
Update extensions/incubator/src/main/java/io/opentelemetry/extension/…
zeitlinger Dec 7, 2023
943f2a5
review comments
zeitlinger Dec 7, 2023
2a3dad5
scope out callWithBaggage for now
zeitlinger Dec 7, 2023
4c0a1f6
review comments
zeitlinger Dec 7, 2023
6b8ec36
review comments
zeitlinger Dec 7, 2023
f99f7ed
Relocate / fix readme, minimize visibility
jack-berg Dec 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 167 additions & 0 deletions extensions/incubator/README.md
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);
```
1 change: 1 addition & 0 deletions extensions/incubator/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ dependencies {

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

testImplementation("io.opentelemetry.semconv:opentelemetry-semconv:1.21.0-alpha")
testImplementation(project(":sdk:testing"))
}
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);

Check warning on line 23 in extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/propagation/CaseInsensitiveMap.java

View check run for this annotation

Codecov / codecov/patch

extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/propagation/CaseInsensitiveMap.java#L23

Added line #L23 was not covered by tests
}

@Override
@Nullable
public String get(Object key) {
return super.get(((String) key).toLowerCase(Locale.ROOT));
}
}
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();

Check warning on line 33 in extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/propagation/ExtendedContextPropagators.java

View check run for this annotation

Codecov / codecov/patch

extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/propagation/ExtendedContextPropagators.java#L33

Added line #L33 was not covered by tests
}

@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;

Check warning on line 76 in extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/propagation/ExtendedContextPropagators.java

View check run for this annotation

Codecov / codecov/patch

extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/propagation/ExtendedContextPropagators.java#L76

Added line #L76 was not covered by tests
}
CaseInsensitiveMap caseInsensitiveMap = new CaseInsensitiveMap(carrier);
return propagators.getTextMapPropagator().extract(current, caseInsensitiveMap, TEXT_MAP_GETTER);
}
}
Loading