Bridge span keys defined in instrumentation api (#3911)

This commit is contained in:
Lauri Tulmin 2021-08-26 06:25:37 +03:00 committed by GitHub
parent 28db0c30cd
commit b5aec6a2e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 390 additions and 3 deletions

View File

@ -18,7 +18,6 @@ public final class SpanKey {
private static final ContextKey<Span> CONSUMER_KEY =
ContextKey.named("opentelemetry-traces-span-key-consumer");
// TODO bridge these constants in AgentContextStorage
private static final ContextKey<Span> HTTP_KEY =
ContextKey.named("opentelemetry-traces-span-key-http");
private static final ContextKey<Span> RPC_KEY =

View File

@ -28,8 +28,12 @@ dependencies {
// using OpenTelemetry SDK to make sure that instrumentation doesn't cause
// OpenTelemetrySdk.getTracerProvider() to throw ClassCastException
testImplementation("io.opentelemetry:opentelemetry-sdk")
testImplementation(project(":instrumentation-api"))
// @WithSpan annotation is used to generate spans in ContextBridgeTest
testImplementation("io.opentelemetry:opentelemetry-extension-annotations")
testInstrumentation(project(":instrumentation:opentelemetry-annotations-1.0:javaagent"))
testImplementation(project(":instrumentation:opentelemetry-api-1.0:testing"))
testInstrumentation(project(":instrumentation:opentelemetry-api-1.0:testing"))
}

View File

@ -151,8 +151,26 @@ public class AgentContextStorage implements ContextStorage, AutoCloseable {
"io.opentelemetry.api.baggage.BaggageContextKey",
BaggageBridging::toApplication,
BaggageBridging::toAgent),
bridgeSpanKey("SERVER_KEY"),
bridgeSpanKey("CONSUMER_KEY"),
bridgeSpanKey("HTTP_KEY"),
bridgeSpanKey("RPC_KEY"),
bridgeSpanKey("DB_KEY"),
bridgeSpanKey("MESSAGING_KEY"),
bridgeSpanKey("CLIENT_KEY"),
bridgeSpanKey("PRODUCER_KEY"),
};
private static ContextKeyBridge<Span, io.opentelemetry.api.trace.Span> bridgeSpanKey(
String name) {
return new ContextKeyBridge<>(
"application.io.opentelemetry.instrumentation.api.instrumenter.SpanKey",
"io.opentelemetry.instrumentation.api.instrumenter.SpanKey",
name,
Bridging::toApplication,
Bridging::toAgentOrNull);
}
@Override
public Scope attach(Context toAttach) {
io.opentelemetry.context.Context currentAgentContext =
@ -266,13 +284,22 @@ public class AgentContextStorage implements ContextStorage, AutoCloseable {
String agentKeyHolderClassName,
Function<AGENT, APPLICATION> toApplication,
Function<APPLICATION, AGENT> toAgent) {
this(applicationKeyHolderClassName, agentKeyHolderClassName, "KEY", toApplication, toAgent);
}
ContextKeyBridge(
String applicationKeyHolderClassName,
String agentKeyHolderClassName,
String fieldName,
Function<AGENT, APPLICATION> toApplication,
Function<APPLICATION, AGENT> toAgent) {
this.toApplication = toApplication;
this.toAgent = toAgent;
ContextKey<APPLICATION> applicationContextKey;
try {
Class<?> applicationKeyHolderClass = Class.forName(applicationKeyHolderClassName);
Field applicationContextKeyField = applicationKeyHolderClass.getDeclaredField("KEY");
Field applicationContextKeyField = applicationKeyHolderClass.getDeclaredField(fieldName);
applicationContextKeyField.setAccessible(true);
applicationContextKey = (ContextKey<APPLICATION>) applicationContextKeyField.get(null);
} catch (Throwable t) {
@ -283,7 +310,7 @@ public class AgentContextStorage implements ContextStorage, AutoCloseable {
io.opentelemetry.context.ContextKey<AGENT> agentContextKey;
try {
Class<?> agentKeyHolderClass = Class.forName(agentKeyHolderClassName);
Field agentContextKeyField = agentKeyHolderClass.getDeclaredField("KEY");
Field agentContextKeyField = agentKeyHolderClass.getDeclaredField(fieldName);
agentContextKeyField.setAccessible(true);
agentContextKey =
(io.opentelemetry.context.ContextKey<AGENT>) agentContextKeyField.get(null);

View File

@ -9,6 +9,10 @@ import io.opentelemetry.api.trace.Span
import io.opentelemetry.context.Context
import io.opentelemetry.context.ContextKey
import io.opentelemetry.extension.annotations.WithSpan
import io.opentelemetry.instrumentation.api.instrumenter.SpanKey
import io.opentelemetry.instrumentation.api.tracer.ClientSpan
import io.opentelemetry.instrumentation.api.tracer.ConsumerSpan
import io.opentelemetry.instrumentation.api.tracer.ServerSpan
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executors
@ -145,6 +149,64 @@ class ContextBridgeTest extends AgentInstrumentationSpecification {
Context.current() == Context.root()
}
def "test server span bridge"() {
expect:
AgentSpanTesting.runWithServerSpan("server") {
assert Span.current() != null
assert ServerSpan.fromContextOrNull(Context.current()) != null
runWithSpan("internal") {
assert ServerSpan.fromContextOrNull(Context.current()) != null
}
}
}
def "test consumer span bridge"() {
expect:
AgentSpanTesting.runWithConsumerSpan("consumer") {
assert Span.current() != null
assert ConsumerSpan.fromContextOrNull(Context.current()) != null
runWithSpan("internal") {
assert ConsumerSpan.fromContextOrNull(Context.current()) != null
}
}
}
def "test client span bridge"() {
expect:
AgentSpanTesting.runWithClientSpan("client") {
assert Span.current() != null
assert ClientSpan.fromContextOrNull(Context.current()) != null
runWithSpan("internal") {
assert ClientSpan.fromContextOrNull(Context.current()) != null
}
}
}
def "test span key bridge"() {
expect:
AgentSpanTesting.runWithAllSpanKeys("parent") {
assert Span.current() != null
def spanKeys = [
SpanKey.SERVER,
SpanKey.CONSUMER,
SpanKey.HTTP_CLIENT,
SpanKey.RPC_CLIENT,
SpanKey.DB_CLIENT,
SpanKey.MESSAGING_PRODUCER,
SpanKey.ALL_CLIENTS,
SpanKey.ALL_PRODUCERS
]
spanKeys.each { spanKey ->
assert spanKey.fromContextOrNull(Context.current()) != null
}
runWithSpan("internal") {
spanKeys.each { spanKey ->
assert spanKey.fromContextOrNull(Context.current()) != null
}
}
}
}
// TODO (trask)
// more tests are needed here, not sure how to implement, probably need to write some test
// instrumentation to help test, similar to :testing-common:integration-tests

View File

@ -0,0 +1,3 @@
plugins {
id("otel.javaagent-testing")
}

View File

@ -0,0 +1,40 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
public class AgentSpanTesting {
/**
* Runs the provided {@code runnable} inside the scope of an SERVER span with name {@code
* spanName}.
*/
public static void runWithServerSpan(String spanName, Runnable runnable) {
runnable.run();
}
/**
* Runs the provided {@code runnable} inside the scope of an CONSUMER span with name {@code
* spanName}.
*/
public static void runWithConsumerSpan(String spanName, Runnable runnable) {
runnable.run();
}
/**
* Runs the provided {@code runnable} inside the scope of an CLIENT span with name {@code
* spanName}.
*/
public static void runWithClientSpan(String spanName, Runnable runnable) {
runnable.run();
}
/**
* Runs the provided {@code runnable} inside the scope of an INTERNAL span with name {@code
* spanName}. Span is added into context under all possible keys from {@link
* io.opentelemetry.instrumentation.api.instrumenter.SpanKey}
*/
public static void runWithAllSpanKeys(String spanName, Runnable runnable) {
runnable.run();
}
}

View File

@ -0,0 +1,138 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
import static net.bytebuddy.matcher.ElementMatchers.named;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
public class AgentSpanTestingInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("AgentSpanTesting");
}
@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
named("runWithServerSpan"), this.getClass().getName() + "$RunWithServerSpanAdvice");
transformer.applyAdviceToMethod(
named("runWithConsumerSpan"), this.getClass().getName() + "$RunWithConsumerSpanAdvice");
transformer.applyAdviceToMethod(
named("runWithClientSpan"), this.getClass().getName() + "$RunWithClientSpanAdvice");
transformer.applyAdviceToMethod(
named("runWithAllSpanKeys"), this.getClass().getName() + "$RunWithAllSpanKeysAdvice");
}
@SuppressWarnings("unused")
public static class RunWithServerSpanAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.Argument(0) String spanName,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
context = AgentSpanTestingTracer.tracer().startServerSpan(spanName);
scope = context.makeCurrent();
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void onExit(
@Advice.Thrown Throwable throwable,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
scope.close();
if (throwable != null) {
AgentSpanTestingTracer.tracer().endExceptionally(context, throwable);
} else {
AgentSpanTestingTracer.tracer().end(context);
}
}
}
@SuppressWarnings("unused")
public static class RunWithConsumerSpanAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.Argument(0) String spanName,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
context = AgentSpanTestingTracer.tracer().startConsumerSpan(spanName);
scope = context.makeCurrent();
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void onExit(
@Advice.Thrown Throwable throwable,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
scope.close();
if (throwable != null) {
AgentSpanTestingTracer.tracer().endExceptionally(context, throwable);
} else {
AgentSpanTestingTracer.tracer().end(context);
}
}
}
@SuppressWarnings("unused")
public static class RunWithClientSpanAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.Argument(0) String spanName,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
context = AgentSpanTestingTracer.tracer().startClientSpan(spanName);
scope = context.makeCurrent();
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void onExit(
@Advice.Thrown Throwable throwable,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
scope.close();
if (throwable != null) {
AgentSpanTestingTracer.tracer().endExceptionally(context, throwable);
} else {
AgentSpanTestingTracer.tracer().end(context);
}
}
}
@SuppressWarnings("unused")
public static class RunWithAllSpanKeysAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.Argument(0) String spanName,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
context = AgentSpanTestingTracer.tracer().startSpanWithAllKeys(spanName);
scope = context.makeCurrent();
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void onExit(
@Advice.Thrown Throwable throwable,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
scope.close();
if (throwable != null) {
AgentSpanTestingTracer.tracer().endExceptionally(context, throwable);
} else {
AgentSpanTestingTracer.tracer().end(context);
}
}
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
import static java.util.Collections.singletonList;
import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import java.util.List;
@AutoService(InstrumentationModule.class)
public class AgentSpanTestingInstrumentationModule extends InstrumentationModule {
public AgentSpanTestingInstrumentationModule() {
super(AgentSpanTestingInstrumentationModule.class.getName());
}
@Override
public boolean isHelperClass(String className) {
return className.startsWith("AgentSpanTestingTracer");
}
@Override
public List<TypeInstrumentation> typeInstrumentations() {
return singletonList(new AgentSpanTestingInstrumentation());
}
}

