-
Notifications
You must be signed in to change notification settings - Fork 292
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Guava asynchronous result type support for OpenTelemetry annotati…
…ons (#5799)
- Loading branch information
1 parent
b2c7d02
commit 360dce2
Showing
5 changed files
with
251 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
52 changes: 52 additions & 0 deletions
52
...src/main/java/datadog/trace/instrumentation/guava10/GuavaAsyncResultSupportExtension.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,52 @@ | ||
package datadog.trace.instrumentation.guava10; | ||
|
||
import com.google.common.util.concurrent.ListenableFuture; | ||
import datadog.trace.bootstrap.instrumentation.api.AgentSpan; | ||
import datadog.trace.bootstrap.instrumentation.decorator.AsyncResultDecorator; | ||
import java.util.concurrent.CancellationException; | ||
import java.util.concurrent.ExecutionException; | ||
|
||
public class GuavaAsyncResultSupportExtension | ||
implements AsyncResultDecorator.AsyncResultSupportExtension { | ||
static { | ||
AsyncResultDecorator.registerExtension(new GuavaAsyncResultSupportExtension()); | ||
} | ||
|
||
/** | ||
* Register the extension as an {@link AsyncResultDecorator.AsyncResultSupportExtension} using | ||
* static class initialization.<br> | ||
* It uses an empty static method call to ensure the class loading and the one-time-only static | ||
* class initialization. This will ensure this extension will only be registered once to the | ||
* {@link AsyncResultDecorator}. | ||
*/ | ||
public static void initialize() {} | ||
|
||
@Override | ||
public boolean supports(Class<?> result) { | ||
return ListenableFuture.class.isAssignableFrom(result); | ||
} | ||
|
||
@Override | ||
public Object apply(Object result, AgentSpan span) { | ||
if (result instanceof ListenableFuture) { | ||
ListenableFuture<?> listenableFuture = (ListenableFuture<?>) result; | ||
if (!listenableFuture.isDone() && !listenableFuture.isCancelled()) { | ||
listenableFuture.addListener( | ||
() -> { | ||
// Get value to check for execution exception | ||
try { | ||
listenableFuture.get(); | ||
} catch (ExecutionException e) { | ||
span.addThrowable(e.getCause()); | ||
} catch (CancellationException | InterruptedException e) { | ||
// Ignored | ||
} | ||
span.finish(); | ||
}, | ||
Runnable::run); | ||
return result; | ||
} | ||
} | ||
return null; | ||
} | ||
} |
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
113 changes: 113 additions & 0 deletions
113
...gent/instrumentation/guava-10/src/test/groovy/GuavaAsyncResultSupportExtensionTest.groovy
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,113 @@ | ||
import annotatedsample.GuavaTracedMethods | ||
import datadog.trace.agent.test.AgentTestRunner | ||
import datadog.trace.bootstrap.instrumentation.api.Tags | ||
import spock.lang.Shared | ||
|
||
import java.util.concurrent.CountDownLatch | ||
import java.util.concurrent.ExecutionException | ||
import java.util.concurrent.ExecutorService | ||
import java.util.concurrent.Executors | ||
|
||
class GuavaAsyncResultSupportExtensionTest extends AgentTestRunner { | ||
@Override | ||
void configurePreAgent() { | ||
super.configurePreAgent() | ||
|
||
injectSysConfig("dd.integration.opentelemetry-annotations-1.20.enabled", "true") | ||
} | ||
|
||
@Shared | ||
ExecutorService executor | ||
|
||
def setupSpec() { | ||
this.executor = Executors.newSingleThreadExecutor() | ||
} | ||
|
||
def cleanupSpec() { | ||
this.executor.shutdownNow() | ||
} | ||
|
||
def "test WithSpan annotated async method (ListenableFuture)"() { | ||
setup: | ||
def latch = new CountDownLatch(1) | ||
def listenableFuture = GuavaTracedMethods.traceAsyncListenableFuture(executor, latch) | ||
expect: | ||
TEST_WRITER.size() == 0 | ||
when: | ||
latch.countDown() | ||
listenableFuture.get() | ||
then: | ||
assertTraces(1) { | ||
trace(1) { | ||
span { | ||
resourceName "GuavaTracedMethods.traceAsyncListenableFuture" | ||
operationName "GuavaTracedMethods.traceAsyncListenableFuture" | ||
tags { | ||
defaultTags() | ||
"$Tags.COMPONENT" "opentelemetry" | ||
} | ||
} | ||
} | ||
} | ||
} | ||
def "test WithSpan annotated async method (cancelled ListenableFuture)"() { | ||
setup: | ||
def latch = new CountDownLatch(1) | ||
def listenableFuture = GuavaTracedMethods.traceAsyncCancelledListenableFuture(latch) | ||
expect: | ||
TEST_WRITER.size() == 0 | ||
when: | ||
listenableFuture.cancel(true) | ||
then: | ||
assertTraces(1) { | ||
trace(1) { | ||
span { | ||
resourceName "GuavaTracedMethods.traceAsyncCancelledListenableFuture" | ||
operationName "GuavaTracedMethods.traceAsyncCancelledListenableFuture" | ||
tags { | ||
defaultTags() | ||
"$Tags.COMPONENT" "opentelemetry" | ||
} | ||
} | ||
} | ||
} | ||
} | ||
def "test WithSpan annotated async method (failing ListenableFuture)"() { | ||
setup: | ||
def latch = new CountDownLatch(1) | ||
def expectedException = new IllegalStateException("Test exception") | ||
def listenableFuture = GuavaTracedMethods.traceAsyncFailingListenableFuture(executor, latch, expectedException) | ||
expect: | ||
TEST_WRITER.size() == 0 | ||
when: | ||
latch.countDown() | ||
listenableFuture.get() | ||
then: | ||
thrown(ExecutionException) | ||
assertTraces(1) { | ||
trace(1) { | ||
span { | ||
resourceName "GuavaTracedMethods.traceAsyncFailingListenableFuture" | ||
operationName "GuavaTracedMethods.traceAsyncFailingListenableFuture" | ||
errored true | ||
tags { | ||
defaultTags() | ||
"$Tags.COMPONENT" "opentelemetry" | ||
errorTags(expectedException) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
67 changes: 67 additions & 0 deletions
67
dd-java-agent/instrumentation/guava-10/src/test/java/annotatedsample/GuavaTracedMethods.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,67 @@ | ||
package annotatedsample; | ||
|
||
import static java.util.concurrent.TimeUnit.SECONDS; | ||
|
||
import com.google.common.util.concurrent.AbstractFuture; | ||
import com.google.common.util.concurrent.ListenableFuture; | ||
import io.opentelemetry.instrumentation.annotations.WithSpan; | ||
import java.util.concurrent.CountDownLatch; | ||
import java.util.concurrent.ExecutorService; | ||
|
||
public class GuavaTracedMethods { | ||
@WithSpan | ||
public static ListenableFuture<String> traceAsyncListenableFuture( | ||
ExecutorService executor, CountDownLatch latch) { | ||
TestFuture listenableFuture = TestFuture.ofComplete(latch, "hello"); | ||
executor.submit(listenableFuture::start); | ||
return listenableFuture; | ||
} | ||
|
||
@WithSpan | ||
public static ListenableFuture<?> traceAsyncCancelledListenableFuture(CountDownLatch latch) { | ||
return TestFuture.ofComplete(latch, "hello"); | ||
} | ||
|
||
@WithSpan | ||
public static ListenableFuture<?> traceAsyncFailingListenableFuture( | ||
ExecutorService executor, CountDownLatch latch, Throwable exception) { | ||
TestFuture listenableFuture = TestFuture.ofFailing(latch, exception); | ||
executor.submit(listenableFuture::start); | ||
return listenableFuture; | ||
} | ||
|
||
private static class TestFuture extends AbstractFuture<String> { | ||
private final CountDownLatch latch; | ||
private final String value; | ||
private final Throwable exception; | ||
|
||
private TestFuture(CountDownLatch latch, String value, Throwable exception) { | ||
this.latch = latch; | ||
this.value = value; | ||
this.exception = exception; | ||
} | ||
|
||
private void start() { | ||
try { | ||
if (!this.latch.await(5, SECONDS)) { | ||
throw new IllegalStateException("Latch still locked"); | ||
} | ||
} catch (InterruptedException e) { | ||
throw new RuntimeException(e); | ||
} | ||
if (this.exception != null) { | ||
setException(this.exception); | ||
} else { | ||
set(this.value); | ||
} | ||
} | ||
|
||
private static TestFuture ofComplete(CountDownLatch latch, String value) { | ||
return new TestFuture(latch, value, null); | ||
} | ||
|
||
private static TestFuture ofFailing(CountDownLatch latch, Throwable exception) { | ||
return new TestFuture(latch, null, exception); | ||
} | ||
} | ||
} |