Skip to content
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 @@ -8,6 +8,9 @@ muzzle {
module.set("azure-core")
versions.set("[1.36.0,1.53.0)")
assertInverse.set(true)
// our advice helper bridges an explicitly supplied application parent context to the agent
// context, so it references io.opentelemetry.context.{Context,Scope}
extraDependency("io.opentelemetry:opentelemetry-api:1.27.0")
}
}

Expand All @@ -24,6 +27,10 @@ sourceSets {
dependencies {
compileOnly(project(":instrumentation:azure-core:azure-core-1.36:library-instrumentation-shaded", configuration = "shadow"))

// needed to bridge an explicitly supplied application parent context (the unshaded
// "application.io.opentelemetry.*" types) to the agent context inside our advice
compileOnly(project(":opentelemetry-api-shaded-for-instrumenting", configuration = "shadow"))

library("com.azure:azure-core:1.36.0")

// Ensure no cross interference
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.azurecore.v1_36;

import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import java.util.Optional;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.asm.Advice.AssignReturned;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

/**
* Bridges an explicitly supplied parent context for {@code azure-core-tracing-opentelemetry}.
*
* <p>When a user passes a parent context to an Azure SDK call, the value is stored on the {@link
* com.azure.core.util.Context} under {@link
* com.azure.core.util.tracing.Tracer#PARENT_TRACE_CONTEXT_KEY} as the application's (unshaded)
* {@code io.opentelemetry.context.Context}. The bundled {@code OpenTelemetryTracer} reads that
* value back and expects it to be the agent's (shaded) context. Convert it here so the tracer does
* not need to reach into agent internals reflectively.
*/
class AzureContextInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("com.azure.core.util.Context");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
named("getData").and(takesArguments(1)), getClass().getName() + "$GetDataAdvice");
}

@SuppressWarnings("unused")
public static class GetDataAdvice {
@AssignReturned.ToReturned
@Advice.OnMethodExit(suppress = Throwable.class, inline = false)
public static Optional<Object> onExit(
@Advice.Argument(0) Object key, @Advice.Return Optional<Object> data) {
return AzureExplicitParentContextHelper.bridgeApplicationContext(key, data);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.azurecore.v1_36;

import static com.azure.core.util.tracing.Tracer.PARENT_TRACE_CONTEXT_KEY;

import io.opentelemetry.context.Context;
import java.util.Optional;

/**
* Converts an explicitly supplied application parent context into the agent context.
*
* <p>In the agent the application's {@code io.opentelemetry.context.Context} (referenced here as
* {@code application.io.opentelemetry.context.Context}) is bridged to the agent context by the
* opentelemetry-api instrumentation. Making the application context current and reading back the
* agent context performs that conversion using only public API, so this works in both inline and
* indy mode without reaching into agent-internal helper classes.
*/
public final class AzureExplicitParentContextHelper {

public static Optional<Object> bridgeApplicationContext(Object key, Optional<Object> data) {
if (!PARENT_TRACE_CONTEXT_KEY.equals(key) || data == null || !data.isPresent()) {
return data;
}
Object value = data.get();
if (!(value instanceof application.io.opentelemetry.context.Context)) {
return data;
}
application.io.opentelemetry.context.Context applicationContext =
(application.io.opentelemetry.context.Context) value;
Context agentContext;
try (application.io.opentelemetry.context.Scope ignored = applicationContext.makeCurrent()) {
agentContext = Context.current();
}
return Optional.of(agentContext);
}

private AzureExplicitParentContextHelper() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return asList(new EmptyTypeInstrumentation(), new AzureHttpClientInstrumentation());
return asList(
new EmptyTypeInstrumentation(),
new AzureContextInstrumentation(),
new AzureHttpClientInstrumentation());
}

private static class EmptyTypeInstrumentation implements TypeInstrumentation {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
import com.azure.core.util.TracingOptions;
import com.azure.core.util.tracing.Tracer;
import com.azure.core.util.tracing.TracerProvider;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.api.internal.SpanKey;
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
Expand Down Expand Up @@ -78,6 +80,32 @@ void testSpan() {
equalTo(stringKey("az.namespace"), "otel.tests"))));
}