View File

@ -0,0 +1,85 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanBuilder;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.SpanKey;
import io.opentelemetry.instrumentation.api.tracer.BaseTracer;
import java.lang.reflect.Field;
public class AgentSpanTestingTracer extends BaseTracer {
private static final AgentSpanTestingTracer TRACER = new AgentSpanTestingTracer();
private AgentSpanTestingTracer() {
super(GlobalOpenTelemetry.get());
}
public static AgentSpanTestingTracer tracer() {
return TRACER;
}
public Context startServerSpan(String name) {
Context parentContext = Context.current();
SpanBuilder spanBuilder = spanBuilder(parentContext, name, SpanKind.SERVER);
return withServerSpan(parentContext, spanBuilder.startSpan());
}
public Context startConsumerSpan(String name) {
Context parentContext = Context.current();
SpanBuilder spanBuilder = spanBuilder(parentContext, name, SpanKind.CONSUMER);
return withConsumerSpan(parentContext, spanBuilder.startSpan());
}
public Context startClientSpan(String name) {
Context parentContext = Context.current();
SpanBuilder spanBuilder = spanBuilder(parentContext, name, SpanKind.CLIENT);
return withClientSpan(parentContext, spanBuilder.startSpan());
}
public Context startSpanWithAllKeys(String name) {
Context parentContext = Context.current();
SpanBuilder spanBuilder = spanBuilder(parentContext, name, SpanKind.INTERNAL);
Span span = spanBuilder.startSpan();
Context newContext = parentContext.with(span);
for (SpanKey spanKey : SpanKeyAccess.getSpanKeys()) {
newContext = spanKey.storeInContext(newContext, span);
}
return newContext;
}
@Override
protected String getInstrumentationName() {
return "agent-span-test-instrumentation";
}
private static class SpanKeyAccess {
public static SpanKey[] getSpanKeys() {
return new SpanKey[] {
SpanKey.SERVER,
SpanKey.CONSUMER,
getSpanKeyByName("HTTP_CLIENT"),
getSpanKeyByName("RPC_CLIENT"),
getSpanKeyByName("DB_CLIENT"),
getSpanKeyByName("MESSAGING_PRODUCER"),
SpanKey.ALL_CLIENTS,
SpanKey.ALL_PRODUCERS
};
}
private static SpanKey getSpanKeyByName(String name) {
try {
Field field = SpanKey.class.getDeclaredField(name);
field.setAccessible(true);
return (SpanKey) field.get(name);
} catch (NoSuchFieldException | IllegalAccessException exception) {
throw new IllegalStateException("Failed to find span key named " + name, exception);
}
}
}
}

View File

@ -253,6 +253,7 @@ include(":instrumentation:okhttp:okhttp-3.0:library")
include(":instrumentation:okhttp:okhttp-3.0:testing")
include(":instrumentation:opentelemetry-annotations-1.0:javaagent")
include(":instrumentation:opentelemetry-api-1.0:javaagent")
include(":instrumentation:opentelemetry-api-1.0:testing")
include(":instrumentation:opentelemetry-api-metrics-1.0:javaagent")
include(":instrumentation:oshi:javaagent")
include(":instrumentation:oshi:library")