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 spanContextOptional = updatedContext.getData(SPAN_CONTEXT_KEY); + assertNotNull(spanContextOptional); + SpanContext spanContext = (SpanContext) spanContextOptional.get(); + assertEquals(spanContext, validSpanContext); + } + + + @Test + public void extractContextInvalidDiagnosticId() { + // Arrange + String diagnosticId = "00000000000000000000000000000000"; + SpanContext invalidSpanContext = SpanContext.create( + TraceId.getInvalid(), + SpanId.getInvalid(), + TraceFlags.getDefault(), + Tracestate.getDefault() + ); + + // Act + Context updatedContext = openTelemetryTracer.extractContext(diagnosticId, Context.NONE); + + // Assert + Optional spanContextOptional = updatedContext.getData(SPAN_CONTEXT_KEY); + assertNotNull(spanContextOptional); + SpanContext spanContext = (SpanContext) spanContextOptional.get(); + assertEquals(spanContext, invalidSpanContext); + } + private static void assertSpanWithExplicitParent(Context updatedContext, SpanId parentSpanId) { assertNotNull(updatedContext.getData(PARENT_SPAN_KEY).get()); diff --git a/sdk/core/azure-core-tracing-opentelemetry/src/test/resources/META-INF/services/com.azure.core.http.policy.AfterRetryPolicyProvider b/sdk/core/azure-core-tracing-opentelemetry/src/test/resources/META-INF/services/com.azure.core.http.policy.AfterRetryPolicyProvider new file mode 100644 index 0000000000000..a4dd2d32f57e6 --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/src/test/resources/META-INF/services/com.azure.core.http.policy.AfterRetryPolicyProvider @@ -0,0 +1 @@ +com.azure.core.tracing.opentelemetry.OpenTelemetryHttpPolicy