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

add extended tracer module #759

Merged
merged 33 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
26cfa8e
add extended tracer module
zeitlinger Feb 23, 2023
5b55f2d
add extended tracer module
zeitlinger Jul 14, 2023
90632f7
add extended tracer module
zeitlinger Jul 14, 2023
26f2053
limit scope to Tracing utility class
zeitlinger Jul 27, 2023
c3a3fba
make it clear that passed in span is ended
zeitlinger Aug 7, 2023
0ed2bd6
extract GlobalTracing to make it clear where the global otel instance…
zeitlinger Aug 7, 2023
dd17061
make OpenTelemetry a field in Tracing
zeitlinger Aug 8, 2023
7d1da59
- remove GlobalTracing.java for now (discussion pending)
zeitlinger Aug 25, 2023
7df83e7
add before/after comparison for extended tracer
zeitlinger Aug 25, 2023
176f116
- remove GlobalTracing.java for now (discussion pending)
zeitlinger Aug 25, 2023
9619428
add before/after comparison for extended tracer
zeitlinger Aug 25, 2023
fa0a83a
add before/after comparison for extended tracer
zeitlinger Aug 25, 2023
8e348cc
- catch & re-throw Throwable over Exception everywhere
zeitlinger Aug 28, 2023
5cbb7f8
add javadoc
zeitlinger Sep 4, 2023
db2263d
take span builder instead of span to reinforce that the span is start…
zeitlinger Sep 6, 2023
6b35e78
use throwing supplier which avoids using sneaky throws
zeitlinger Sep 18, 2023
9c0cd7a
remove non-essential parts of Tracing
zeitlinger Sep 18, 2023
8c59ebe
Update extended-tracer/src/main/java/io/opentelemetry/contrib/tracer/…
zeitlinger Sep 25, 2023
5c948ba
Update extended-tracer/build.gradle.kts
zeitlinger Sep 25, 2023
13c3819
Update extended-tracer/build.gradle.kts
zeitlinger Sep 25, 2023
12dccfa
Update extended-tracer/src/main/java/io/opentelemetry/contrib/tracer/…
zeitlinger Sep 25, 2023
c060404
rename ThrowingSupplier to SpanCallback
zeitlinger Sep 25, 2023
b0500ba
Update extended-tracer/src/main/java/io/opentelemetry/contrib/tracer/…
zeitlinger Oct 2, 2023
bb9e704
rename transport to carrier
zeitlinger Oct 2, 2023
afbefa0
add SpanRunnable similar to SpanCallback
zeitlinger Oct 4, 2023
a264436
rename propagation methods
zeitlinger Oct 4, 2023
18ed800
add SpanRunnable similar to SpanCallback
zeitlinger Oct 5, 2023
ad1b988
add more tests to show usage
zeitlinger Oct 5, 2023
7cd4616
- make callWithBaggage static
zeitlinger Oct 6, 2023
68e1b5c
- make callWithBaggage static
zeitlinger Oct 12, 2023
07e55dd
- make callWithBaggage static
zeitlinger Oct 26, 2023
c6e0b9c
Update extended-tracer/build.gradle.kts
zeitlinger Nov 1, 2023
11f07bf
Update extended-tracer/build.gradle.kts
zeitlinger Nov 6, 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
1 change: 1 addition & 0 deletions .github/scripts/draft-change-log-entries.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ component_names["maven-extension/"]="Maven extension"
component_names["micrometer-meter-provider/"]="Micrometer MeterProvider"
component_names["noop-api/"]="No-op API"
component_names["processors/"]="Telemetry processors"
component_names["extended-tracer/"]="Extended Tracer"
component_names["prometheus-client-bridge/"]="Prometheus client bridge"
component_names["runtime-attach/"]="Runtime attach"
component_names["resource-providers/"]="Resource providers"
Expand Down
168 changes: 168 additions & 0 deletions extended-tracer/README.md
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));
zeitlinger marked this conversation as resolved.
Show resolved Hide resolved
```

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
trask marked this conversation as resolved.
Show resolved Hide resolved

Learn more about component owners in [component_owners.yml](../.github/component_owners.yml).
12 changes: 12 additions & 0 deletions extended-tracer/build.gradle.kts
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")
zeitlinger marked this conversation as resolved.
Show resolved Hide resolved
}
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);
}
}
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;
}
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;
}
Loading
Loading