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:
Gregor Zeitlinger 2023-12-07 23:27:57 +01:00 committed by GitHub
parent c8e1e3619e
commit 2b33f9fb07
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 734 additions and 109 deletions

View File

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

View File

@ -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"))
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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