@Test
void testExplicitParentContextBridge() {
// Azure's bundled OpenTelemetryTracer expects the value stored under
// Tracer.PARENT_TRACE_CONTEXT_KEY to be the agent (shaded) Context.
// This test verifies our Context#getData instrumentation bridges an explicitly supplied
// application io.opentelemetry.context.Context into an agent io.opentelemetry.context.Context.
// The parent span is never made current, so correct parenting requires this bridge.
Tracer azTracer = createAzTracer();

Span parentSpan = GlobalOpenTelemetry.getTracer("test").spanBuilder("parent").startSpan();
// application (unshaded) context carrying the parent span, NOT made current
io.opentelemetry.context.Context parentContext =
io.opentelemetry.context.Context.root().with(parentSpan);

Context azContext = new Context(Tracer.PARENT_TRACE_CONTEXT_KEY, parentContext);
Context child = azTracer.start("child", azContext);
azTracer.end(null, null, child);
parentSpan.end();

testing.waitAndAssertTracesWithoutScopeVersionVerification(
trace ->
trace.hasSpansSatisfyingExactly(
span -> span.hasName("parent").hasNoParent(),
span -> span.hasName("child").hasParent(trace.getSpan(0))));
}

@Test
void testPipelineAndSuppression() {
AtomicBoolean hasClientAndHttpKeys = new AtomicBoolean(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ muzzle {
module.set("azure-core")
versions.set("[1.53.0,)")
assertInverse.set(true)
// our advice helper bridges an explicitly supplied application parent context to the agent
// context, so it references io.opentelemetry.context.{Context,Scope}
extraDependency("io.opentelemetry:opentelemetry-api:1.27.0")
}
}

Expand All @@ -24,6 +27,10 @@ sourceSets {
dependencies {
compileOnly(project(":instrumentation:azure-core:azure-core-1.53:library-instrumentation-shaded", configuration = "shadow"))

// needed to bridge an explicitly supplied application parent context (the unshaded
// "application.io.opentelemetry.*" types) to the agent context inside our advice
compileOnly(project(":opentelemetry-api-shaded-for-instrumenting", configuration = "shadow"))

library("com.azure:azure-core:1.53.0")

// Ensure no cross interference
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.azurecore.v1_53;

import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import java.util.Optional;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.asm.Advice.AssignReturned;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

/**
* Bridges an explicitly supplied parent context for {@code azure-core-tracing-opentelemetry}.
*
* <p>When a user passes a parent context to an Azure SDK call, the value is stored on the {@link
* com.azure.core.util.Context} under {@link
* com.azure.core.util.tracing.Tracer#PARENT_TRACE_CONTEXT_KEY} as the application's (unshaded)
* {@code io.opentelemetry.context.Context}. The bundled {@code OpenTelemetryTracer} reads that
* value back and expects it to be the agent's (shaded) context. Convert it here so the tracer does
* not need to reach into agent internals reflectively.
*/
class AzureContextInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("com.azure.core.util.Context");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
named("getData").and(takesArguments(1)), getClass().getName() + "$GetDataAdvice");
}

@SuppressWarnings("unused")
public static class GetDataAdvice {
@AssignReturned.ToReturned
@Advice.OnMethodExit(suppress = Throwable.class, inline = false)
public static Optional<Object> onExit(
@Advice.Argument(0) Object key, @Advice.Return Optional<Object> data) {
return AzureExplicitParentContextHelper.bridgeApplicationContext(key, data);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.azurecore.v1_53;

import static com.azure.core.util.tracing.Tracer.PARENT_TRACE_CONTEXT_KEY;

import io.opentelemetry.context.Context;
import java.util.Optional;

/**
* Converts an explicitly supplied application parent context into the agent context.
*
* <p>In the agent the application's {@code io.opentelemetry.context.Context} (referenced here as
* {@code application.io.opentelemetry.context.Context}) is bridged to the agent context by the
* opentelemetry-api instrumentation. Making the application context current and reading back the
* agent context performs that conversion using only public API, so this works in both inline and
* indy mode without reaching into agent-internal helper classes.
*/
public final class AzureExplicitParentContextHelper {

public static Optional<Object> bridgeApplicationContext(Object key, Optional<Object> data) {
if (!PARENT_TRACE_CONTEXT_KEY.equals(key) || data == null || !data.isPresent()) {
return data;
}
Object value = data.get();
if (!(value instanceof application.io.opentelemetry.context.Context)) {
return data;
}
application.io.opentelemetry.context.Context applicationContext =
(application.io.opentelemetry.context.Context) value;
Context agentContext;
try (application.io.opentelemetry.context.Scope ignored = applicationContext.makeCurrent()) {
agentContext = Context.current();
}
return Optional.of(agentContext);
}

private AzureExplicitParentContextHelper() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return asList(new EmptyTypeInstrumentation(), new AzureHttpClientInstrumentation());
return asList(
new EmptyTypeInstrumentation(),
new AzureContextInstrumentation(),
new AzureHttpClientInstrumentation());
Comment thread
trask marked this conversation as resolved.
}

private static class EmptyTypeInstrumentation implements TypeInstrumentation {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
import com.azure.core.util.TracingOptions;
import com.azure.core.util.tracing.Tracer;
import com.azure.core.util.tracing.TracerProvider;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.api.internal.SpanKey;
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
Expand Down Expand Up @@ -78,6 +80,32 @@ void testSpan() {
equalTo(stringKey("az.namespace"), "otel.tests"))));
}

@Test
void testExplicitParentContextBridge() {
// Azure's bundled OpenTelemetryTracer expects the value stored under
// Tracer.PARENT_TRACE_CONTEXT_KEY to be the agent (shaded) Context.
// This test verifies our Context#getData instrumentation bridges an explicitly supplied
// application io.opentelemetry.context.Context into an agent io.opentelemetry.context.Context.
// The parent span is never made current, so correct parenting requires this bridge.
Tracer azTracer = createAzTracer();

Span parentSpan = GlobalOpenTelemetry.getTracer("test").spanBuilder("parent").startSpan();
// application (unshaded) context carrying the parent span, NOT made current
io.opentelemetry.context.Context parentContext =
io.opentelemetry.context.Context.root().with(parentSpan);

Context azContext = new Context(Tracer.PARENT_TRACE_CONTEXT_KEY, parentContext);
Context child = azTracer.start("child", azContext);
azTracer.end(null, null, child);
parentSpan.end();

testing.waitAndAssertTracesWithoutScopeVersionVerification(
trace ->
trace.hasSpansSatisfyingExactly(
span -> span.hasName("parent").hasNoParent(),
span -> span.hasName("child").hasParent(trace.getSpan(0))));
}

@Test
void testPipelineAndSuppression() {
AtomicBoolean hasClientAndHttpKeys = new AtomicBoolean(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.opentelemetryapi;
package io.opentelemetry.javaagent.instrumentation.opentelemetryapi.v1_0;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.javaagent.instrumentation.opentelemetryapi.context.propagation.ApplicationContextPropagators;
import io.opentelemetry.javaagent.instrumentation.opentelemetryapi.trace.ApplicationTracerProvider;
import io.opentelemetry.javaagent.instrumentation.opentelemetryapi.v1_0.context.propagation.ApplicationContextPropagators;
import io.opentelemetry.javaagent.instrumentation.opentelemetryapi.v1_0.trace.ApplicationTracerProvider;
import javax.annotation.Nullable;

public class ApplicationOpenTelemetry implements application.io.opentelemetry.api.OpenTelemetry {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.opentelemetryapi;
package io.opentelemetry.javaagent.instrumentation.opentelemetryapi.v1_0;

import static net.bytebuddy.matcher.ElementMatchers.isStatic;
import static net.bytebuddy.matcher.ElementMatchers.named;

import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.opentelemetryapi.context.AgentContextStorage;
import io.opentelemetry.javaagent.instrumentation.opentelemetryapi.v1_0.context.AgentContextStorage;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.asm.Advice.AssignReturned;
import net.bytebuddy.description.type.TypeDescription;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.opentelemetryapi;
package io.opentelemetry.javaagent.instrumentation.opentelemetryapi.v1_0;

import static net.bytebuddy.matcher.ElementMatchers.named;

import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.opentelemetryapi.context.AgentContextStorage;
import io.opentelemetry.javaagent.instrumentation.opentelemetryapi.v1_0.context.AgentContextStorage;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
Expand Down
Loading
Loading