-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #36271 from brunobat/AddingSpanAttributes
Support for AddingSpanAttributes
- Loading branch information
Showing
6 changed files
with
297 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
167 changes: 167 additions & 0 deletions
167
.../io/quarkus/opentelemetry/deployment/interceptor/AddingSpanAttributesInterceptorTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
package io.quarkus.opentelemetry.deployment.interceptor; | ||
|
||
import static io.opentelemetry.api.trace.SpanKind.INTERNAL; | ||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
import static org.junit.jupiter.api.Assertions.assertFalse; | ||
import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
||
import java.util.List; | ||
|
||
import jakarta.enterprise.context.ApplicationScoped; | ||
import jakarta.inject.Inject; | ||
|
||
import org.jboss.shrinkwrap.api.ShrinkWrap; | ||
import org.jboss.shrinkwrap.api.spec.JavaArchive; | ||
import org.junit.jupiter.api.AfterEach; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.RegisterExtension; | ||
|
||
import io.opentelemetry.api.common.AttributeKey; | ||
import io.opentelemetry.api.trace.Span; | ||
import io.opentelemetry.api.trace.Tracer; | ||
import io.opentelemetry.context.Scope; | ||
import io.opentelemetry.instrumentation.annotations.AddingSpanAttributes; | ||
import io.opentelemetry.instrumentation.annotations.SpanAttribute; | ||
import io.opentelemetry.instrumentation.annotations.WithSpan; | ||
import io.opentelemetry.sdk.trace.data.SpanData; | ||
import io.quarkus.opentelemetry.deployment.common.TestSpanExporter; | ||
import io.quarkus.opentelemetry.deployment.common.TestSpanExporterProvider; | ||
import io.quarkus.test.QuarkusUnitTest; | ||
|
||
public class AddingSpanAttributesInterceptorTest { | ||
@RegisterExtension | ||
static final QuarkusUnitTest TEST = new QuarkusUnitTest() | ||
.setArchiveProducer( | ||
() -> ShrinkWrap.create(JavaArchive.class) | ||
.addClass(HelloRouter.class) | ||
.addClasses(TestSpanExporter.class, TestSpanExporterProvider.class) | ||
.addAsManifestResource( | ||
"META-INF/services-config/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider", | ||
"services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider") | ||
.addAsResource("resource-config/application.properties", "application.properties")); | ||
|
||
@Inject | ||
HelloRouter helloRouter; | ||
@Inject | ||
Tracer tracer; | ||
@Inject | ||
TestSpanExporter spanExporter; | ||
|
||
@AfterEach | ||
void tearDown() { | ||
spanExporter.reset(); | ||
} | ||
|
||
@Test | ||
void withSpanAttributesTest_existingSpan() { | ||
Span span = tracer.spanBuilder("withSpanAttributesTest").startSpan(); | ||
String result; | ||
try (Scope scope = span.makeCurrent()) { | ||
result = helloRouter.withSpanAttributes( | ||
"implicit", "explicit", null, "ignore"); | ||
} finally { | ||
span.end(); | ||
} | ||
assertEquals("hello!", result); | ||
List<SpanData> spanItems = spanExporter.getFinishedSpanItems(1); | ||
SpanData spanDataOut = spanItems.get(0); | ||
assertEquals("withSpanAttributesTest", spanDataOut.getName()); | ||
assertEquals(INTERNAL, spanDataOut.getKind()); | ||
assertFalse(spanDataOut.getAttributes().isEmpty(), "No attributes found"); | ||
assertEquals("implicit", getAttribute(spanDataOut, "implicitName")); | ||
assertEquals("explicit", getAttribute(spanDataOut, "explicitName")); | ||
} | ||
|
||
@Test | ||
void withSpanAttributesTest_noActiveSpan() { | ||
String resultWithoutSpan = helloRouter.withSpanAttributes( | ||
"implicit", "explicit", null, "ignore"); | ||
assertEquals("hello!", resultWithoutSpan); | ||
|
||
spanExporter.getFinishedSpanItems(0); | ||
// No span created | ||
|
||
String resultWithSpan = helloRouter.withSpanTakesPrecedence( | ||
"implicit", "explicit", null, "ignore"); | ||
assertEquals("hello!", resultWithSpan); | ||
|
||
// we need 1 span to make sure we don't get a false positive. | ||
// The previous call to getFinishedSpanItems might return too early. | ||
|
||
List<SpanData> spanItems = spanExporter.getFinishedSpanItems(1); | ||
assertEquals(1, spanItems.size()); | ||
SpanData spanDataOut = spanItems.get(0); | ||
assertEquals("HelloRouter.withSpanTakesPrecedence", spanDataOut.getName()); | ||
} | ||
|
||
@Test | ||
void withSpanAttributesTest_newSpan() { | ||
String result = helloRouter.withSpanTakesPrecedence( | ||
"implicit", "explicit", null, "ignore"); | ||
|
||
assertEquals("hello!", result); | ||
List<SpanData> spanItems = spanExporter.getFinishedSpanItems(1); | ||
SpanData spanDataOut = spanItems.get(0); | ||
assertEquals("HelloRouter.withSpanTakesPrecedence", spanDataOut.getName()); | ||
assertEquals(INTERNAL, spanDataOut.getKind()); | ||
assertEquals(2, spanDataOut.getAttributes().size()); | ||
assertEquals("implicit", getAttribute(spanDataOut, "implicitName")); | ||
assertEquals("explicit", getAttribute(spanDataOut, "explicitName")); | ||
} | ||
|
||
@Test | ||
void noAttributesAdded() { | ||
Span span = tracer.spanBuilder("noAttributesAdded").startSpan(); | ||
String result; | ||
try (Scope scope = span.makeCurrent()) { | ||
result = helloRouter.noAttributesAdded( | ||
"implicit", "explicit", null, "ignore"); | ||
} finally { | ||
span.end(); | ||
} | ||
assertEquals("hello!", result); | ||
List<SpanData> spanItems = spanExporter.getFinishedSpanItems(1); | ||
SpanData spanDataOut = spanItems.get(0); | ||
assertEquals("noAttributesAdded", spanDataOut.getName()); | ||
assertEquals(INTERNAL, spanDataOut.getKind()); | ||
assertTrue(spanDataOut.getAttributes().isEmpty(), "No attributes must be present"); | ||
} | ||
|
||
private static Object getAttribute(SpanData spanDataOut, String attributeName) { | ||
return spanDataOut.getAttributes().asMap().get(AttributeKey.stringKey(attributeName)); | ||
} | ||
|
||
@ApplicationScoped | ||
public static class HelloRouter { | ||
// mast have already an active span | ||
@AddingSpanAttributes | ||
public String withSpanAttributes( | ||
@SpanAttribute String implicitName, | ||
@SpanAttribute("explicitName") String parameter, | ||
@SpanAttribute("nullAttribute") String nullAttribute, | ||
String notTraced) { | ||
|
||
return "hello!"; | ||
} | ||
|
||
@WithSpan | ||
@AddingSpanAttributes | ||
public String withSpanTakesPrecedence( | ||
@SpanAttribute String implicitName, | ||
@SpanAttribute("explicitName") String parameter, | ||
@SpanAttribute("nullAttribute") String nullAttribute, | ||
String notTraced) { | ||
|
||
return "hello!"; | ||
} | ||
|
||
public String noAttributesAdded( | ||
@SpanAttribute String implicitName, | ||
@SpanAttribute("explicitName") String parameter, | ||
@SpanAttribute("nullAttribute") String nullAttribute, | ||
String notTraced) { | ||
|
||
return "hello!"; | ||
} | ||
} | ||
} |
2 changes: 1 addition & 1 deletion
2
...y/deployment/WithSpanInterceptorTest.java → .../interceptor/WithSpanInterceptorTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
...oyment/WithSpanLegacyInterceptorTest.java → ...ceptor/WithSpanLegacyInterceptorTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
87 changes: 87 additions & 0 deletions
87
...in/java/io/quarkus/opentelemetry/runtime/tracing/cdi/AddingSpanAttributesInterceptor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package io.quarkus.opentelemetry.runtime.tracing.cdi; | ||
|
||
import java.lang.reflect.Method; | ||
import java.lang.reflect.Parameter; | ||
|
||
import jakarta.annotation.Priority; | ||
import jakarta.interceptor.AroundInvoke; | ||
import jakarta.interceptor.Interceptor; | ||
|
||
import io.opentelemetry.api.trace.Span; | ||
import io.opentelemetry.context.Scope; | ||
import io.opentelemetry.instrumentation.annotations.AddingSpanAttributes; | ||
import io.opentelemetry.instrumentation.annotations.SpanAttribute; | ||
import io.opentelemetry.instrumentation.api.annotation.support.ParameterAttributeNamesExtractor; | ||
import io.quarkus.arc.ArcInvocationContext; | ||
|
||
/** | ||
* Will capture the arguments annotated with {@link SpanAttribute} on methods annotated with {@link AddingSpanAttributes}. | ||
* Will not start a Span if one is not already started. | ||
*/ | ||
@SuppressWarnings("CdiInterceptorInspection") | ||
@Interceptor | ||
@Priority(Interceptor.Priority.PLATFORM_BEFORE) | ||
public class AddingSpanAttributesInterceptor { | ||
|
||
private final WithSpanParameterAttributeNamesExtractor extractor; | ||
|
||
public AddingSpanAttributesInterceptor() { | ||
extractor = new WithSpanParameterAttributeNamesExtractor(); | ||
} | ||
|
||
@AroundInvoke | ||
public Object span(final ArcInvocationContext invocationContext) throws Exception { | ||
String[] extractedParameterNames = extractor.extract(invocationContext.getMethod(), | ||
invocationContext.getMethod().getParameters()); | ||
Object[] parameterValues = invocationContext.getParameters(); | ||
|
||
Span span = Span.current(); | ||
if (span.isRecording()) { | ||
try (Scope scope = span.makeCurrent()) { | ||
for (int i = 0; i < extractedParameterNames.length; i++) { | ||
if (extractedParameterNames[i] == null || parameterValues[i] == null) { | ||
continue; | ||
} | ||
span.setAttribute(extractedParameterNames[i], parameterValues[i].toString()); | ||
} | ||
} | ||
} | ||
return invocationContext.proceed(); | ||
} | ||
|
||
private static final class WithSpanParameterAttributeNamesExtractor implements ParameterAttributeNamesExtractor { | ||
@Override | ||
public String[] extract(final Method method, final Parameter[] parameters) { | ||
String[] attributeNames = new String[parameters.length]; | ||
for (int i = 0; i < parameters.length; i++) { | ||
attributeNames[i] = attributeName(parameters[i]); | ||
} | ||
return attributeNames; | ||
} | ||
|
||
private static String attributeName(Parameter parameter) { | ||
String value; | ||
SpanAttribute spanAttribute = parameter.getDeclaredAnnotation(SpanAttribute.class); | ||
if (spanAttribute == null) { | ||
// Needed because SpanAttribute cannot be transformed | ||
io.opentelemetry.extension.annotations.SpanAttribute legacySpanAttribute = parameter.getDeclaredAnnotation( | ||
io.opentelemetry.extension.annotations.SpanAttribute.class); | ||
if (legacySpanAttribute == null) { | ||
return null; | ||
} else { | ||
value = legacySpanAttribute.value(); | ||
} | ||
} else { | ||
value = spanAttribute.value(); | ||
} | ||
|
||
if (!value.isEmpty()) { | ||
return value; | ||
} else if (parameter.isNamePresent()) { | ||
return parameter.getName(); | ||
} else { | ||
return null; | ||
} | ||
} | ||
} | ||
} |