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 asynchronous tracing for Java 8 CompletableFuture in WithSpanAdvice #2530

Merged
merged 23 commits into from
Mar 22, 2021
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
fd02a85
Add asynchronous tracing for Java 8 CompletableFuture in Spring WithS…
HaloFour Mar 8, 2021
ee201f3
Add unit tests, fix bugs
HaloFour Mar 8, 2021
6b8f4f1
Placate spotless
HaloFour Mar 8, 2021
7ac98d5
Move MethodSpanStrategies to instrumentation-api, enable registration…
HaloFour Mar 8, 2021
b63a896
Merge branch 'main' of github.com:open-telemetry/opentelemetry-java-i…
HaloFour Mar 8, 2021
44a019a
Switch to withSpan
HaloFour Mar 8, 2021
0f5a370
Fix unit test, refactor MethodSpanStrategy interface
HaloFour Mar 9, 2021
ef099f5
Refactor to instrumentation-api, add docs
HaloFour Mar 9, 2021
b465fbd
spotless, handle null and already done scenarios
HaloFour Mar 9, 2021
7c7d7f1
Minor refactorings, add unit tests
HaloFour Mar 9, 2021
cbe3ec6
Placate checkstyle+spotless
HaloFour Mar 9, 2021
1978160
placate codeNarc
HaloFour Mar 9, 2021
a5b5104
Merge branch 'main' of github.com:open-telemetry/opentelemetry-java-i…
HaloFour Mar 10, 2021
850f3a8
Isolate changes to only otelannotations instrumentation
HaloFour Mar 10, 2021
3c8a635
nix unit tests
HaloFour Mar 10, 2021
b1981de
Consolidate JDK8 strategies, remove use of context
HaloFour Mar 10, 2021
272a0f7
Clarify verbiage in Javadoc
HaloFour Mar 12, 2021
4a717d2
Merge branch 'main' of github.com:open-telemetry/opentelemetry-java-i…
HaloFour Mar 12, 2021
9f74fea
Refactor synchronous completion and add comments, tests
HaloFour Mar 12, 2021
5ad2ea8
Merge branch 'main' of github.com:open-telemetry/opentelemetry-java-i…
HaloFour Mar 16, 2021
16025a4
Early return on uncompleted future
HaloFour Mar 16, 2021
72594ae
Add check to Jdk8MethodStrategy to ensure return type of method is co…
HaloFour Mar 17, 2021
da48a91
A couple of suggestions (#1)
trask Mar 21, 2021
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 @@ -13,6 +13,7 @@
import io.opentelemetry.context.Scope;
import java.lang.reflect.Method;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.implementation.bytecode.assign.Assigner;

/**
* Instrumentation for methods annotated with {@link WithSpan} annotation.
Expand Down Expand Up @@ -42,6 +43,7 @@ public static void onEnter(
public static void stopSpan(
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope,
@Advice.Return(typing = Assigner.Typing.DYNAMIC) Object returnValue,
@Advice.Thrown Throwable throwable) {
if (scope == null) {
return;
Expand All @@ -51,7 +53,7 @@ public static void stopSpan(
if (throwable != null) {
tracer().endExceptionally(context, throwable);
} else {
tracer().end(context);
tracer().end(context, returnValue);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.tracer.BaseTracer;
import io.opentelemetry.javaagent.instrumentation.otelannotations.async.MethodSpanStrategies;
import io.opentelemetry.javaagent.instrumentation.otelannotations.async.MethodSpanStrategy;
import java.lang.reflect.Method;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -23,19 +25,33 @@ public static WithSpanTracer tracer() {

private static final Logger log = LoggerFactory.getLogger(WithSpanTracer.class);

private final MethodSpanStrategies methodSpanStrategies = MethodSpanStrategies.getInstance();

public Context startSpan(
Context parentContext, WithSpan applicationAnnotation, Method method, SpanKind kind) {

Context spanStrategyContext = withMethodSpanStrategy(parentContext, method);
HaloFour marked this conversation as resolved.
Show resolved Hide resolved
Span span =
spanBuilder(
parentContext, spanNameForMethodWithAnnotation(applicationAnnotation, method), kind)
.startSpan();
if (kind == SpanKind.SERVER) {
return withServerSpan(parentContext, span);
return withServerSpan(spanStrategyContext, span);
}
if (kind == SpanKind.CLIENT) {
return withClientSpan(parentContext, span);
return withClientSpan(spanStrategyContext, span);
}
return parentContext.with(span);
return spanStrategyContext.with(span);
}

/**
* Resolves the {@link MethodSpanStrategy} for tracing the specified {@code method} and stores
* that strategy in the returned {@code Context}.
*/
protected Context withMethodSpanStrategy(Context context, Method method) {
Class<?> returnType = method.getReturnType();
MethodSpanStrategy methodSpanStrategy = methodSpanStrategies.resolveStrategy(returnType);
return context.with(methodSpanStrategy);
}

/**
Expand Down Expand Up @@ -69,6 +85,20 @@ public static SpanKind toAgentOrNull(
}
}

/**
* Denotes the end of the invocation of the traced method with a successful result which will end
* the span stored in the passed {@code context}. If the method returned a value representing an
* asynchronous operation then the span will remain open until the asynchronous operation has
HaloFour marked this conversation as resolved.
Show resolved Hide resolved
* completed.
*
* @param result Return value from the traced method.
* @return Either {@code result} or a value composing over {@code result} for notification of
* completion.
*/
public Object end(Context context, Object result) {
return MethodSpanStrategy.fromContext(context).end(this, context, result);
}

@Override
protected String getInstrumentationName() {
return "io.opentelemetry.javaagent.opentelemetry-annotations-1.0";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.otelannotations.async;

import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.tracer.BaseTracer;
import java.util.concurrent.CompletableFuture;

enum CompletableFutureMethodSpanStrategy implements MethodSpanStrategy {
HaloFour marked this conversation as resolved.
Show resolved Hide resolved
INSTANCE;

@Override
public Object end(BaseTracer tracer, Context context, Object result) {
if (result instanceof CompletableFuture) {
CompletableFuture<?> future = (CompletableFuture<?>) result;
if (future.isDone()) {
return endSynchronously(tracer, context, future);
} else {
return endOnCompletion(tracer, context, future);
}
}
tracer.end(context);
return result;
}

private CompletableFuture<?> endSynchronously(
BaseTracer tracer, Context context, CompletableFuture<?> future) {
try {
future.join();
tracer.end(context);
} catch (Exception exception) {
tracer.endExceptionally(context, exception);
}
return future;
}

private CompletableFuture<?> endOnCompletion(
BaseTracer tracer, Context context, CompletableFuture<?> future) {
return future.whenComplete(
(value, error) -> {
if (error != null) {
tracer.endExceptionally(context, error);
} else {
tracer.end(context);
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.otelannotations.async;

import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.tracer.BaseTracer;
import java.util.concurrent.CompletionStage;

enum CompletionStageMethodSpanStrategy implements MethodSpanStrategy {
INSTANCE;

@Override
public Object end(BaseTracer tracer, Context context, Object result) {
if (result instanceof CompletionStage) {
CompletionStage<?> stage = (CompletionStage<?>) result;
return stage.whenComplete(
(value, error) -> {
if (error != null) {
tracer.endExceptionally(context, error);
} else {
tracer.end(context);
}
});
}
tracer.end(context);
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.otelannotations.async;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

/**
* Registry of {@link MethodSpanStrategy} implementations for tracing the asynchronous operations
* represented by the return type of a traced method.
*/
public class MethodSpanStrategies {
private static final MethodSpanStrategies instance;

static {
Map<Class<?>, MethodSpanStrategy> strategies = new HashMap<>();
strategies.put(CompletionStage.class, MethodSpanStrategy.forCompletionStage());
strategies.put(CompletableFuture.class, MethodSpanStrategy.forCompletableFuture());
instance = new MethodSpanStrategies(strategies);
}

public static MethodSpanStrategies getInstance() {
return instance;
}

private final Map<Class<?>, MethodSpanStrategy> strategies;

private MethodSpanStrategies(Map<Class<?>, MethodSpanStrategy> strategies) {
this.strategies = strategies;
}

public void registerStrategy(Class<?> returnType, MethodSpanStrategy strategy) {
strategies.put(returnType, strategy);
}

public MethodSpanStrategy resolveStrategy(Class<?> returnType) {
return strategies.getOrDefault(returnType, MethodSpanStrategy.synchronous());
mateuszrzeszutek marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.otelannotations.async;

import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextKey;
import io.opentelemetry.context.ImplicitContextKeyed;
import io.opentelemetry.instrumentation.api.tracer.BaseTracer;

/**
* Represents an implementation of a strategy for composing over the return value of a traced
* method. If the return value represents the result of an asynchronous operation the implementation
* can compose or register for notification of completion at which point the span representing the
* invocation of the method will be ended.
*/
public interface MethodSpanStrategy extends ImplicitContextKeyed {
ContextKey<MethodSpanStrategy> CONTEXT_KEY =
ContextKey.named("opentelemetry-spring-autoconfigure-aspects-method-span-strategy");

/**
* Denotes the end of the invocation of the traced method with a successful result which will end
* the span stored in the passed {@code context}. If the method returned a value representing an
* asynchronous operation then the span will remain open until the asynchronous operation has
* completed.
*
* @param tracer {@link BaseTracer} tracer to be used to end the span stored in the {@code
* context}.
* @param result Return value of the traced method.
* @return Either {@code result} or a value composing over {@code result} for notification of
* completion.
*/
Object end(BaseTracer tracer, Context context, Object result);

@Override
default Context storeInContext(Context context) {
return context.with(CONTEXT_KEY, this);
}

static MethodSpanStrategy fromContext(Context context) {
MethodSpanStrategy methodSpanStrategy = context.get(CONTEXT_KEY);
return methodSpanStrategy != null ? methodSpanStrategy : synchronous();
}

/**
* Returns a {@link MethodSpanStrategy} for tracing synchronous methods where the return value
* does not represent the completion of an asynchronous operation.
*/
static MethodSpanStrategy synchronous() {
return SynchronousMethodSpanStrategy.INSTANCE;
}

/**
* Returns a {@link MethodSpanStrategy} for tracing a method that returns a {@link
* java.util.concurrent.CompletionStage} representing the completion of an asynchronous operation.
*/
static MethodSpanStrategy forCompletionStage() {
return CompletionStageMethodSpanStrategy.INSTANCE;
}

/**
* Returns a {@link MethodSpanStrategy} for tracing a method that returns a {@link
* java.util.concurrent.CompletableFuture} representing the completion of an asynchronous
* operation.
*/
static MethodSpanStrategy forCompletableFuture() {
return CompletableFutureMethodSpanStrategy.INSTANCE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.otelannotations.async;

import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.tracer.BaseTracer;

enum SynchronousMethodSpanStrategy implements MethodSpanStrategy {
INSTANCE;

@Override
public Object end(BaseTracer tracer, Context context, Object result) {
tracer.end(context);
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* Provides implementations of strategies for tracing methods that return asynchronous and reactive
* values so that the span can be ended when the asynchronous operation completes.
*/
package io.opentelemetry.javaagent.instrumentation.otelannotations.async;
Loading