diff --git a/sdk/core/azure-core-tracing-opentelemetry/pom.xml b/sdk/core/azure-core-tracing-opentelemetry/pom.xml
index ff6c0adefb084..bc2e5f46f57cf 100644
--- a/sdk/core/azure-core-tracing-opentelemetry/pom.xml
+++ b/sdk/core/azure-core-tracing-opentelemetry/pom.xml
@@ -50,6 +50,12 @@
0.2.0
test
+
+ com.azure
+ azure-core-http-netty
+ 1.3.0-beta.1
+ test
+
org.junit.jupiter
junit-jupiter-api
diff --git a/sdk/core/azure-core-tracing-opentelemetry/src/main/java/com/azure/core/tracing/opentelemetry/OpenTelemetryTracer.java b/sdk/core/azure-core-tracing-opentelemetry/src/main/java/com/azure/core/tracing/opentelemetry/OpenTelemetryTracer.java
index 9eaf836f8ab79..e17575a76fd94 100644
--- a/sdk/core/azure-core-tracing-opentelemetry/src/main/java/com/azure/core/tracing/opentelemetry/OpenTelemetryTracer.java
+++ b/sdk/core/azure-core-tracing-opentelemetry/src/main/java/com/azure/core/tracing/opentelemetry/OpenTelemetryTracer.java
@@ -119,6 +119,7 @@ public void end(int responseCode, Throwable throwable, Context context) {
*/
@Override
public void setAttribute(String key, String value, Context context) {
+ Objects.requireNonNull(context, "'context' cannot be null");
if (CoreUtils.isNullOrEmpty(value)) {
logger.warning("Failed to set span attribute since value is null or empty.");
return;
diff --git a/sdk/core/azure-core-tracing-opentelemetry/src/main/java/module-info.java b/sdk/core/azure-core-tracing-opentelemetry/src/main/java/module-info.java
index 6ef07d68d0c1d..f3c53360330dc 100644
--- a/sdk/core/azure-core-tracing-opentelemetry/src/main/java/module-info.java
+++ b/sdk/core/azure-core-tracing-opentelemetry/src/main/java/module-info.java
@@ -5,6 +5,8 @@
requires transitive com.azure.core;
requires opentelemetry.api;
+ opens com.azure.core.tracing.opentelemetry to com.fasterxml.jackson.databind;
+
exports com.azure.core.tracing.opentelemetry;
provides com.azure.core.util.tracing.Tracer
diff --git a/sdk/core/azure-core-tracing-opentelemetry/src/test/java/com/azure/core/tracing/opentelemetry/OpenTelemetryHttpPolicyTests.java b/sdk/core/azure-core-tracing-opentelemetry/src/test/java/com/azure/core/tracing/opentelemetry/OpenTelemetryHttpPolicyTests.java
new file mode 100644
index 0000000000000..22ff418088815
--- /dev/null
+++ b/sdk/core/azure-core-tracing-opentelemetry/src/test/java/com/azure/core/tracing/opentelemetry/OpenTelemetryHttpPolicyTests.java
@@ -0,0 +1,135 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.core.tracing.opentelemetry;
+
+import com.azure.core.annotation.ExpectedResponses;
+import com.azure.core.annotation.Get;
+import com.azure.core.annotation.Host;
+import com.azure.core.annotation.ServiceInterface;
+import com.azure.core.http.HttpClient;
+import com.azure.core.http.HttpPipeline;
+import com.azure.core.http.HttpPipelineBuilder;
+import com.azure.core.http.policy.HttpPipelinePolicy;
+import com.azure.core.http.policy.HttpPolicyProviders;
+import com.azure.core.http.rest.RestProxy;
+import com.azure.core.tracing.opentelemetry.implementation.AmqpPropagationFormatUtil;
+import com.azure.core.util.Context;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.opentelemetry.OpenTelemetry;
+import io.opentelemetry.trace.Span;
+import io.opentelemetry.trace.SpanContext;
+import io.opentelemetry.trace.Tracer;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static com.azure.core.util.tracing.Tracer.PARENT_SPAN_KEY;
+import static com.azure.core.util.tracing.Tracer.SPAN_CONTEXT_KEY;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ * Unit tests for {@link OpenTelemetryHttpPolicy}.
+ */
+public class OpenTelemetryHttpPolicyTests {
+
+ @Test
+ public void addAfterPolicyTest() {
+ // Arrange & Act
+ final HttpPipeline pipeline = createHttpPipeline();
+
+ // Assert
+ assertEquals(1, pipeline.getPolicyCount());
+ assertEquals(OpenTelemetryHttpPolicy.class, pipeline.getPolicy(0).getClass());
+ }
+
+ @Host("https://httpbin.org")
+ @ServiceInterface(name = "TestService")
+ interface TestService {
+ @Get("anything")
+ @ExpectedResponses({200})
+ HttpBinJSON getAnything(Context context);
+ }
+
+ @Test
+ public void openTelemetryHttpPolicyTest() {
+ // Arrange
+ // Get the global singleton Tracer object.
+ Tracer tracer = OpenTelemetry.getTracerFactory().get("TracerSdkTest");
+ // Start user parent span.
+ Span parentSpan = tracer.spanBuilder(PARENT_SPAN_KEY).startSpan();
+ tracer.withSpan(parentSpan);
+ // Add parent span to tracingContext
+ Context tracingContext = new Context(PARENT_SPAN_KEY, parentSpan);
+
+ Span expectedSpan = tracer
+ .spanBuilder("/anything")
+ .setParent(parentSpan)
+ .setSpanKind(Span.Kind.CLIENT)
+ .startSpan();
+
+ // Act
+ HttpBinJSON response = RestProxy
+ .create(OpenTelemetryHttpPolicyTests.TestService.class, createHttpPipeline()).getAnything(tracingContext);
+
+ // Assert
+ String diagnosticId = response.headers().get("Traceparent");
+ assertNotNull(diagnosticId);
+ Context updatedContext = AmqpPropagationFormatUtil.extractContext(diagnosticId, Context.NONE);
+ SpanContext returnedSpanContext = (SpanContext) updatedContext.getData(SPAN_CONTEXT_KEY).get();
+ verifySpanContextAttributes(expectedSpan.getContext(), returnedSpanContext);
+ }
+
+ private static HttpPipeline createHttpPipeline() {
+ final HttpClient httpClient = HttpClient.createDefault();
+ final List policies = new ArrayList<>();
+ HttpPolicyProviders.addAfterRetryPolicies(policies);
+
+ final HttpPipeline httpPipeline = new HttpPipelineBuilder()
+ .httpClient(httpClient)
+ .policies(policies.toArray(new HttpPipelinePolicy[0]))
+ .build();
+ return httpPipeline;
+ }
+
+ private static void verifySpanContextAttributes(SpanContext expectedSpanContext, SpanContext actualSpanContext) {
+ assertEquals(expectedSpanContext.getTraceId(), actualSpanContext.getTraceId());
+ assertNotEquals(expectedSpanContext.getSpanId(), actualSpanContext.getSpanId());
+ assertEquals(expectedSpanContext.getTraceFlags(), actualSpanContext.getTraceFlags());
+ assertEquals(expectedSpanContext.getTracestate(), actualSpanContext.getTracestate());
+ assertEquals(expectedSpanContext.isValid(), actualSpanContext.isValid());
+ assertEquals(expectedSpanContext.isRemote(), actualSpanContext.isRemote());
+ }
+
+ /**
+ * Maps to the JSON return values from http://httpbin.org.
+ */
+ static class HttpBinJSON {
+
+ @JsonProperty()
+ private Map headers;
+
+ /**
+ * Gets the response headers.
+ *
+ * @return The response headers.
+ */
+ public Map headers() {
+ return headers;
+ }
+
+ /**
+ * Sets the response headers.
+ *
+ * @param headers The response headers.
+ */
+ public void headers(Map headers) {
+ this.headers = headers;
+ }
+ }
+
+}
diff --git a/sdk/core/azure-core-tracing-opentelemetry/src/test/java/com/azure/core/tracing/opentelemetry/OpenTelemetryTracerTest.java b/sdk/core/azure-core-tracing-opentelemetry/src/test/java/com/azure/core/tracing/opentelemetry/OpenTelemetryTracerTest.java
index 798d849782ea5..1954544a61bc1 100644
--- a/sdk/core/azure-core-tracing-opentelemetry/src/test/java/com/azure/core/tracing/opentelemetry/OpenTelemetryTracerTest.java
+++ b/sdk/core/azure-core-tracing-opentelemetry/src/test/java/com/azure/core/tracing/opentelemetry/OpenTelemetryTracerTest.java
@@ -13,13 +13,17 @@
import io.opentelemetry.trace.Span;
import io.opentelemetry.trace.SpanContext;
import io.opentelemetry.trace.SpanId;
+import io.opentelemetry.trace.TraceFlags;
+import io.opentelemetry.trace.TraceId;
import io.opentelemetry.trace.Tracer;
+import io.opentelemetry.trace.Tracestate;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.Map;
+import java.util.Optional;
import static com.azure.core.tracing.opentelemetry.OpenTelemetryTracer.AZ_NAMESPACE_KEY;
import static com.azure.core.tracing.opentelemetry.OpenTelemetryTracer.COMPONENT;
@@ -33,6 +37,7 @@
import static com.azure.core.util.tracing.Tracer.SCOPE_KEY;
import static com.azure.core.util.tracing.Tracer.SPAN_BUILDER_KEY;
import static com.azure.core.util.tracing.Tracer.SPAN_CONTEXT_KEY;
+import static com.azure.core.util.tracing.Tracer.USER_SPAN_NAME_KEY;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -248,6 +253,34 @@ public void addLinkTest() {
Assertions.assertEquals(expectedLink.getContext().getSpanId(), createdLink.getContext().getSpanId());
}
+ @Test
+ public void addLinkNoSpanContextTest() {
+ // Arrange
+ Span.Builder span = tracer.spanBuilder("parent-span");
+
+ // Act
+ openTelemetryTracer.addLink(new Context(SPAN_BUILDER_KEY, span));
+ ReadableSpan span1 = (ReadableSpan) span.startSpan();
+
+ //Assert
+ // verify no links were added
+ assertEquals(span1.toSpanData().getLinks().size(), 0);
+ }
+
+ @Test
+ public void addLinkNoSpanToLinkTest() {
+ // Arrange
+ Span.Builder span = tracer.spanBuilder("parent-span");
+
+ // Act
+ openTelemetryTracer.addLink(Context.NONE);
+ ReadableSpan span1 = (ReadableSpan) span.startSpan();
+
+ //Assert
+ // verify no links were added
+ assertEquals(span1.toSpanData().getLinks().size(), 0);
+ }
+
@Test
public void endSpanNoSuccessErrorMessageTest() {
// Arrange
@@ -292,6 +325,93 @@ public void endSpanTestThrowableResponseCode() {
}
+ @Test
+ public void setAttributeTest() {
+ // Arrange
+ final String firstKey = "first-key";
+ final String firstKeyValue = "first-value";
+ Context spanContext = openTelemetryTracer.start(METHOD_NAME, tracingContext);
+ final ReadableSpan recordEventsSpan = (ReadableSpan) spanContext.getData(PARENT_SPAN_KEY).get();
+
+ // Act
+ openTelemetryTracer.setAttribute(firstKey, firstKeyValue, spanContext);
+
+ // Assert
+ final Map attributeMap = recordEventsSpan.toSpanData().getAttributes();
+ assertEquals(attributeMap.get(firstKey), AttributeValue.stringAttributeValue(firstKeyValue));
+ }
+
+ @Test
+ public void setAttributeNoSpanTest() {
+ // Arrange
+ final String firstKey = "first-key";
+ final String firstKeyValue = "first-value";
+ Context spanContext = openTelemetryTracer.start(METHOD_NAME, tracingContext);
+ final ReadableSpan recordEventsSpan = (ReadableSpan) spanContext.getData(PARENT_SPAN_KEY).get();
+
+ // Act
+ openTelemetryTracer.setAttribute(firstKey, firstKeyValue, Context.NONE);
+
+ // Assert
+ final Map attributeMap = recordEventsSpan.toSpanData().getAttributes();
+ assertEquals(attributeMap.size(), 0);
+ }
+
+ @Test
+ public void setSpanNameTest() {
+ // Arrange
+ Context initialContext = Context.NONE;
+ final String spanName = "child-span";
+
+ // Act
+ Context updatedContext = openTelemetryTracer.setSpanName(spanName, initialContext);
+
+ // Assert
+ assertEquals(updatedContext.getData(USER_SPAN_NAME_KEY).get(), spanName);
+ }
+
+ @Test
+ public void extractContextValidDiagnosticId() {
+ // Arrange
+ String diagnosticId = "00-bc7293302f5dc6de8a2372491092df95-dfd6fee494751d3f-01";
+ SpanContext validSpanContext = SpanContext.create(
+ TraceId.fromLowerBase16(diagnosticId, 3),
+ SpanId.fromLowerBase16(diagnosticId, 36),
+ TraceFlags.fromLowerBase16(diagnosticId, 53),
+ Tracestate.builder().build());
+
+ // Act
+ Context updatedContext = openTelemetryTracer.extractContext(diagnosticId, Context.NONE);
+
+ // Assert
+ Optional