Extended tracer (#6017)
Co-authored-by: jack-berg <34418638+jack-berg@users.noreply.github.com> Co-authored-by: Jack Berg <jberg@newrelic.com>
This commit is contained in:
parent
c8e1e3619e
commit
2b33f9fb07
|
@ -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);
|
||||
```
|
|
@ -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"))
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.extension.incubator.trace;
|
||||
|
||||
import io.opentelemetry.api.OpenTelemetry;
|
||||
import io.opentelemetry.api.common.AttributeKey;
|
||||
import io.opentelemetry.api.common.Attributes;
|
||||
import io.opentelemetry.api.trace.Span;
|
||||
import io.opentelemetry.api.trace.SpanBuilder;
|
||||
import io.opentelemetry.api.trace.SpanContext;
|
||||
import io.opentelemetry.api.trace.SpanKind;
|
||||
import io.opentelemetry.api.trace.StatusCode;
|
||||
import io.opentelemetry.context.Context;
|
||||
import io.opentelemetry.context.Scope;
|
||||
import io.opentelemetry.context.propagation.ContextPropagators;
|
||||
import io.opentelemetry.extension.incubator.propagation.ExtendedContextPropagators;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public final class ExtendedSpanBuilder implements SpanBuilder {
|
||||
private final SpanBuilder delegate;
|
||||
|
||||
ExtendedSpanBuilder(SpanBuilder delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtendedSpanBuilder setParent(Context context) {
|
||||
delegate.setParent(context);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtendedSpanBuilder setNoParent() {
|
||||
delegate.setNoParent();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtendedSpanBuilder addLink(SpanContext spanContext) {
|
||||
delegate.addLink(spanContext);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtendedSpanBuilder addLink(SpanContext spanContext, Attributes attributes) {
|
||||
delegate.addLink(spanContext, attributes);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtendedSpanBuilder setAttribute(String key, String value) {
|
||||
delegate.setAttribute(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtendedSpanBuilder setAttribute(String key, long value) {
|
||||
delegate.setAttribute(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtendedSpanBuilder setAttribute(String key, double value) {
|
||||
delegate.setAttribute(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtendedSpanBuilder setAttribute(String key, boolean value) {
|
||||
delegate.setAttribute(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ExtendedSpanBuilder setAttribute(AttributeKey<T> key, T value) {
|
||||
delegate.setAttribute(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtendedSpanBuilder setAllAttributes(Attributes attributes) {
|
||||
delegate.setAllAttributes(attributes);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtendedSpanBuilder setSpanKind(SpanKind spanKind) {
|
||||
delegate.setSpanKind(spanKind);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtendedSpanBuilder setStartTimestamp(long startTimestamp, TimeUnit unit) {
|
||||
delegate.setStartTimestamp(startTimestamp, unit);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtendedSpanBuilder setStartTimestamp(Instant startTimestamp) {
|
||||
delegate.setStartTimestamp(startTimestamp);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a span context from the given carrier and set it as parent of the span for {@link
|
||||
* #startAndCall(SpanCallable)} and {@link #startAndRun(SpanRunnable)}.
|
||||
*
|
||||
* <p>The span context will be extracted from the <code>carrier</code>, which you usually get from
|
||||
* HTTP headers of the metadata of a message you're processing.
|
||||
*
|
||||
* <p>A typical usage would be: <code>
|
||||
* ExtendedTracer.create(tracer)
|
||||
* .setSpanKind(SpanKind.SERVER)
|
||||
* .setParentFrom(propagators, carrier)
|
||||
* .run("my-span", () -> { ... });
|
||||
* </code>
|
||||
*
|
||||
* @param propagators provide the propagators from {@link OpenTelemetry#getPropagators()}
|
||||
* @param carrier the string map where to extract the span context from
|
||||
*/
|
||||
public ExtendedSpanBuilder setParentFrom(
|
||||
ContextPropagators propagators, Map<String, String> carrier) {
|
||||
setParent(ExtendedContextPropagators.extractTextMapPropagationContext(carrier, propagators));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Span startSpan() {
|
||||
return delegate.startSpan();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given {@link SpanCallable} inside of the span created by the given {@link
|
||||
* SpanBuilder}. The span will be ended at the end of the {@link SpanCallable}.
|
||||
*
|
||||
* <p>If an exception is thrown by the {@link SpanCallable}, the span will be marked as error, and
|
||||
* the exception will be recorded.
|
||||
*
|
||||
* @param spanCallable the {@link SpanCallable} to call
|
||||
* @param <T> the type of the result
|
||||
* @param <E> the type of the exception
|
||||
* @return the result of the {@link SpanCallable}
|
||||
*/
|
||||
public <T, E extends Throwable> T startAndCall(SpanCallable<T, E> spanCallable) throws E {
|
||||
return startAndCall(spanCallable, ExtendedSpanBuilder::setSpanError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given {@link SpanCallable} inside of the span created by the given {@link
|
||||
* SpanBuilder}. The span will be ended at the end of the {@link SpanCallable}.
|
||||
*
|
||||
* <p>If an exception is thrown by the {@link SpanCallable}, the <code>handleException</code>
|
||||
* consumer will be called, giving you the opportunity to handle the exception and span in a
|
||||
* custom way, e.g. not marking the span as error.
|
||||
*
|
||||
* @param spanCallable the {@link SpanCallable} to call
|
||||
* @param handleException the consumer to call when an exception is thrown
|
||||
* @param <T> the type of the result
|
||||
* @param <E> the type of the exception
|
||||
* @return the result of the {@link SpanCallable}
|
||||
*/
|
||||
public <T, E extends Throwable> T startAndCall(
|
||||
SpanCallable<T, E> spanCallable, BiConsumer<Span, Throwable> handleException) throws E {
|
||||
Span span = startSpan();
|
||||
|
||||
//noinspection unused
|
||||
try (Scope unused = span.makeCurrent()) {
|
||||
return spanCallable.callInSpan();
|
||||
} catch (Throwable e) {
|
||||
handleException.accept(span, e);
|
||||
throw e;
|
||||
} finally {
|
||||
span.end();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given {@link SpanRunnable} inside of the span created by the given {@link
|
||||
* SpanBuilder}. The span will be ended at the end of the {@link SpanRunnable}.
|
||||
*
|
||||
* <p>If an exception is thrown by the {@link SpanRunnable}, the span will be marked as error, and
|
||||
* the exception will be recorded.
|
||||
*
|
||||
* @param runnable the {@link SpanRunnable} to run
|
||||
* @param <E> the type of the exception
|
||||
*/
|
||||
@SuppressWarnings("NullAway")
|
||||
public <E extends Throwable> void startAndRun(SpanRunnable<E> runnable) throws E {
|
||||
startAndRun(runnable, ExtendedSpanBuilder::setSpanError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given {@link SpanRunnable} inside of the span created by the given {@link
|
||||
* SpanBuilder}. The span will be ended at the end of the {@link SpanRunnable}.
|
||||
*
|
||||
* <p>If an exception is thrown by the {@link SpanRunnable}, the <code>handleException</code>
|
||||
* consumer will be called, giving you the opportunity to handle the exception and span in a
|
||||
* custom way, e.g. not marking the span as error.
|
||||
*
|
||||
* @param runnable the {@link SpanRunnable} to run
|
||||
* @param <E> the type of the exception
|
||||
*/
|
||||
@SuppressWarnings("NullAway")
|
||||
public <E extends Throwable> void startAndRun(
|
||||
SpanRunnable<E> runnable, BiConsumer<Span, Throwable> handleException) throws E {
|
||||
startAndCall(
|
||||
() -> {
|
||||
runnable.runInSpan();
|
||||
return null;
|
||||
},
|
||||
handleException);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a span as error. This is the default exception handler.
|
||||
*
|
||||
* @param span the span
|
||||
* @param exception the exception that caused the error
|
||||
*/
|
||||
private static void setSpanError(Span span, Throwable exception) {
|
||||
span.setStatus(StatusCode.ERROR);
|
||||
span.recordException(exception);
|
||||
}
|
||||
}
|
|
@ -5,54 +5,40 @@
|
|||
|
||||
package io.opentelemetry.extension.incubator.trace;
|
||||
|
||||
import io.opentelemetry.api.trace.Span;
|
||||
import io.opentelemetry.api.trace.SpanBuilder;
|
||||
import io.opentelemetry.api.trace.Tracer;
|
||||
import io.opentelemetry.context.Scope;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/** Provides easy mechanisms for wrapping standard Java constructs with an OpenTelemetry Span. */
|
||||
/**
|
||||
* Utility class to simplify tracing.
|
||||
*
|
||||
* <p>The <a
|
||||
* href="https://github.com/opentelemetry/opentelemetry-java/blob/main/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator">README</a>
|
||||
* explains the use cases in more detail.
|
||||
*/
|
||||
public final class ExtendedTracer implements Tracer {
|
||||
|
||||
private final Tracer delegate;
|
||||
|
||||
/** Create a new {@link ExtendedTracer} that wraps the provided Tracer. */
|
||||
public static ExtendedTracer create(Tracer delegate) {
|
||||
return new ExtendedTracer(delegate);
|
||||
}
|
||||
|
||||
private ExtendedTracer(Tracer delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
/** Run the provided {@link Runnable} and wrap with a {@link Span} with the provided name. */
|
||||
public void run(String spanName, Runnable runnable) {
|
||||
Span span = delegate.spanBuilder(spanName).startSpan();
|
||||
try (Scope scope = span.makeCurrent()) {
|
||||
runnable.run();
|
||||
} catch (Throwable e) {
|
||||
span.recordException(e);
|
||||
throw e;
|
||||
} finally {
|
||||
span.end();
|
||||
}
|
||||
}
|
||||
|
||||
/** Call the provided {@link Callable} and wrap with a {@link Span} with the provided name. */
|
||||
public <T> T call(String spanName, Callable<T> callable) throws Exception {
|
||||
Span span = delegate.spanBuilder(spanName).startSpan();
|
||||
try (Scope scope = span.makeCurrent()) {
|
||||
return callable.call();
|
||||
} catch (Throwable e) {
|
||||
span.recordException(e);
|
||||
throw e;
|
||||
} finally {
|
||||
span.end();
|
||||
}
|
||||
/**
|
||||
* Creates a new instance of {@link ExtendedTracer}.
|
||||
*
|
||||
* @param delegate the {@link Tracer} to use
|
||||
*/
|
||||
public static ExtendedTracer create(Tracer delegate) {
|
||||
return new ExtendedTracer(delegate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ExtendedSpanBuilder} with the given span name.
|
||||
*
|
||||
* @param spanName the name of the span
|
||||
* @return the {@link ExtendedSpanBuilder}
|
||||
*/
|
||||
@Override
|
||||
public SpanBuilder spanBuilder(String spanName) {
|
||||
return delegate.spanBuilder(spanName);
|
||||
public ExtendedSpanBuilder spanBuilder(String spanName) {
|
||||
return new ExtendedSpanBuilder(delegate.spanBuilder(spanName));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.extension.incubator.trace;
|
||||
|
||||
/**
|
||||
* An interface for creating a lambda that is wrapped in a span, returns a value, and that may
|
||||
* throw.
|
||||
*
|
||||
* @param <E> Thrown exception type.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface SpanCallable<T, E extends Throwable> {
|
||||
T callInSpan() throws E;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.extension.incubator.trace;
|
||||
|
||||
/**
|
||||
* An interface for creating a lambda that is wrapped in a span and that may throw.
|
||||
*
|
||||
* @param <E> Thrown exception type.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface SpanRunnable<E extends Throwable> {
|
||||
void runInSpan() throws E;
|
||||
}
|
|
@ -5,113 +5,209 @@
|
|||
|
||||
package io.opentelemetry.extension.incubator.trace;
|
||||
|
||||
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
|
||||
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.assertj.core.api.Assertions.assertThatException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.junit.jupiter.api.Named.named;
|
||||
|
||||
import io.opentelemetry.api.common.AttributeKey;
|
||||
import com.google.errorprone.annotations.Keep;
|
||||
import io.opentelemetry.api.common.Attributes;
|
||||
import io.opentelemetry.api.trace.Span;
|
||||
import io.opentelemetry.api.trace.Tracer;
|
||||
import io.opentelemetry.api.trace.SpanKind;
|
||||
import io.opentelemetry.context.Context;
|
||||
import io.opentelemetry.context.Scope;
|
||||
import io.opentelemetry.context.propagation.ContextPropagators;
|
||||
import io.opentelemetry.extension.incubator.propagation.ExtendedContextPropagators;
|
||||
import io.opentelemetry.sdk.testing.assertj.SpanDataAssert;
|
||||
import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension;
|
||||
import io.opentelemetry.sdk.trace.data.StatusData;
|
||||
import io.opentelemetry.semconv.SemanticAttributes;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
class ExtendedTracerTest {
|
||||
|
||||
interface ThrowingBiConsumer<T, U> {
|
||||
void accept(T t, U u) throws Throwable;
|
||||
}
|
||||
|
||||
@RegisterExtension
|
||||
static final OpenTelemetryExtension otelTesting = OpenTelemetryExtension.create();
|
||||
|
||||
private final Tracer tracer = otelTesting.getOpenTelemetry().getTracer("test");
|
||||
private final ExtendedTracer extendedTracer =
|
||||
ExtendedTracer.create(otelTesting.getOpenTelemetry().getTracer("test"));
|
||||
|
||||
@Test
|
||||
void runRunnable() {
|
||||
ExtendedTracer.create(tracer).run("testSpan", () -> Span.current().setAttribute("one", 1));
|
||||
|
||||
otelTesting
|
||||
.assertTraces()
|
||||
.hasTracesSatisfyingExactly(
|
||||
traceAssert ->
|
||||
traceAssert.hasSpansSatisfyingExactly(
|
||||
spanDataAssert ->
|
||||
spanDataAssert
|
||||
.hasName("testSpan")
|
||||
.hasAttributes(Attributes.of(AttributeKey.longKey("one"), 1L))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void runRunnable_throws() {
|
||||
assertThatThrownBy(
|
||||
void wrapInSpan() {
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(
|
||||
() ->
|
||||
ExtendedTracer.create(tracer)
|
||||
.run(
|
||||
"throwingRunnable",
|
||||
extendedTracer
|
||||
.spanBuilder("test")
|
||||
.startAndRun(
|
||||
() -> {
|
||||
Span.current().setAttribute("one", 1);
|
||||
throw new RuntimeException("failed");
|
||||
}))
|
||||
.isInstanceOf(RuntimeException.class);
|
||||
// runs in span
|
||||
throw new IllegalStateException("ex");
|
||||
}));
|
||||
|
||||
String result =
|
||||
extendedTracer
|
||||
.spanBuilder("another test")
|
||||
.startAndCall(
|
||||
() -> {
|
||||
// runs in span
|
||||
return "result";
|
||||
});
|
||||
assertThat(result).isEqualTo("result");
|
||||
|
||||
otelTesting
|
||||
.assertTraces()
|
||||
.hasTracesSatisfyingExactly(
|
||||
traceAssert ->
|
||||
traceAssert.hasSpansSatisfyingExactly(
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span ->
|
||||
span.hasName("throwingRunnable")
|
||||
.hasAttributes(Attributes.of(AttributeKey.longKey("one"), 1L))
|
||||
.hasEventsSatisfying(
|
||||
(events) ->
|
||||
assertThat(events)
|
||||
.singleElement()
|
||||
.satisfies(
|
||||
eventData ->
|
||||
assertThat(eventData.getName())
|
||||
.isEqualTo("exception")))));
|
||||
span.hasName("test")
|
||||
.hasStatus(StatusData.error())
|
||||
.hasEventsSatisfyingExactly(
|
||||
event ->
|
||||
event
|
||||
.hasName("exception")
|
||||
.hasAttributesSatisfyingExactly(
|
||||
equalTo(
|
||||
SemanticAttributes.EXCEPTION_TYPE,
|
||||
"java.lang.IllegalStateException"),
|
||||
satisfies(
|
||||
SemanticAttributes.EXCEPTION_STACKTRACE,
|
||||
string ->
|
||||
string.contains(
|
||||
"java.lang.IllegalStateException: ex")),
|
||||
equalTo(SemanticAttributes.EXCEPTION_MESSAGE, "ex")))),
|
||||
trace -> trace.hasSpansSatisfyingExactly(a -> a.hasName("another test")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void callCallable() throws Exception {
|
||||
assertThat(
|
||||
ExtendedTracer.create(tracer)
|
||||
.call(
|
||||
"spanCallable",
|
||||
() -> {
|
||||
Span.current().setAttribute("one", 1);
|
||||
return "hello";
|
||||
}))
|
||||
.isEqualTo("hello");
|
||||
void propagation() {
|
||||
extendedTracer
|
||||
.spanBuilder("parent")
|
||||
.startAndRun(
|
||||
() -> {
|
||||
ContextPropagators propagators = otelTesting.getOpenTelemetry().getPropagators();
|
||||
Map<String, String> propagationHeaders =
|
||||
ExtendedContextPropagators.getTextMapPropagationContext(propagators);
|
||||
assertThat(propagationHeaders).hasSize(1).containsKey("traceparent");
|
||||
|
||||
// make sure the parent span is not stored in a thread local anymore
|
||||
Span invalid = Span.getInvalid();
|
||||
//noinspection unused
|
||||
try (Scope unused = invalid.makeCurrent()) {
|
||||
extendedTracer
|
||||
.spanBuilder("child")
|
||||
.setSpanKind(SpanKind.SERVER)
|
||||
.setParent(Context.current())
|
||||
.setNoParent()
|
||||
.setParentFrom(propagators, propagationHeaders)
|
||||
.setAttribute(
|
||||
"key",
|
||||
"value") // any method can be called here on the span (and we increase the
|
||||
// test coverage)
|
||||
.setAttribute("key2", 0)
|
||||
.setAttribute("key3", 0.0)
|
||||
.setAttribute("key4", false)
|
||||
.setAttribute(SemanticAttributes.CLIENT_PORT, 1234L)
|
||||
.addLink(invalid.getSpanContext())
|
||||
.addLink(invalid.getSpanContext(), Attributes.empty())
|
||||
.setAllAttributes(Attributes.empty())
|
||||
.setStartTimestamp(0, java.util.concurrent.TimeUnit.NANOSECONDS)
|
||||
.setStartTimestamp(Instant.MIN)
|
||||
.startAndRun(() -> {});
|
||||
}
|
||||
});
|
||||
|
||||
otelTesting
|
||||
.assertTraces()
|
||||
.hasTracesSatisfyingExactly(
|
||||
traceAssert ->
|
||||
traceAssert.hasSpansSatisfyingExactly(
|
||||
spanDataAssert ->
|
||||
spanDataAssert
|
||||
.hasName("spanCallable")
|
||||
.hasAttributes(Attributes.of(AttributeKey.longKey("one"), 1L))));
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
SpanDataAssert::hasNoParent, span -> span.hasParent(trace.getSpan(0))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void callCallable_throws() {
|
||||
assertThatThrownBy(
|
||||
private static class ExtractAndRunParameter {
|
||||
private final ThrowingBiConsumer<ExtendedTracer, SpanCallable<Void, Throwable>> extractAndRun;
|
||||
private final SpanKind wantKind;
|
||||
private final StatusData wantStatus;
|
||||
|
||||
private ExtractAndRunParameter(
|
||||
ThrowingBiConsumer<ExtendedTracer, SpanCallable<Void, Throwable>> extractAndRun,
|
||||
SpanKind wantKind,
|
||||
StatusData wantStatus) {
|
||||
this.extractAndRun = extractAndRun;
|
||||
this.wantKind = wantKind;
|
||||
this.wantStatus = wantStatus;
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
private static Stream<Arguments> extractAndRun() {
|
||||
BiConsumer<Span, Throwable> ignoreException =
|
||||
(span, throwable) -> {
|
||||
// ignore
|
||||
};
|
||||
return Stream.of(
|
||||
Arguments.of(
|
||||
named(
|
||||
"server",
|
||||
new ExtractAndRunParameter(
|
||||
(t, c) ->
|
||||
t.spanBuilder("span")
|
||||
.setSpanKind(SpanKind.SERVER)
|
||||
.setParentFrom(
|
||||
otelTesting.getOpenTelemetry().getPropagators(),
|
||||
Collections.emptyMap())
|
||||
.startAndCall(c),
|
||||
SpanKind.SERVER,
|
||||
StatusData.error()))),
|
||||
Arguments.of(
|
||||
named(
|
||||
"server - ignore exception",
|
||||
new ExtractAndRunParameter(
|
||||
(t, c) ->
|
||||
t.spanBuilder("span")
|
||||
.setSpanKind(SpanKind.SERVER)
|
||||
.setParentFrom(
|
||||
otelTesting.getOpenTelemetry().getPropagators(),
|
||||
Collections.emptyMap())
|
||||
.startAndCall(c, ignoreException),
|
||||
SpanKind.SERVER,
|
||||
StatusData.unset()))));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void extractAndRun(ExtractAndRunParameter parameter) {
|
||||
assertThatException()
|
||||
.isThrownBy(
|
||||
() ->
|
||||
ExtendedTracer.create(tracer)
|
||||
.call(
|
||||
"throwingCallable",
|
||||
() -> {
|
||||
Span.current().setAttribute("one", 1);
|
||||
throw new RuntimeException("failed");
|
||||
}))
|
||||
.isInstanceOf(RuntimeException.class);
|
||||
parameter.extractAndRun.accept(
|
||||
extendedTracer,
|
||||
() -> {
|
||||
throw new RuntimeException("ex");
|
||||
}));
|
||||
|
||||
otelTesting
|
||||
.assertTraces()
|
||||
.hasTracesSatisfyingExactly(
|
||||
traceAssert ->
|
||||
traceAssert.hasSpansSatisfyingExactly(
|
||||
spanDataAssert ->
|
||||
spanDataAssert
|
||||
.hasName("throwingCallable")
|
||||
.hasAttributes(Attributes.of(AttributeKey.longKey("one"), 1L))));
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span -> span.hasKind(parameter.wantKind).hasStatus(parameter.wantStatus)));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue