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

End span with attribute on cancellation of Guava future #3175

Merged
merged 1 commit into from
Jun 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ muzzle {
}
}

tasks.withType(Test).configureEach {
// TODO run tests both with and without experimental span attributes
jvmArgs "-Dotel.instrumentation.guava.experimental-span-attributes=true"
}

dependencies {
library "com.google.guava:guava:10.0"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,24 @@

package io.opentelemetry.javaagent.instrumentation.guava;

import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.instrumentation.api.tracer.async.AsyncSpanEndStrategies;
import io.opentelemetry.instrumentation.guava.GuavaAsyncSpanEndStrategy;

public final class InstrumentationHelper {
static {
AsyncSpanEndStrategies.getInstance().registerStrategy(GuavaAsyncSpanEndStrategy.INSTANCE);
registerAsyncSpanEndStrategy();
}

private static void registerAsyncSpanEndStrategy() {
AsyncSpanEndStrategies.getInstance()
.registerStrategy(
GuavaAsyncSpanEndStrategy.newBuilder()
.setCaptureExperimentalSpanAttributes(
Config.get()
.getBooleanProperty(
"otel.instrumentation.guava.experimental-span-attributes", false))
.build());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,29 @@ class GuavaWithSpanInstrumentationTest extends AgentInstrumentationSpecification
}
}
}

def "should capture span for canceled ListenableFuture"() {
setup:
def future = SettableFuture.<String>create()
new TracedWithSpan().listenableFuture(future)

expect:
Thread.sleep(500) // sleep a bit just to make sure no span is captured
assertTraces(0) {}

future.cancel(true)

assertTraces(1) {
trace(0, 1) {
span(0) {
name "TracedWithSpan.listenableFuture"
kind SpanKind.INTERNAL
hasNoParent()
attributes {
"guava.canceled" true
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,29 @@

import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.Uninterruptibles;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.tracer.BaseTracer;
import io.opentelemetry.instrumentation.api.tracer.async.AsyncSpanEndStrategy;

public enum GuavaAsyncSpanEndStrategy implements AsyncSpanEndStrategy {
INSTANCE;
public final class GuavaAsyncSpanEndStrategy implements AsyncSpanEndStrategy {
private static final AttributeKey<Boolean> CANCELED_ATTRIBUTE_KEY =
AttributeKey.booleanKey("guava.canceled");

public static GuavaAsyncSpanEndStrategy create() {
return newBuilder().build();
}

public static GuavaAsyncSpanEndStrategyBuilder newBuilder() {
return new GuavaAsyncSpanEndStrategyBuilder();
}

private final boolean captureExperimentalSpanAttributes;

GuavaAsyncSpanEndStrategy(boolean captureExperimentalSpanAttributes) {
this.captureExperimentalSpanAttributes = captureExperimentalSpanAttributes;
}

@Override
public boolean supports(Class<?> returnType) {
Expand All @@ -30,12 +47,19 @@ public Object end(BaseTracer tracer, Context context, Object returnValue) {
return future;
}

private static void endSpan(BaseTracer tracer, Context context, ListenableFuture<?> future) {
try {
Uninterruptibles.getUninterruptibly(future);
private void endSpan(BaseTracer tracer, Context context, ListenableFuture<?> future) {
if (future.isCancelled()) {
if (captureExperimentalSpanAttributes) {
Span.fromContext(context).setAttribute(CANCELED_ATTRIBUTE_KEY, true);
}
tracer.end(context);
} catch (Throwable exception) {
tracer.endExceptionally(context, exception);
} else {
try {
Uninterruptibles.getUninterruptibly(future);
tracer.end(context);
} catch (Throwable exception) {
tracer.endExceptionally(context, exception);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.guava;

public final class GuavaAsyncSpanEndStrategyBuilder {
private boolean captureExperimentalSpanAttributes = false;

GuavaAsyncSpanEndStrategyBuilder() {}

public GuavaAsyncSpanEndStrategyBuilder setCaptureExperimentalSpanAttributes(
boolean captureExperimentalSpanAttributes) {
this.captureExperimentalSpanAttributes = captureExperimentalSpanAttributes;
return this;
}

public GuavaAsyncSpanEndStrategy build() {
return new GuavaAsyncSpanEndStrategy(captureExperimentalSpanAttributes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.SettableFuture
import io.opentelemetry.api.trace.Span
import io.opentelemetry.context.Context
import io.opentelemetry.instrumentation.api.tracer.BaseTracer
import io.opentelemetry.instrumentation.guava.GuavaAsyncSpanEndStrategy
Expand All @@ -16,11 +17,19 @@ class GuavaAsyncSpanEndStrategyTest extends Specification {

Context context

def underTest = GuavaAsyncSpanEndStrategy.INSTANCE
Span span

def underTest = GuavaAsyncSpanEndStrategy.create()

def underTestWithExperimentalAttributes = GuavaAsyncSpanEndStrategy.newBuilder()
.setCaptureExperimentalSpanAttributes(true)
.build()

void setup() {
tracer = Mock()
context = Mock()
span = Mock()
span.storeInContext(_) >> { callRealMethod() }
}

def "ListenableFuture is supported"() {
Expand Down Expand Up @@ -86,4 +95,42 @@ class GuavaAsyncSpanEndStrategyTest extends Specification {
then:
1 * tracer.endExceptionally(context, { it.getCause() == exception })
}

def "ends span on eventually canceled future"() {
given:
def future = SettableFuture.<String>create()
def context = span.storeInContext(Context.root())

when:
underTest.end(tracer, context, future)

then:
0 * tracer._

when:
future.cancel(true)

then:
1 * tracer.end(context)
0 * span.setAttribute(_)
}

def "ends span on eventually canceled future and capturing experimental span attributes"() {
given:
def future = SettableFuture.<String>create()
def context = span.storeInContext(Context.root())

when:
underTestWithExperimentalAttributes.end(tracer, context, future)

then:
0 * tracer._

when:
future.cancel(true)

then:
1 * tracer.end(context)
1 * span.setAttribute({ it.getKey() == "guava.canceled" }, true)
}
}