diff --git a/api/src/main/java/io/smallrye/opentelemetry/api/OpenTelemetryHandler.java b/api/src/main/java/io/smallrye/opentelemetry/api/OpenTelemetryHandler.java
index 4caef969..3f58490e 100644
--- a/api/src/main/java/io/smallrye/opentelemetry/api/OpenTelemetryHandler.java
+++ b/api/src/main/java/io/smallrye/opentelemetry/api/OpenTelemetryHandler.java
@@ -56,7 +56,10 @@ private static Severity toSeverity(final Level level) {
}
public static void install(final OpenTelemetry openTelemetry) {
- Logger logger = openTelemetry.getLogsBridge().loggerBuilder(OpenTelemetryConfig.INSTRUMENTATION_NAME).build();
+ Logger logger = openTelemetry.getLogsBridge()
+ .loggerBuilder(OpenTelemetryConfig.INSTRUMENTATION_NAME)
+ .setInstrumentationVersion(OpenTelemetryConfig.INSTRUMENTATION_VERSION)
+ .build();
LogManager.getLogManager().getLogger("").addHandler(new OpenTelemetryHandler(logger));
}
}
diff --git a/implementation/micrometer-otel-bridge/src/test/java/io/smallrye/opentelemetry/implementation/micrometer/cdi/HistogramTest.java b/implementation/micrometer-otel-bridge/src/test/java/io/smallrye/opentelemetry/implementation/micrometer/cdi/HistogramTest.java
index 283ab86b..7997ff7e 100644
--- a/implementation/micrometer-otel-bridge/src/test/java/io/smallrye/opentelemetry/implementation/micrometer/cdi/HistogramTest.java
+++ b/implementation/micrometer-otel-bridge/src/test/java/io/smallrye/opentelemetry/implementation/micrometer/cdi/HistogramTest.java
@@ -35,7 +35,7 @@ public class HistogramTest {
void histogramTest() {
manualHistogramBean.recordHistogram();
- MetricData testSummary = exporter.getFinishedHistogramItem("testSummary", 4);
+ MetricData testSummary = exporter.getLastFinishedHistogramItem("testSummary", 4);
assertNotNull(testSummary);
assertThat(testSummary)
.hasDescription("This is a test distribution summary")
diff --git a/implementation/observation-otel-bridge/pom.xml b/implementation/observation-otel-bridge/pom.xml
index c9daba79..f5fe46cf 100644
--- a/implementation/observation-otel-bridge/pom.xml
+++ b/implementation/observation-otel-bridge/pom.xml
@@ -12,14 +12,6 @@
smallrye-opentelemetry-observation-otel-bridge
SmallRye OpenTelemetry: Observation to OpenTelemetry bridge
-
- 1.0.2
- ${project.build.sourceDirectory}
- .*
- ${project.build.directory}/observation-docs/
-
-
-
io.smallrye.opentelemetry
diff --git a/implementation/observation-otel-bridge/src/main/java/io/smallrye/opentelemetry/instrumentation/observation/cdi/ObservationExtension.java b/implementation/observation-otel-bridge/src/main/java/io/smallrye/opentelemetry/instrumentation/observation/cdi/ObservationExtension.java
index b55b0857..bb2576fb 100644
--- a/implementation/observation-otel-bridge/src/main/java/io/smallrye/opentelemetry/instrumentation/observation/cdi/ObservationExtension.java
+++ b/implementation/observation-otel-bridge/src/main/java/io/smallrye/opentelemetry/instrumentation/observation/cdi/ObservationExtension.java
@@ -20,7 +20,6 @@
import jakarta.enterprise.inject.spi.Extension;
import jakarta.enterprise.util.Nonbinding;
-import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.annotation.Observed;
public class ObservationExtension implements Extension {
@@ -28,7 +27,7 @@ public void beforeBeanDiscovery(@Observes BeforeBeanDiscovery beforeBeanDiscover
beforeBeanDiscovery.addInterceptorBinding(
new ObservedAnnotatedType(beanManager.createAnnotatedType(Observed.class)));
- beforeBeanDiscovery.addAnnotatedType(ObservationRegistry.class, ObservationRegistry.class.getName());
+ beforeBeanDiscovery.addAnnotatedType(ObservationRegistryProducer.class, ObservationRegistryProducer.class.getName());
}
public void afterBeanDiscovery(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager) {
diff --git a/implementation/observation-otel-bridge/src/main/java/io/smallrye/opentelemetry/instrumentation/observation/ObservationRegistryProducer.java b/implementation/observation-otel-bridge/src/main/java/io/smallrye/opentelemetry/instrumentation/observation/cdi/ObservationRegistryProducer.java
similarity index 57%
rename from implementation/observation-otel-bridge/src/main/java/io/smallrye/opentelemetry/instrumentation/observation/ObservationRegistryProducer.java
rename to implementation/observation-otel-bridge/src/main/java/io/smallrye/opentelemetry/instrumentation/observation/cdi/ObservationRegistryProducer.java
index 78361244..5b7e0568 100644
--- a/implementation/observation-otel-bridge/src/main/java/io/smallrye/opentelemetry/instrumentation/observation/ObservationRegistryProducer.java
+++ b/implementation/observation-otel-bridge/src/main/java/io/smallrye/opentelemetry/instrumentation/observation/cdi/ObservationRegistryProducer.java
@@ -1,4 +1,4 @@
-package io.smallrye.opentelemetry.instrumentation.observation;
+package io.smallrye.opentelemetry.instrumentation.observation.cdi;
import jakarta.enterprise.inject.Produces;
import jakarta.inject.Inject;
@@ -13,6 +13,7 @@
import io.smallrye.opentelemetry.instrumentation.observation.handler.OpenTelemetryObservationHandler;
import io.smallrye.opentelemetry.instrumentation.observation.handler.PropagatingReceiverTracingObservationHandler;
import io.smallrye.opentelemetry.instrumentation.observation.handler.PropagatingSenderTracingObservationHandler;
+import io.smallrye.opentelemetry.instrumentation.observation.handler.TracingAwareMeterObservationHandler;
@Singleton
public class ObservationRegistryProducer {
@@ -23,15 +24,12 @@ public class ObservationRegistryProducer {
OpenTelemetry openTelemetry;
@Inject
- OpenTelemetryObservationHandler openTelemetryObservationHandler;
-
- @Inject
- MeterRegistry registry;
+ MeterRegistry meterRegistry;
@Produces
@Singleton
public ObservationRegistry registry() {
- ObservationRegistry observationRegistry = ObservationRegistry.create();
+ final ObservationRegistry observationRegistry = ObservationRegistry.create();
observationRegistry.observationConfig()
// .observationFilter(new CloudObservationFilter()) // Where global filters go
@@ -41,9 +39,16 @@ public ObservationRegistry registry() {
openTelemetry.getPropagators().getTextMapPropagator()),
new PropagatingReceiverTracingObservationHandler(tracer,
openTelemetry.getPropagators().getTextMapPropagator()),
- // new TracingAwareMeterObservationHandler(tracer) // For exemplars... Maybe not be needed
- openTelemetryObservationHandler))
- .observationHandler(new DefaultMeterObservationHandler(registry));
+ new OpenTelemetryObservationHandler(tracer)))
+ // todo duplicate the tracer strategy for baggage, adding a condition to bypass when no baggage is present
+ // todo just implement the receiver and sender handlers
+ // todo. Alternatively we can split in 2 the tracing handlers, one to create spans (in the current .observationHandler(new ObservationHandler.FirstMatchingCompositeObservationHandler )
+ // todo. A new .observationHandler bloc to process the baggage on the receiver side.
+ // todo. Another to propagate the context in a new .observationHandler )
+ // todo. We assume on the receiver side we open and close the baggage once because it should have just 1 scope app wide and the
+ // todo. user will use the baggage api itself. We are just making sure we don't break the propagation to the user.
+ .observationHandler(
+ new TracingAwareMeterObservationHandler(new DefaultMeterObservationHandler(meterRegistry), tracer));
// .observationHandler(new PrintOutHandler()) // Can be implemented for debugging. Other handlers for future frameworks can also be added.
return observationRegistry;
}
diff --git a/implementation/observation-otel-bridge/src/main/java/io/smallrye/opentelemetry/instrumentation/observation/handler/AbstractTracingObservationHandler.java b/implementation/observation-otel-bridge/src/main/java/io/smallrye/opentelemetry/instrumentation/observation/handler/AbstractTracingObservationHandler.java
index 99ee4eb0..dec1c05c 100644
--- a/implementation/observation-otel-bridge/src/main/java/io/smallrye/opentelemetry/instrumentation/observation/handler/AbstractTracingObservationHandler.java
+++ b/implementation/observation-otel-bridge/src/main/java/io/smallrye/opentelemetry/instrumentation/observation/handler/AbstractTracingObservationHandler.java
@@ -3,6 +3,8 @@
import static io.opentelemetry.semconv.SemanticAttributes.NET_SOCK_PEER_ADDR;
import static io.opentelemetry.semconv.SemanticAttributes.NET_SOCK_PEER_PORT;
import static io.opentelemetry.semconv.SemanticAttributes.PEER_SERVICE;
+import static io.smallrye.opentelemetry.instrumentation.observation.handler.HandlerUtil.HIGH_CARD_ATTRIBUTES;
+import static io.smallrye.opentelemetry.instrumentation.observation.handler.HandlerUtil.LOW_CARD_ATTRIBUTES;
import java.net.URI;
import java.util.logging.Logger;
@@ -108,16 +110,37 @@ protected Span getParentSpan(T context) {
return null;
}
+ @SuppressWarnings("unchecked")
protected void tagSpan(T context, Span span) {
+ final Attributes highCardAttributes = context.get(HIGH_CARD_ATTRIBUTES);
+ setOtelAttributes(span, highCardAttributes);
+
+ final Attributes lowCardAttributes = context.get(LOW_CARD_ATTRIBUTES);
+ setOtelAttributes(span, lowCardAttributes);
+
for (KeyValue keyValue : context.getAllKeyValues()) {
if (!keyValue.getKey().equalsIgnoreCase("ERROR")) {
span.setAttribute(keyValue.getKey(), keyValue.getValue());
+
} else {
span.recordException(new RuntimeException(keyValue.getValue()));
}
}
}
+ private void setOtelAttributes(Span span, Attributes contextAttributes) {
+ if (contextAttributes != null) {
+ contextAttributes.forEach((key, value) -> {
+ // FIXME this is a bit of a hack because KeyValue only allows String values
+ if (key.getKey().equalsIgnoreCase("ERROR")) {
+ span.recordException(new RuntimeException(value.toString()));
+ } else {
+ span.setAttribute((AttributeKey
+
+ io.smallrye.opentelemetry
+ smallrye-opentelemetry-observation-otel-bridge
+ ${project.version}
+
+
+ io.smallrye.opentelemetry
+ smallrye-opentelemetry-rest-observation
+ ${project.version}
+
io.vertx
@@ -196,6 +206,7 @@
implementation/rest
implementation/micrometer-otel-bridge
implementation/observation-otel-bridge
+ implementation/rest-observation
test
diff --git a/test/src/main/java/io/smallrye/opentelemetry/test/InMemoryExporter.java b/test/src/main/java/io/smallrye/opentelemetry/test/InMemoryExporter.java
index 9630f4c2..f9d00496 100644
--- a/test/src/main/java/io/smallrye/opentelemetry/test/InMemoryExporter.java
+++ b/test/src/main/java/io/smallrye/opentelemetry/test/InMemoryExporter.java
@@ -1,15 +1,22 @@
package io.smallrye.opentelemetry.test;
+import static io.opentelemetry.semconv.HttpAttributes.HTTP_REQUEST_METHOD;
+import static io.opentelemetry.semconv.HttpAttributes.HTTP_RESPONSE_STATUS_CODE;
import static io.opentelemetry.semconv.HttpAttributes.HTTP_ROUTE;
+import static io.opentelemetry.semconv.UrlAttributes.URL_PATH;
import static java.util.Comparator.comparingLong;
import static java.util.concurrent.TimeUnit.SECONDS;
+import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Collection;
+import java.util.Comparator;
import java.util.List;
+import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -32,6 +39,11 @@
@ApplicationScoped
public class InMemoryExporter {
+
+ private static final List KEY_COMPONENTS = List.of(HTTP_REQUEST_METHOD.getKey(),
+ HTTP_ROUTE.getKey(),
+ HTTP_RESPONSE_STATUS_CODE.getKey());
+
@Inject
InMemorySpanExporter spanExporter;
@Inject
@@ -41,7 +53,9 @@ public class InMemoryExporter {
public List getFinishedSpanItems(final int count) {
assertSpanCount(count);
- return spanExporter.getFinishedSpanItems().stream().sorted(comparingLong(SpanData::getStartEpochNanos).reversed())
+ return spanExporter.getFinishedSpanItems().stream()
+ .sorted(comparingLong(SpanData::getStartEpochNanos)
+ .reversed())
.collect(toList());
}
@@ -54,27 +68,41 @@ public MetricData getFinishedMetricItem(final String name) {
return metrics(name).get(1);
}
- public MetricData getFinishedMetricItem(final String name, final String route) {
- assertMetricsAtLeast(1, name, route);
- return metrics(name).route(route).get(1);
+ public List getFinishedMetricItemList(final String name) {
+ assertMetricsAtLeast(1, name);
+ return metrics(name).metricData.collect(Collectors.toList());
}
public void assertMetricsAtLeast(final int count, final String name) {
- await().atMost(5, SECONDS).untilAsserted(() -> assertTrue(metrics(name).count() >= count));
+ await().atMost(5, SECONDS).untilAsserted(() -> assertTrue(metrics(name).countDataPoints() >= count));
}
public void assertMetricsAtLeast(final int count, final String name, final String route) {
- await().atMost(5, SECONDS).untilAsserted(() -> assertTrue(metrics(name).route(route).count() >= count));
+ await().atMost(5, SECONDS).untilAsserted(() -> assertTrue(metrics(name).route(route).countDataPoints() >= count,
+ "Metrics count: " + metrics(name).route(route).countDataPoints() +
+ ". Found metrics " + metrics(name).route(route).metricData
+ .map(md -> md.getData().getPoints().stream()
+ .map(pointData -> md.getName() + " : " + pointData.getAttributes().get(HTTP_ROUTE))
+ .collect(toList()))
+ .collect(toList())));
}
- public MetricData getFinishedHistogramItem(final String name, final int count) {
- await().atMost(5, SECONDS).untilAsserted(() -> assertEquals(count, histogram(name).count()));
+ public MetricData getFinishedHistogramItemByPoints(final String name, final int count) {
+ await().atMost(5, SECONDS).untilAsserted(() -> assertEquals(count, histogram(name).countDataPoints()));
return histogram(name).get(count);
}
- public MetricData getFinishedHistogramItem(final String name, final String route, final int count) {
- await().atMost(5, SECONDS).untilAsserted(() -> assertEquals(count, histogram(name).route(route).count()));
- return histogram(name).route(route).get(count);
+ public MetricData getLastFinishedHistogramItem(final String name, final int count) {
+ await().atMost(5, SECONDS).untilAsserted(() -> assertTrue(histogram(name).getAll().stream().count() >= count));
+ List metricDataList = histogram(name).metricData.collect(toList());
+ return metricDataList.get(metricDataList.size() - 1); // last added entry
+ }
+
+ public MetricData getLastFinishedHistogramItem(final String name, final String route, final int count) {
+ await().atMost(5, SECONDS)
+ .untilAsserted(() -> assertTrue(histogram(name).route(route).getAll().stream().count() >= count));
+ List metricDataList = histogram(name).route(route).metricData.collect(toList());
+ return metricDataList.get(metricDataList.size() - 1); // last added entry
}
public void reset() {
@@ -83,6 +111,24 @@ public void reset() {
logRecordExporter.reset();
}
+ // Not for
+ public Map getMostRecentPointsMap(List finishedMetricItems) {
+ // get last metric item
+ Map collect = finishedMetricItems.get(finishedMetricItems.size() - 1).getHistogramData()
+ .getPoints().stream()
+ .collect(toMap(
+ pointData -> pointData.getAttributes().asMap().entrySet().stream()
+ //valid attributes for the resulting map key
+ .filter(entry -> KEY_COMPONENTS.contains(entry.getKey().getKey()))
+ // ensure order
+ .sorted(Comparator.comparing(o -> o.getKey().getKey()))
+ // build key
+ .map(entry -> entry.getKey().getKey() + ":" + entry.getValue().toString())
+ .collect(joining(",")),
+ pointData -> pointData));
+ return collect;
+ }
+
private class MetricDataFilter {
private Stream metricData;
@@ -148,15 +194,70 @@ public boolean test(final PointData pointData) {
return this;
}
- int count() {
- return metricData.map(this::count)
+ MetricDataFilter path(final String path) {
+ metricData = metricData.map(new Function() {
+ @Override
+ public MetricData apply(final MetricData metricData) {
+ return new MetricData() {
+ @Override
+ public Resource getResource() {
+ return metricData.getResource();
+ }
+
+ @Override
+ public InstrumentationScopeInfo getInstrumentationScopeInfo() {
+ return metricData.getInstrumentationScopeInfo();
+ }
+
+ @Override
+ public String getName() {
+ return metricData.getName();
+ }
+
+ @Override
+ public String getDescription() {
+ return metricData.getDescription();
+ }
+
+ @Override
+ public String getUnit() {
+ return metricData.getUnit();
+ }
+
+ @Override
+ public MetricDataType getType() {
+ return metricData.getType();
+ }
+
+ @Override
+ public Data> getData() {
+ return new Data() {
+ @Override
+ public Collection getPoints() {
+ return metricData.getData().getPoints().stream().filter(new Predicate() {
+ @Override
+ public boolean test(final PointData pointData) {
+ String value = pointData.getAttributes().get(URL_PATH);
+ return value != null && value.equals(path);
+ }
+ }).collect(Collectors.toSet());
+ }
+ };
+ }
+ };
+ }
+ });
+ return this;
+ }
+
+ int countDataPoints() {
+ return metricData.map(metricData1 -> countPoints(metricData1))
.mapToInt(Integer::intValue)
- .max()
- .orElse(0);
+ .sum();
}
MetricData get(final int count) {
- return metricData.filter(metricData -> count(metricData) >= count)
+ return metricData.filter(metricData -> countPoints(metricData) >= count)
.max(comparingLong(metricData -> metricData.getData().getPoints()
.stream()
.map(PointData::getEpochNanos)
@@ -165,7 +266,11 @@ MetricData get(final int count) {
.orElseThrow();
}
- int count(final MetricData metricData) {
+ List getAll() {
+ return metricData.collect(Collectors.toList());
+ }
+
+ int countPoints(final MetricData metricData) {
return metricData.getData().getPoints().size();
}
}
@@ -177,7 +282,7 @@ private MetricDataFilter metrics(final String name) {
private MetricDataFilter histogram(final String name) {
return new MetricDataFilter(name) {
@Override
- int count(final MetricData metricData) {
+ int countPoints(final MetricData metricData) {
return metricData.getData().getPoints().stream()
.map((o -> ((HistogramPointData) o).getCount()))
.mapToInt(Long::intValue)
diff --git a/testsuite/extra/src/test/java/io/smallrye/opentelemetry/extra/test/metrics/rest/RestMetricsTest.java b/testsuite/extra/src/test/java/io/smallrye/opentelemetry/extra/test/metrics/rest/RestMetricsTest.java
index 41c20a1a..741f3acc 100644
--- a/testsuite/extra/src/test/java/io/smallrye/opentelemetry/extra/test/metrics/rest/RestMetricsTest.java
+++ b/testsuite/extra/src/test/java/io/smallrye/opentelemetry/extra/test/metrics/rest/RestMetricsTest.java
@@ -62,7 +62,8 @@ void metricAttributes() {
given().get("/span/2").then().statusCode(HTTP_OK);
given().get("/span/2").then().statusCode(HTTP_OK);
- MetricData metricsSpan = exporter.getFinishedHistogramItem("http.server.request.duration", url.getPath() + "span", 1);
+ MetricData metricsSpan = exporter.getLastFinishedHistogramItem("http.server.request.duration", url.getPath() + "span",
+ 1);
assertEquals("Duration of HTTP server requests.", metricsSpan.getDescription());
assertEquals(HISTOGRAM, metricsSpan.getType());
assertEquals(1, metricsSpan.getData().getPoints().size());
@@ -77,7 +78,7 @@ void metricAttributes() {
assertTrue(pointSpan.getEpochNanos() > 0);
assertEquals(1, pointSpan.getCount());
- MetricData metricsSpanName = exporter.getFinishedHistogramItem("http.server.request.duration",
+ MetricData metricsSpanName = exporter.getLastFinishedHistogramItem("http.server.request.duration",
url.getPath() + "span/{name}", 3);
assertEquals("Duration of HTTP server requests.", metricsSpanName.getDescription());
assertEquals(HISTOGRAM, metricsSpanName.getType());
diff --git a/testsuite/extra/src/test/java/io/smallrye/opentelemetry/extra/test/trace/rest/RestClientSpanTest.java b/testsuite/extra/src/test/java/io/smallrye/opentelemetry/extra/test/trace/rest/RestClientSpanTest.java
index 91909d37..9a38d4e4 100644
--- a/testsuite/extra/src/test/java/io/smallrye/opentelemetry/extra/test/trace/rest/RestClientSpanTest.java
+++ b/testsuite/extra/src/test/java/io/smallrye/opentelemetry/extra/test/trace/rest/RestClientSpanTest.java
@@ -50,6 +50,7 @@
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -60,6 +61,7 @@
import io.opentelemetry.instrumentation.annotations.WithSpan;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.semconv.ErrorAttributes;
+import io.smallrye.opentelemetry.extra.test.AttributeKeysStability;
import io.smallrye.opentelemetry.test.InMemoryExporter;
@ExtendWith(ArquillianExtension.class)
@@ -103,9 +105,9 @@ void span() {
SpanData client = spans.get(1);
assertEquals(CLIENT, client.getKind());
assertEquals("GET", client.getName());
- assertEquals(HTTP_OK, get(server, HTTP_RESPONSE_STATUS_CODE));
- assertEquals(HttpMethod.GET, get(client, HTTP_REQUEST_METHOD));
- assertEquals(url.toString() + "span", get(client, URL_FULL));
+ Assertions.assertEquals(HTTP_OK, AttributeKeysStability.get(server, HTTP_RESPONSE_STATUS_CODE));
+ Assertions.assertEquals(HttpMethod.GET, AttributeKeysStability.get(client, HTTP_REQUEST_METHOD));
+ Assertions.assertEquals(url.toString() + "span", AttributeKeysStability.get(client, URL_FULL));
assertEquals(client.getTraceId(), server.getTraceId());
assertEquals(server.getParentSpanId(), client.getSpanId());
@@ -121,18 +123,18 @@ void spanName() {
SpanData server = spans.get(0);
assertEquals(SERVER, server.getKind());
assertEquals(HttpMethod.GET + " " + url.getPath() + "span/{name}", server.getName());
- assertEquals(HTTP_OK, get(server, HTTP_RESPONSE_STATUS_CODE));
- assertEquals(HttpMethod.GET, get(server, HTTP_REQUEST_METHOD));
- assertEquals("http", get(server, URL_SCHEME));
- assertEquals(url.getPath() + "span/1", get(server, URL_PATH));
- assertEquals(url.getHost(), get(server, SERVER_ADDRESS));
+ Assertions.assertEquals(HTTP_OK, AttributeKeysStability.get(server, HTTP_RESPONSE_STATUS_CODE));
+ Assertions.assertEquals(HttpMethod.GET, AttributeKeysStability.get(server, HTTP_REQUEST_METHOD));
+ Assertions.assertEquals("http", AttributeKeysStability.get(server, URL_SCHEME));
+ Assertions.assertEquals(url.getPath() + "span/1", AttributeKeysStability.get(server, URL_PATH));
+ Assertions.assertEquals(url.getHost(), AttributeKeysStability.get(server, SERVER_ADDRESS));
SpanData client = spans.get(1);
assertEquals(CLIENT, client.getKind());
assertEquals("GET", client.getName());
- assertEquals(HTTP_OK, get(client, HTTP_RESPONSE_STATUS_CODE));
- assertEquals(HttpMethod.GET, get(client, HTTP_REQUEST_METHOD));
- assertEquals(url.toString() + "span/1", get(client, URL_FULL));
+ Assertions.assertEquals(HTTP_OK, AttributeKeysStability.get(client, HTTP_RESPONSE_STATUS_CODE));
+ Assertions.assertEquals(HttpMethod.GET, AttributeKeysStability.get(client, HTTP_REQUEST_METHOD));
+ Assertions.assertEquals(url.toString() + "span/1", AttributeKeysStability.get(client, URL_FULL));
assertEquals(server.getTraceId(), client.getTraceId());
assertEquals(server.getParentSpanId(), client.getSpanId());
@@ -158,9 +160,9 @@ void spanNameQuery() {
SpanData client = spans.get(1);
assertEquals(CLIENT, client.getKind());
assertEquals("GET", client.getName());
- assertEquals(HTTP_OK, get(client, HTTP_RESPONSE_STATUS_CODE));
- assertEquals(HttpMethod.GET, get(client, HTTP_REQUEST_METHOD));
- assertEquals(url.toString() + "span/1?query=query", get(client, URL_FULL));
+ Assertions.assertEquals(HTTP_OK, AttributeKeysStability.get(client, HTTP_RESPONSE_STATUS_CODE));
+ Assertions.assertEquals(HttpMethod.GET, AttributeKeysStability.get(client, HTTP_REQUEST_METHOD));
+ Assertions.assertEquals(url.toString() + "span/1?query=query", AttributeKeysStability.get(client, URL_FULL));
assertEquals(client.getTraceId(), server.getTraceId());
assertEquals(server.getParentSpanId(), client.getSpanId());
@@ -220,18 +222,18 @@ void spanChild() {
SpanData server = spans.get(1);
assertEquals(SERVER, server.getKind());
assertEquals(HttpMethod.GET + " " + url.getPath() + "span/child", server.getName());
- assertEquals(HTTP_OK, get(server, HTTP_RESPONSE_STATUS_CODE));
- assertEquals(HttpMethod.GET, get(server, HTTP_REQUEST_METHOD));
- assertEquals("http", get(server, URL_SCHEME));
- assertEquals(url.getPath() + "span/child", get(server, URL_PATH));
- assertEquals(url.getHost(), get(server, SERVER_ADDRESS));
+ Assertions.assertEquals(HTTP_OK, AttributeKeysStability.get(server, HTTP_RESPONSE_STATUS_CODE));
+ Assertions.assertEquals(HttpMethod.GET, AttributeKeysStability.get(server, HTTP_REQUEST_METHOD));
+ Assertions.assertEquals("http", AttributeKeysStability.get(server, URL_SCHEME));
+ Assertions.assertEquals(url.getPath() + "span/child", AttributeKeysStability.get(server, URL_PATH));
+ Assertions.assertEquals(url.getHost(), AttributeKeysStability.get(server, SERVER_ADDRESS));
SpanData client = spans.get(2);
assertEquals(CLIENT, client.getKind());
assertEquals("GET", client.getName());
- assertEquals(HTTP_OK, get(client, HTTP_RESPONSE_STATUS_CODE));
- assertEquals(HttpMethod.GET, get(client, HTTP_REQUEST_METHOD));
- assertEquals(url.toString() + "span/child", get(client, URL_FULL));
+ Assertions.assertEquals(HTTP_OK, AttributeKeysStability.get(client, HTTP_RESPONSE_STATUS_CODE));
+ Assertions.assertEquals(HttpMethod.GET, AttributeKeysStability.get(client, HTTP_REQUEST_METHOD));
+ Assertions.assertEquals(url.toString() + "span/child", AttributeKeysStability.get(client, URL_FULL));
assertEquals(client.getTraceId(), internal.getTraceId());
assertEquals(client.getTraceId(), server.getTraceId());
@@ -249,19 +251,19 @@ void spanCurrent() {
SpanData server = spans.get(0);
assertEquals(SERVER, server.getKind());
assertEquals(HttpMethod.GET + " " + url.getPath() + "span/current", server.getName());
- assertEquals(HTTP_OK, get(server, HTTP_RESPONSE_STATUS_CODE));
- assertEquals(HttpMethod.GET, get(server, HTTP_REQUEST_METHOD));
- assertEquals("http", get(server, URL_SCHEME));
- assertEquals(url.getPath() + "span/current", get(server, URL_PATH));
- assertEquals(url.getHost(), get(server, SERVER_ADDRESS));
+ Assertions.assertEquals(HTTP_OK, AttributeKeysStability.get(server, HTTP_RESPONSE_STATUS_CODE));
+ Assertions.assertEquals(HttpMethod.GET, AttributeKeysStability.get(server, HTTP_REQUEST_METHOD));
+ Assertions.assertEquals("http", AttributeKeysStability.get(server, URL_SCHEME));
+ Assertions.assertEquals(url.getPath() + "span/current", AttributeKeysStability.get(server, URL_PATH));
+ Assertions.assertEquals(url.getHost(), AttributeKeysStability.get(server, SERVER_ADDRESS));
assertEquals("tck.current.value", server.getAttributes().get(stringKey("tck.current.key")));
SpanData client = spans.get(1);
assertEquals(CLIENT, client.getKind());
assertEquals("GET", client.getName());
- assertEquals(HTTP_OK, get(client, HTTP_RESPONSE_STATUS_CODE));
- assertEquals(HttpMethod.GET, get(client, HTTP_REQUEST_METHOD));
- assertEquals(url.toString() + "span/current", get(client, URL_FULL));
+ Assertions.assertEquals(HTTP_OK, AttributeKeysStability.get(client, HTTP_RESPONSE_STATUS_CODE));
+ Assertions.assertEquals(HttpMethod.GET, AttributeKeysStability.get(client, HTTP_REQUEST_METHOD));
+ Assertions.assertEquals(url.toString() + "span/current", AttributeKeysStability.get(client, URL_FULL));
assertEquals(client.getTraceId(), server.getTraceId());
assertEquals(server.getParentSpanId(), client.getSpanId());
@@ -282,18 +284,18 @@ void spanNew() {
SpanData server = spans.get(1);
assertEquals(SERVER, server.getKind());
assertEquals(HttpMethod.GET + " " + url.getPath() + "span/new", server.getName());
- assertEquals(HTTP_OK, get(server, HTTP_RESPONSE_STATUS_CODE));
- assertEquals(HttpMethod.GET, get(server, HTTP_REQUEST_METHOD));
- assertEquals("http", get(server, URL_SCHEME));
- assertEquals(url.getPath() + "span/new", get(server, URL_PATH));
- assertEquals(url.getHost(), get(server, SERVER_ADDRESS));
+ Assertions.assertEquals(HTTP_OK, AttributeKeysStability.get(server, HTTP_RESPONSE_STATUS_CODE));
+ Assertions.assertEquals(HttpMethod.GET, AttributeKeysStability.get(server, HTTP_REQUEST_METHOD));
+ Assertions.assertEquals("http", AttributeKeysStability.get(server, URL_SCHEME));
+ Assertions.assertEquals(url.getPath() + "span/new", AttributeKeysStability.get(server, URL_PATH));
+ Assertions.assertEquals(url.getHost(), AttributeKeysStability.get(server, SERVER_ADDRESS));
SpanData client = spans.get(2);
assertEquals(CLIENT, client.getKind());
assertEquals("GET", client.getName());
- assertEquals(HTTP_OK, get(client, HTTP_RESPONSE_STATUS_CODE));
- assertEquals(HttpMethod.GET, get(client, HTTP_REQUEST_METHOD));
- assertEquals(url.toString() + "span/new", get(client, URL_FULL));
+ Assertions.assertEquals(HTTP_OK, AttributeKeysStability.get(client, HTTP_RESPONSE_STATUS_CODE));
+ Assertions.assertEquals(HttpMethod.GET, AttributeKeysStability.get(client, HTTP_REQUEST_METHOD));
+ Assertions.assertEquals(url.toString() + "span/new", AttributeKeysStability.get(client, URL_FULL));
assertEquals(client.getTraceId(), internal.getTraceId());
assertEquals(client.getTraceId(), server.getTraceId());
diff --git a/testsuite/extra/src/test/java/io/smallrye/opentelemetry/extra/test/trace/rest/RestSpanTest.java b/testsuite/extra/src/test/java/io/smallrye/opentelemetry/extra/test/trace/rest/RestSpanTest.java
index cccf1b63..a845cb57 100644
--- a/testsuite/extra/src/test/java/io/smallrye/opentelemetry/extra/test/trace/rest/RestSpanTest.java
+++ b/testsuite/extra/src/test/java/io/smallrye/opentelemetry/extra/test/trace/rest/RestSpanTest.java
@@ -46,6 +46,7 @@
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -54,6 +55,7 @@
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.semconv.ErrorAttributes;
import io.smallrye.opentelemetry.api.OpenTelemetryConfig;
+import io.smallrye.opentelemetry.extra.test.AttributeKeysStability;
import io.smallrye.opentelemetry.test.InMemoryExporter;
@ExtendWith(ArquillianExtension.class)
@@ -115,8 +117,8 @@ void spanName() {
SpanData span = spanItems.get(0);
assertEquals(SERVER, span.getKind());
assertEquals(HttpMethod.GET + " " + url.getPath() + "span/{name}", span.getName());
- assertEquals(HTTP_OK, get(span, HTTP_RESPONSE_STATUS_CODE));
- assertEquals(HttpMethod.GET, get(span, HTTP_REQUEST_METHOD));
+ Assertions.assertEquals(HTTP_OK, AttributeKeysStability.get(span, HTTP_RESPONSE_STATUS_CODE));
+ Assertions.assertEquals(HttpMethod.GET, AttributeKeysStability.get(span, HTTP_REQUEST_METHOD));
}
@Test
@@ -178,19 +180,19 @@ void spanPost() {
assertEquals(HttpMethod.POST + " " + url.getPath() + "span", span.getName());
// Common Attributes
- assertEquals(HttpMethod.POST, get(span, HTTP_REQUEST_METHOD));
- assertEquals(HTTP_OK, get(span, HTTP_RESPONSE_STATUS_CODE));
+ Assertions.assertEquals(HttpMethod.POST, AttributeKeysStability.get(span, HTTP_REQUEST_METHOD));
+ Assertions.assertEquals(HTTP_OK, AttributeKeysStability.get(span, HTTP_RESPONSE_STATUS_CODE));
assertNotNull(span.getAttributes().get(USER_AGENT_ORIGINAL));
assertNull(span.getAttributes().get(NETWORK_LOCAL_ADDRESS));
assertNull(span.getAttributes().get(NETWORK_LOCAL_PORT));
// Server Attributes
- assertEquals("http", get(span, URL_SCHEME));
- assertEquals(url.getPath() + "span", get(span, URL_PATH));
+ Assertions.assertEquals("http", AttributeKeysStability.get(span, URL_SCHEME));
+ Assertions.assertEquals(url.getPath() + "span", AttributeKeysStability.get(span, URL_PATH));
assertEquals(url.getPath() + "span", span.getAttributes().get(HTTP_ROUTE));
- assertNull(get(span, CLIENT_ADDRESS));
- assertEquals(url.getHost(), get(span, SERVER_ADDRESS));
- assertEquals(url.getPort(), get(span, SERVER_PORT));
+ assertNull(AttributeKeysStability.get(span, CLIENT_ADDRESS));
+ Assertions.assertEquals(url.getHost(), AttributeKeysStability.get(span, SERVER_ADDRESS));
+ Assertions.assertEquals(url.getPort(), AttributeKeysStability.get(span, SERVER_PORT));
}
@Test
@@ -202,8 +204,8 @@ void subResource() {
SpanData span = spanItems.get(0);
assertEquals(SERVER, span.getKind());
assertEquals(HttpMethod.GET, span.getName());
- assertEquals(HTTP_OK, get(span, HTTP_RESPONSE_STATUS_CODE));
- assertEquals(HttpMethod.GET, get(span, HTTP_REQUEST_METHOD));
+ Assertions.assertEquals(HTTP_OK, AttributeKeysStability.get(span, HTTP_RESPONSE_STATUS_CODE));
+ Assertions.assertEquals(HttpMethod.GET, AttributeKeysStability.get(span, HTTP_REQUEST_METHOD));
}
@Path("/")
diff --git a/testsuite/observation/pom.xml b/testsuite/observation/pom.xml
new file mode 100644
index 00000000..ad4b358d
--- /dev/null
+++ b/testsuite/observation/pom.xml
@@ -0,0 +1,94 @@
+
+
+ 4.0.0
+
+ io.smallrye.opentelemetry
+ smallrye-opentelemetry-testsuite
+ 2.8.2-SNAPSHOT
+
+
+ smallrye-opentelemetry-observation-otel-bridge-it
+ SmallRye OpenTelemetry: Test Suite for Observation to OpenTelemetry bridge
+
+
+
+ io.smallrye.opentelemetry
+ smallrye-opentelemetry-api
+
+
+ io.smallrye.opentelemetry
+ smallrye-opentelemetry-config
+ test
+
+
+ io.smallrye.opentelemetry
+ smallrye-opentelemetry-rest-observation
+ test
+
+
+ io.smallrye.opentelemetry
+ smallrye-opentelemetry-test
+ test
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+ io.rest-assured
+ rest-assured
+
+
+ org.awaitility
+ awaitility
+
+
+
+
+ org.jboss.arquillian.junit5
+ arquillian-junit5-container
+
+
+ io.smallrye.testing
+ smallrye-testing-tck-jetty
+
+
+ io.smallrye.config
+ smallrye-config
+
+
+ org.jboss.resteasy.microprofile
+ microprofile-rest-client
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ true
+
+
+
+ stable
+
+ test
+
+
+ false
+
+ http
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/ArquillianExtension.java b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/ArquillianExtension.java
new file mode 100644
index 00000000..5ce644a8
--- /dev/null
+++ b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/ArquillianExtension.java
@@ -0,0 +1,12 @@
+package io.smallrye.opentelemetry.observation.test;
+
+import org.jboss.arquillian.container.test.spi.client.deployment.ApplicationArchiveProcessor;
+import org.jboss.arquillian.core.spi.LoadableExtension;
+
+public class ArquillianExtension implements LoadableExtension {
+ @Override
+ public void register(ExtensionBuilder extensionBuilder) {
+ extensionBuilder.service(ApplicationArchiveProcessor.class, DeploymentProcessor.class);
+ extensionBuilder.observer(ArquillianLifecycle.class);
+ }
+}
diff --git a/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/ArquillianLifecycle.java b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/ArquillianLifecycle.java
new file mode 100644
index 00000000..b23ab327
--- /dev/null
+++ b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/ArquillianLifecycle.java
@@ -0,0 +1,33 @@
+package io.smallrye.opentelemetry.observation.test;
+
+import org.jboss.arquillian.container.spi.client.protocol.metadata.HTTPContext;
+import org.jboss.arquillian.container.spi.client.protocol.metadata.ProtocolMetaData;
+import org.jboss.arquillian.container.spi.client.protocol.metadata.Servlet;
+import org.jboss.arquillian.container.spi.event.container.AfterDeploy;
+import org.jboss.arquillian.container.spi.event.container.BeforeDeploy;
+import org.jboss.arquillian.core.api.Instance;
+import org.jboss.arquillian.core.api.annotation.Inject;
+import org.jboss.arquillian.core.api.annotation.Observes;
+import org.jboss.arquillian.test.spi.TestClass;
+
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.restassured.RestAssured;
+
+public class ArquillianLifecycle {
+ public void beforeDeploy(@Observes BeforeDeploy event, TestClass testClass) {
+ GlobalOpenTelemetry.resetForTest();
+ }
+
+ @Inject
+ Instance protocolMetadata;
+
+ public void afterDeploy(@Observes AfterDeploy event, TestClass testClass) {
+ HTTPContext httpContext = protocolMetadata.get().getContexts(HTTPContext.class).iterator().next();
+ Servlet servlet = httpContext.getServlets().iterator().next();
+ String baseUri = servlet.getBaseURI().toString();
+ TestConfigSource.configuration.put("baseUri", baseUri);
+
+ RestAssured.port = httpContext.getPort();
+ RestAssured.basePath = servlet.getBaseURI().getPath();
+ }
+}
diff --git a/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/AttributeKeysStability.java b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/AttributeKeysStability.java
new file mode 100644
index 00000000..7b3bc98c
--- /dev/null
+++ b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/AttributeKeysStability.java
@@ -0,0 +1,10 @@
+package io.smallrye.opentelemetry.observation.test;
+
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.sdk.trace.data.SpanData;
+
+public class AttributeKeysStability {
+ public static T get(SpanData spanData, AttributeKey key) {
+ return spanData.getAttributes().get((AttributeKey extends T>) key);
+ }
+}
diff --git a/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/DeploymentProcessor.java b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/DeploymentProcessor.java
new file mode 100644
index 00000000..6c21992b
--- /dev/null
+++ b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/DeploymentProcessor.java
@@ -0,0 +1,19 @@
+package io.smallrye.opentelemetry.observation.test;
+
+import org.eclipse.microprofile.config.spi.ConfigSource;
+import org.jboss.arquillian.container.test.spi.client.deployment.ApplicationArchiveProcessor;
+import org.jboss.arquillian.test.spi.TestClass;
+import org.jboss.shrinkwrap.api.Archive;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+
+public class DeploymentProcessor implements ApplicationArchiveProcessor {
+ @Override
+ public void process(Archive> archive, TestClass testClass) {
+ if (archive instanceof WebArchive) {
+ WebArchive war = (WebArchive) archive;
+ war.addAsServiceProvider(ConfigSource.class, TestConfigSource.class);
+ war.addClass(HttpServerAttributesFilter.class);
+ war.toString(true);
+ }
+ }
+}
diff --git a/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/HttpServerAttributesFilter.java b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/HttpServerAttributesFilter.java
new file mode 100644
index 00000000..05aecb5a
--- /dev/null
+++ b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/HttpServerAttributesFilter.java
@@ -0,0 +1,31 @@
+package io.smallrye.opentelemetry.observation.test;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.ws.rs.container.ContainerRequestContext;
+import jakarta.ws.rs.container.ContainerRequestFilter;
+import jakarta.ws.rs.container.ContainerResponseContext;
+import jakarta.ws.rs.container.ContainerResponseFilter;
+import jakarta.ws.rs.container.PreMatching;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.ext.Provider;
+
+import io.opentelemetry.semconv.SemanticAttributes;
+
+@Provider
+@PreMatching
+public class HttpServerAttributesFilter implements ContainerRequestFilter, ContainerResponseFilter {
+ @Context
+ HttpServletRequest httpServletRequest;
+
+ @Override
+ public void filter(final ContainerRequestContext request) {
+ String[] nameAndVersion = httpServletRequest.getProtocol().split("/");
+ request.setProperty(SemanticAttributes.NETWORK_PROTOCOL_NAME.getKey(), nameAndVersion[0]);
+ request.setProperty(SemanticAttributes.NETWORK_PROTOCOL_VERSION.getKey(), nameAndVersion[1]);
+ }
+
+ @Override
+ public void filter(final ContainerRequestContext request, final ContainerResponseContext response) {
+
+ }
+}
diff --git a/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/TestApplication.java b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/TestApplication.java
new file mode 100644
index 00000000..5096be62
--- /dev/null
+++ b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/TestApplication.java
@@ -0,0 +1,91 @@
+package io.smallrye.opentelemetry.observation.test;
+
+import static jakarta.ws.rs.core.MediaType.TEXT_PLAIN;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.inject.spi.CDI;
+import jakarta.inject.Inject;
+import jakarta.servlet.annotation.WebServlet;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.ws.rs.ApplicationPath;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.Response;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.junit5.ArquillianExtension;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(ArquillianExtension.class)
+class TestApplication {
+ @ArquillianResource
+ private URL url;
+ @Inject
+ HelloBean helloBean;
+
+ @Deployment
+ public static WebArchive createDeployment() {
+ return ShrinkWrap.create(WebArchive.class);
+ }
+
+ @Test
+ public void servlet() {
+ String uri = url.toExternalForm() + "servlet";
+ WebTarget echoEndpointTarget = ClientBuilder.newClient().target(uri);
+ Response response = echoEndpointTarget.request(TEXT_PLAIN).get();
+ assertEquals(response.getStatus(), HttpURLConnection.HTTP_OK);
+ }
+
+ @Test
+ public void rest() {
+ String uri = url.toExternalForm() + "rest";
+ WebTarget echoEndpointTarget = ClientBuilder.newClient().target(uri);
+ Response response = echoEndpointTarget.request(TEXT_PLAIN).get();
+ assertEquals(response.getStatus(), HttpURLConnection.HTTP_OK);
+ }
+
+ @WebServlet(urlPatterns = "/servlet")
+ public static class TestServlet extends HttpServlet {
+ @Override
+ protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws IOException {
+ resp.getWriter().write(CDI.current().select(HelloBean.class).get().hello());
+ }
+ }
+
+ @ApplicationPath("/rest")
+ public static class RestApplication extends Application {
+
+ }
+
+ @Path("/")
+ public static class TestEndpoint {
+ @Inject
+ HelloBean helloBean;
+
+ @GET
+ public String hello() {
+ return helloBean.hello();
+ }
+ }
+
+ @ApplicationScoped
+ public static class HelloBean {
+ public String hello() {
+ return "hello";
+ }
+ }
+}
diff --git a/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/TestConfigSource.java b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/TestConfigSource.java
new file mode 100644
index 00000000..7732f952
--- /dev/null
+++ b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/TestConfigSource.java
@@ -0,0 +1,26 @@
+package io.smallrye.opentelemetry.observation.test;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.microprofile.config.spi.ConfigSource;
+
+public class TestConfigSource implements ConfigSource {
+ static final Map configuration = new HashMap<>();
+
+ @Override
+ public Set getPropertyNames() {
+ return configuration.keySet();
+ }
+
+ @Override
+ public String getValue(final String propertyName) {
+ return configuration.get(propertyName);
+ }
+
+ @Override
+ public String getName() {
+ return TestConfigSource.class.getName();
+ }
+}
diff --git a/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/baggage/BaggageTest.java b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/baggage/BaggageTest.java
new file mode 100644
index 00000000..7862ebaf
--- /dev/null
+++ b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/baggage/BaggageTest.java
@@ -0,0 +1,73 @@
+package io.smallrye.opentelemetry.observation.test.baggage;
+
+import static java.net.HttpURLConnection.HTTP_OK;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.net.URL;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.ApplicationPath;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.Response;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.junit5.ArquillianExtension;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import io.opentelemetry.api.baggage.Baggage;
+import io.smallrye.opentelemetry.test.InMemoryExporter;
+
+@Disabled("baggage propagation not implemented")
+@ExtendWith(ArquillianExtension.class)
+class BaggageTest {
+ @Deployment
+ public static WebArchive createDeployment() {
+ return ShrinkWrap.create(WebArchive.class);
+ }
+
+ @ArquillianResource
+ URL url;
+ @Inject
+ InMemoryExporter spanExporter;
+
+ @BeforeEach
+ void setUp() {
+ spanExporter.reset();
+ }
+
+ @Test
+ void baggage() {
+ WebTarget target = ClientBuilder.newClient().target(url.toString() + "baggage");
+ Response response = target.request().header("baggage", "user=naruto").get();
+ assertEquals(HTTP_OK, response.getStatus());
+
+ spanExporter.getFinishedSpanItems(2);
+ }
+
+ @Path("/baggage")
+ public static class BaggageResource {
+ @Inject
+ Baggage baggage;
+
+ @GET
+ public Response baggage() {
+ assertEquals("naruto", baggage.getEntryValue("user"));
+ return Response.ok().build();
+ }
+ }
+
+ @ApplicationPath("/")
+ public static class RestApplication extends Application {
+
+ }
+}
diff --git a/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/metrics/cdi/GaugeCdiTest.java b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/metrics/cdi/GaugeCdiTest.java
new file mode 100644
index 00000000..984ad617
--- /dev/null
+++ b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/metrics/cdi/GaugeCdiTest.java
@@ -0,0 +1,63 @@
+package io.smallrye.opentelemetry.observation.test.metrics.cdi;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.junit5.ArquillianExtension;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.Meter;
+import io.smallrye.opentelemetry.test.InMemoryExporter;
+
+@ExtendWith(ArquillianExtension.class)
+public class GaugeCdiTest {
+ @Deployment
+ public static WebArchive createDeployment() {
+ return ShrinkWrap.create(WebArchive.class);
+ }
+
+ @Inject
+ MeterBean meterBean;
+
+ @Inject
+ InMemoryExporter exporter;
+
+ @BeforeEach
+ void setUp() {
+ exporter.reset();
+ }
+
+ @Test
+ void gauge() throws InterruptedException {
+ meterBean.getMeter()
+ .gaugeBuilder("jvm.memory.total")
+ .setDescription("Reports JVM memory usage. -> Manual")
+ .setUnit("byte")
+ .buildWithCallback(
+ result -> result.record(Runtime.getRuntime().totalMemory(), Attributes.empty()));
+ exporter.assertMetricsAtLeast(1, "jvm.memory.total");
+ }
+
+ @Test
+ void meter() {
+ assertNotNull(meterBean.getMeter());
+ }
+
+ @ApplicationScoped
+ public static class MeterBean {
+ @Inject
+ Meter meter;
+
+ public Meter getMeter() {
+ return meter;
+ }
+ }
+}
diff --git a/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/metrics/rest/RestMetricsTest.java b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/metrics/rest/RestMetricsTest.java
new file mode 100644
index 00000000..c68773af
--- /dev/null
+++ b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/metrics/rest/RestMetricsTest.java
@@ -0,0 +1,152 @@
+package io.smallrye.opentelemetry.observation.test.metrics.rest;
+
+import static io.opentelemetry.sdk.metrics.data.MetricDataType.HISTOGRAM;
+import static io.restassured.RestAssured.given;
+import static java.net.HttpURLConnection.HTTP_OK;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.everyItem;
+import static org.hamcrest.Matchers.hasProperty;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.ApplicationPath;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.Response;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.junit5.ArquillianExtension;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import io.opentelemetry.sdk.metrics.data.HistogramPointData;
+import io.opentelemetry.sdk.metrics.data.MetricData;
+import io.smallrye.opentelemetry.test.InMemoryExporter;
+
+@ExtendWith(ArquillianExtension.class)
+public class RestMetricsTest {
+
+ @Deployment
+ public static WebArchive createDeployment() {
+ return ShrinkWrap.create(WebArchive.class);
+ }
+
+ @ArquillianResource
+ URL url;
+
+ @Inject
+ InMemoryExporter metricExporter;
+
+ @AfterEach
+ void reset() {
+ // important, metrics continue to arrive after reset.
+ metricExporter.reset();
+ }
+
+ @Test
+ void metricAttributes() {
+ given().get("/span").then().statusCode(HTTP_OK);
+ given().get("/span/1").then().statusCode(HTTP_OK);
+ given().get("/span/2").then().statusCode(HTTP_OK);
+ given().get("/span/2").then().statusCode(HTTP_OK);
+ given().get("/span/2").then().statusCode(HTTP_OK);
+
+ metricExporter.assertMetricsAtLeast(1, "http.server", url.getPath() + "span");
+ metricExporter.assertMetricsAtLeast(4, "http.server", url.getPath() + "span/{name}");
+ List finishedMetricItems = metricExporter.getFinishedMetricItemList("http.server");
+
+ assertThat(finishedMetricItems, allOf(
+ everyItem(hasProperty("name", equalTo("http.server"))),
+ everyItem(hasProperty("type", equalTo(HISTOGRAM)))));
+
+ Map pointDataMap = metricExporter.getMostRecentPointsMap(finishedMetricItems);
+ System.out.println(pointDataMap);
+ assertEquals(1,
+ getCount(pointDataMap,
+ "http.request.method:GET,http.response.status_code:200,http.route:" + url.getPath() + "span"),
+ pointDataMap.keySet().stream()
+ .collect(Collectors.joining("**")));
+ assertTrue(
+ getCount(pointDataMap,
+ "http.request.method:GET,http.response.status_code:200,http.route:" + url.getPath()
+ + "span/{name}") >= 4, // we also get the hit from the other test.
+ pointDataMap.keySet().stream()
+ .collect(Collectors.joining("**")));
+ }
+
+ private long getCount(final Map pointDataMap, final String key) {
+ HistogramPointData histogramPointData = (HistogramPointData) pointDataMap.get(key);
+ if (histogramPointData == null) {
+ return 0;
+ }
+ return histogramPointData.getCount();
+ }
+
+ @Test
+ void metrics() {
+ given().get("/span/12").then().statusCode(HTTP_OK);
+ metricExporter.assertMetricsAtLeast(1, "queueSize");
+ metricExporter.assertMetricsAtLeast(1, "http.server.active.duration", url.getPath() + "span/{name}");
+ // metricExporter.assertMetricsAtLeast(1, "http.server.active_requests");
+ metricExporter.assertMetricsAtLeast(1, "processedSpans");
+ }
+
+ @Path("/")
+ public static class SpanResource {
+ @GET
+ @Path("/span")
+ public Response span() {
+ return Response.ok().build();
+ }
+
+ @GET
+ @Path("/span/{name}")
+ public Response spanName(@PathParam(value = "name") String name) {
+ return Response.ok().build();
+ }
+
+ @POST
+ @Path("/span")
+ public Response spanPost(String payload) {
+ return Response.ok(payload).build();
+ }
+
+ @Path("/sub/{id}")
+ public SubResource subResource(@PathParam("id") String id) {
+ return new SubResource(id);
+ }
+ }
+
+ public static class SubResource {
+ private final String id;
+
+ public SubResource(final String id) {
+ this.id = id;
+ }
+
+ @GET
+ public Response get() {
+ return Response.ok().build();
+ }
+ }
+
+ @ApplicationPath("/")
+ public static class RestApplication extends Application {
+
+ }
+}
diff --git a/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/trace/cdi/TracerTest.java b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/trace/cdi/TracerTest.java
new file mode 100644
index 00000000..4e53c417
--- /dev/null
+++ b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/trace/cdi/TracerTest.java
@@ -0,0 +1,41 @@
+package io.smallrye.opentelemetry.observation.test.trace.cdi;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.junit5.ArquillianExtension;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import io.opentelemetry.api.trace.Tracer;
+
+@ExtendWith(ArquillianExtension.class)
+class TracerTest {
+ @Deployment
+ public static WebArchive createDeployment() {
+ return ShrinkWrap.create(WebArchive.class);
+ }
+
+ @Inject
+ TracerBean tracerBean;
+
+ @Test
+ void tracer() {
+ assertNotNull(tracerBean.getTracer());
+ }
+
+ @ApplicationScoped
+ public static class TracerBean {
+ @Inject
+ Tracer tracer;
+
+ public Tracer getTracer() {
+ return tracer;
+ }
+ }
+}
diff --git a/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/trace/rest/ContextRootTest.java b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/trace/rest/ContextRootTest.java
new file mode 100644
index 00000000..90b8ee92
--- /dev/null
+++ b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/trace/rest/ContextRootTest.java
@@ -0,0 +1,71 @@
+package io.smallrye.opentelemetry.observation.test.trace.rest;
+
+import static io.opentelemetry.api.trace.SpanKind.SERVER;
+import static io.opentelemetry.semconv.SemanticAttributes.HTTP_ROUTE;
+import static io.restassured.RestAssured.given;
+import static java.net.HttpURLConnection.HTTP_OK;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.net.URL;
+import java.util.List;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.ApplicationPath;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.Response;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.junit5.ArquillianExtension;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import io.opentelemetry.sdk.trace.data.SpanData;
+import io.smallrye.opentelemetry.test.InMemoryExporter;
+
+@ExtendWith(ArquillianExtension.class)
+class ContextRootTest {
+ @Deployment
+ public static WebArchive createDeployment() {
+ return ShrinkWrap.create(WebArchive.class);
+ }
+
+ @ArquillianResource
+ URL url;
+ @Inject
+ InMemoryExporter spanExporter;
+
+ @BeforeEach
+ void setUp() {
+ spanExporter.reset();
+ }
+
+ @Test
+ void route() {
+ given().get("/application/resource/span").then().statusCode(HTTP_OK);
+
+ List spanItems = spanExporter.getFinishedSpanItems(1);
+ assertEquals(1, spanItems.size());
+ assertEquals(SERVER, spanItems.get(0).getKind());
+ assertEquals(url.getPath() + "application/resource/span", spanItems.get(0).getAttributes().get(HTTP_ROUTE));
+ }
+
+ @ApplicationPath("/application")
+ public static class RestApplication extends Application {
+
+ }
+
+ @Path("/resource")
+ public static class Resource {
+ @GET
+ @Path("/span")
+ public Response span() {
+ return Response.ok().build();
+ }
+ }
+}
diff --git a/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/trace/rest/RestClientSpanTest.java b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/trace/rest/RestClientSpanTest.java
new file mode 100644
index 00000000..f442a2f9
--- /dev/null
+++ b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/trace/rest/RestClientSpanTest.java
@@ -0,0 +1,376 @@
+package io.smallrye.opentelemetry.observation.test.trace.rest;
+
+import static io.opentelemetry.api.common.AttributeKey.stringKey;
+import static io.opentelemetry.api.trace.SpanKind.CLIENT;
+import static io.opentelemetry.api.trace.SpanKind.INTERNAL;
+import static io.opentelemetry.api.trace.SpanKind.SERVER;
+import static io.opentelemetry.semconv.HttpAttributes.HTTP_REQUEST_METHOD;
+import static io.opentelemetry.semconv.HttpAttributes.HTTP_RESPONSE_STATUS_CODE;
+import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS;
+import static io.opentelemetry.semconv.UrlAttributes.URL_FULL;
+import static io.opentelemetry.semconv.UrlAttributes.URL_PATH;
+import static io.opentelemetry.semconv.UrlAttributes.URL_SCHEME;
+import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
+import static java.net.HttpURLConnection.HTTP_OK;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.net.URL;
+import java.util.List;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.context.RequestScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.ApplicationPath;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.HttpMethod;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.Response;
+
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.junit5.ArquillianExtension;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.instrumentation.annotations.WithSpan;
+import io.opentelemetry.sdk.trace.data.SpanData;
+import io.smallrye.opentelemetry.test.InMemoryExporter;
+
+@ExtendWith(ArquillianExtension.class)
+class RestClientSpanTest {
+ @Deployment
+ public static WebArchive createDeployment() {
+ return ShrinkWrap.create(WebArchive.class)
+ .addAsResource(new StringAsset("client/mp-rest/url=${baseUri}"), "META-INF/microprofile-config.properties");
+ }
+
+ @ArquillianResource
+ URL url;
+ @Inject
+ InMemoryExporter spanExporter;
+ @Inject
+ @RestClient
+ SpanResourceClient client;
+
+ @BeforeEach
+ void setUp() {
+ spanExporter.reset();
+ }
+
+ @Test
+ void span() {
+ Response response = client.span();
+ assertEquals(response.getStatus(), HTTP_OK);
+
+ List spans = spanExporter.getFinishedSpanItems(2);
+
+ SpanData server = spans.get(0);
+ assertEquals(SERVER, server.getKind());
+ assertEquals(HttpMethod.GET + " " + url.getPath() + "span", server.getName());
+ assertEquals(HTTP_OK, server.getAttributes().get(HTTP_RESPONSE_STATUS_CODE));
+ assertEquals(HttpMethod.GET, server.getAttributes().get(HTTP_REQUEST_METHOD));
+ assertEquals("http", server.getAttributes().get(URL_SCHEME));
+ assertEquals(url.getPath() + "span", server.getAttributes().get(URL_PATH));
+ assertEquals(url.getHost(), server.getAttributes().get(SERVER_ADDRESS));
+
+ SpanData client = spans.get(1);
+ assertEquals(CLIENT, client.getKind());
+ assertEquals("GET", client.getName());
+ assertEquals(HTTP_OK, client.getAttributes().get(HTTP_RESPONSE_STATUS_CODE));
+ assertEquals(HttpMethod.GET, client.getAttributes().get(HTTP_REQUEST_METHOD));
+ assertEquals(url.toString() + "span", client.getAttributes().get(URL_FULL));
+
+ assertEquals(client.getTraceId(), server.getTraceId());
+ assertEquals(server.getParentSpanId(), client.getSpanId());
+ }
+
+ @Test
+ void spanName() {
+ Response response = client.spanName("1");
+ assertEquals(response.getStatus(), HTTP_OK);
+
+ List spans = spanExporter.getFinishedSpanItems(2);
+
+ SpanData server = spans.get(0);
+ assertEquals(SERVER, server.getKind());
+ assertEquals(HttpMethod.GET + " " + url.getPath() + "span/{name}", server.getName());
+ assertEquals(HTTP_OK, server.getAttributes().get(HTTP_RESPONSE_STATUS_CODE));
+ assertEquals(HttpMethod.GET, server.getAttributes().get(HTTP_REQUEST_METHOD));
+ assertEquals("http", server.getAttributes().get(URL_SCHEME));
+ assertEquals(url.getPath() + "span/1", server.getAttributes().get(URL_PATH));
+ assertEquals(url.getHost(), server.getAttributes().get(SERVER_ADDRESS));
+
+ SpanData client = spans.get(1);
+ assertEquals(CLIENT, client.getKind());
+ assertEquals("GET", client.getName());
+ assertEquals(HTTP_OK, client.getAttributes().get(HTTP_RESPONSE_STATUS_CODE));
+ assertEquals(HttpMethod.GET, client.getAttributes().get(HTTP_REQUEST_METHOD));
+ assertEquals(url.toString() + "span/1", client.getAttributes().get(URL_FULL));
+
+ assertEquals(server.getTraceId(), client.getTraceId());
+ assertEquals(server.getParentSpanId(), client.getSpanId());
+ }
+
+ @Test
+ void spanNameQuery() {
+ Response response = client.spanNameQuery("1", "query");
+ assertEquals(response.getStatus(), HTTP_OK);
+
+ List spans = spanExporter.getFinishedSpanItems(2);
+
+ SpanData server = spans.get(0);
+ assertEquals(SERVER, server.getKind());
+ assertEquals(HttpMethod.GET + " " + url.getPath() + "span/{name}", server.getName());
+ assertEquals(HTTP_OK, server.getAttributes().get(HTTP_RESPONSE_STATUS_CODE));
+ assertEquals(HttpMethod.GET, server.getAttributes().get(HTTP_REQUEST_METHOD));
+ assertEquals("http", server.getAttributes().get(URL_SCHEME));
+ assertEquals(url.getPath() + "span/1", server.getAttributes().get(URL_PATH));
+ assertEquals(url.getHost(), server.getAttributes().get(SERVER_ADDRESS));
+
+ SpanData client = spans.get(1);
+ assertEquals(CLIENT, client.getKind());
+ assertEquals("GET", client.getName());
+ assertEquals(HTTP_OK, client.getAttributes().get(HTTP_RESPONSE_STATUS_CODE));
+ assertEquals(HttpMethod.GET, client.getAttributes().get(HTTP_REQUEST_METHOD));
+ assertEquals(url.toString() + "span/1?query=query", client.getAttributes().get(URL_FULL));
+
+ assertEquals(client.getTraceId(), server.getTraceId());
+ assertEquals(server.getParentSpanId(), client.getSpanId());
+ }
+
+ @Test
+ void spanError() {
+ // Can't use REST Client here due to org.jboss.resteasy.microprofile.client.DefaultResponseExceptionMapper
+ WebTarget target = ClientBuilder.newClient().target(url.toString() + "span/error");
+ Response response = target.request().get();
+ assertEquals(response.getStatus(), HTTP_INTERNAL_ERROR);
+
+ List spans = spanExporter.getFinishedSpanItems(2);
+
+ SpanData server = spans.get(0);
+ assertEquals(SERVER, server.getKind());
+ assertEquals(HttpMethod.GET + " " + url.getPath() + "span/error", server.getName());
+ assertEquals(HTTP_INTERNAL_ERROR, server.getAttributes().get(HTTP_RESPONSE_STATUS_CODE));
+ assertEquals(HttpMethod.GET, server.getAttributes().get(HTTP_REQUEST_METHOD));
+ assertEquals("http", server.getAttributes().get(URL_SCHEME));
+ assertEquals(url.getPath() + "span/error", server.getAttributes().get(URL_PATH));
+ assertEquals(url.getHost(), server.getAttributes().get(SERVER_ADDRESS));
+
+ SpanData client = spans.get(1);
+ assertEquals(CLIENT, client.getKind());
+ assertEquals("GET", client.getName());
+ assertEquals(HTTP_INTERNAL_ERROR, client.getAttributes().get(HTTP_RESPONSE_STATUS_CODE));
+ assertEquals(HttpMethod.GET, client.getAttributes().get(HTTP_REQUEST_METHOD));
+ assertEquals(url.toString() + "span/error", client.getAttributes().get(URL_FULL));
+
+ assertEquals(client.getTraceId(), server.getTraceId());
+ assertEquals(server.getParentSpanId(), client.getSpanId());
+ }
+
+ @Test
+ void spanChild() {
+ Response response = client.spanChild();
+ assertEquals(response.getStatus(), HTTP_OK);
+
+ List spans = spanExporter.getFinishedSpanItems(3);
+
+ SpanData internal = spans.get(0);
+ assertEquals(INTERNAL, internal.getKind());
+ assertEquals("SpanBean.spanChild", internal.getName());
+
+ SpanData server = spans.get(1);
+ assertEquals(SERVER, server.getKind());
+ assertEquals(HttpMethod.GET + " " + url.getPath() + "span/child", server.getName());
+ assertEquals(HTTP_OK, server.getAttributes().get(HTTP_RESPONSE_STATUS_CODE));
+ assertEquals(HttpMethod.GET, server.getAttributes().get(HTTP_REQUEST_METHOD));
+ assertEquals("http", server.getAttributes().get(URL_SCHEME));
+ assertEquals(url.getPath() + "span/child", server.getAttributes().get(URL_PATH));
+ assertEquals(url.getHost(), server.getAttributes().get(SERVER_ADDRESS));
+
+ SpanData client = spans.get(2);
+ assertEquals(CLIENT, client.getKind());
+ assertEquals("GET", client.getName());
+ assertEquals(HTTP_OK, client.getAttributes().get(HTTP_RESPONSE_STATUS_CODE));
+ assertEquals(HttpMethod.GET, client.getAttributes().get(HTTP_REQUEST_METHOD));
+ assertEquals(url.toString() + "span/child", client.getAttributes().get(URL_FULL));
+
+ assertEquals(client.getTraceId(), internal.getTraceId());
+ assertEquals(client.getTraceId(), server.getTraceId());
+ assertEquals(internal.getParentSpanId(), server.getSpanId());
+ assertEquals(server.getParentSpanId(), client.getSpanId());
+ }
+
+ @Test
+ void spanCurrent() {
+ Response response = client.spanCurrent();
+ assertEquals(response.getStatus(), HTTP_OK);
+
+ List spans = spanExporter.getFinishedSpanItems(2);
+
+ SpanData server = spans.get(0);
+ assertEquals(SERVER, server.getKind());
+ assertEquals(HttpMethod.GET + " " + url.getPath() + "span/current", server.getName());
+ assertEquals(HTTP_OK, server.getAttributes().get(HTTP_RESPONSE_STATUS_CODE));
+ assertEquals(HttpMethod.GET, server.getAttributes().get(HTTP_REQUEST_METHOD));
+ assertEquals("http", server.getAttributes().get(URL_SCHEME));
+ assertEquals(url.getPath() + "span/current", server.getAttributes().get(URL_PATH));
+ assertEquals(url.getHost(), server.getAttributes().get(SERVER_ADDRESS));
+ assertEquals("tck.current.value", server.getAttributes().get(stringKey("tck.current.key")));
+
+ SpanData client = spans.get(1);
+ assertEquals(CLIENT, client.getKind());
+ assertEquals("GET", client.getName());
+ assertEquals(HTTP_OK, client.getAttributes().get(HTTP_RESPONSE_STATUS_CODE));
+ assertEquals(HttpMethod.GET, client.getAttributes().get(HTTP_REQUEST_METHOD));
+ assertEquals(url.toString() + "span/current", client.getAttributes().get(URL_FULL));
+
+ assertEquals(client.getTraceId(), server.getTraceId());
+ assertEquals(server.getParentSpanId(), client.getSpanId());
+ }
+
+ @Test
+ void spanNew() {
+ Response response = client.spanNew();
+ assertEquals(response.getStatus(), HTTP_OK);
+
+ List spans = spanExporter.getFinishedSpanItems(3);
+
+ SpanData internal = spans.get(0);
+ assertEquals(INTERNAL, internal.getKind());
+ assertEquals("span.new", internal.getName());
+ assertEquals("tck.new.value", internal.getAttributes().get(stringKey("tck.new.key")));
+
+ SpanData server = spans.get(1);
+ assertEquals(SERVER, server.getKind());
+ assertEquals(HttpMethod.GET + " " + url.getPath() + "span/new", server.getName());
+ assertEquals(HTTP_OK, server.getAttributes().get(HTTP_RESPONSE_STATUS_CODE));
+ assertEquals(HttpMethod.GET, server.getAttributes().get(HTTP_REQUEST_METHOD));
+ assertEquals("http", server.getAttributes().get(URL_SCHEME));
+ assertEquals(url.getPath() + "span/new", server.getAttributes().get(URL_PATH));
+ assertEquals(url.getHost(), server.getAttributes().get(SERVER_ADDRESS));
+
+ SpanData client = spans.get(2);
+ assertEquals(CLIENT, client.getKind());
+ assertEquals("GET", client.getName());
+ assertEquals(HTTP_OK, client.getAttributes().get(HTTP_RESPONSE_STATUS_CODE));
+ assertEquals(HttpMethod.GET, client.getAttributes().get(HTTP_REQUEST_METHOD));
+ assertEquals(url.toString() + "span/new", client.getAttributes().get(URL_FULL));
+
+ assertEquals(client.getTraceId(), internal.getTraceId());
+ assertEquals(client.getTraceId(), server.getTraceId());
+ assertEquals(internal.getParentSpanId(), server.getSpanId());
+ assertEquals(server.getParentSpanId(), client.getSpanId());
+ }
+
+ @RequestScoped
+ @Path("/")
+ public static class SpanResource {
+ @Inject
+ SpanBean spanBean;
+ @Inject
+ Span span;
+ @Inject
+ Tracer tracer;
+
+ @GET
+ @Path("/span")
+ public Response span() {
+ return Response.ok().build();
+ }
+
+ @GET
+ @Path("/span/{name}")
+ public Response spanName(@PathParam(value = "name") String name, @QueryParam("query") String query) {
+ return Response.ok().build();
+ }
+
+ @GET
+ @Path("/span/error")
+ public Response spanError() {
+ return Response.serverError().build();
+ }
+
+ @GET
+ @Path("/span/child")
+ public Response spanChild() {
+ spanBean.spanChild();
+ return Response.ok().build();
+ }
+
+ @GET
+ @Path("/span/current")
+ public Response spanCurrent() {
+ span.setAttribute("tck.current.key", "tck.current.value");
+ return Response.ok().build();
+ }
+
+ @GET
+ @Path("/span/new")
+ public Response spanNew() {
+ Span span = tracer.spanBuilder("span.new")
+ .setSpanKind(INTERNAL)
+ .setParent(Context.current().with(this.span))
+ .setAttribute("tck.new.key", "tck.new.value")
+ .startSpan();
+
+ span.end();
+
+ return Response.ok().build();
+ }
+ }
+
+ @ApplicationScoped
+ public static class SpanBean {
+ @WithSpan
+ void spanChild() {
+
+ }
+ }
+
+ @RegisterRestClient(configKey = "client")
+ @Path("/")
+ public interface SpanResourceClient {
+ @GET
+ @Path("/span")
+ Response span();
+
+ @GET
+ @Path("/span/{name}")
+ Response spanName(@PathParam(value = "name") String name);
+
+ @GET
+ @Path("/span/{name}")
+ Response spanNameQuery(@PathParam(value = "name") String name, @QueryParam("query") String query);
+
+ @GET
+ @Path("/span/child")
+ Response spanChild();
+
+ @GET
+ @Path("/span/current")
+ Response spanCurrent();
+
+ @GET
+ @Path("/span/new")
+ Response spanNew();
+ }
+
+ @ApplicationPath("/")
+ public static class RestApplication extends Application {
+
+ }
+}
diff --git a/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/trace/rest/RestSpanTest.java b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/trace/rest/RestSpanTest.java
new file mode 100644
index 00000000..9de49b16
--- /dev/null
+++ b/testsuite/observation/src/test/java/io/smallrye/opentelemetry/observation/test/trace/rest/RestSpanTest.java
@@ -0,0 +1,204 @@
+package io.smallrye.opentelemetry.observation.test.trace.rest;
+
+import static io.opentelemetry.api.trace.SpanKind.SERVER;
+import static io.opentelemetry.semconv.ClientAttributes.CLIENT_ADDRESS;
+import static io.opentelemetry.semconv.HttpAttributes.HTTP_REQUEST_METHOD;
+import static io.opentelemetry.semconv.HttpAttributes.HTTP_RESPONSE_STATUS_CODE;
+import static io.opentelemetry.semconv.HttpAttributes.HTTP_ROUTE;
+import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_LOCAL_ADDRESS;
+import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_LOCAL_PORT;
+import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PROTOCOL_NAME;
+import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PROTOCOL_VERSION;
+import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS;
+import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT;
+import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_NAME;
+import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_VERSION;
+import static io.opentelemetry.semconv.UrlAttributes.URL_PATH;
+import static io.opentelemetry.semconv.UrlAttributes.URL_QUERY;
+import static io.opentelemetry.semconv.UrlAttributes.URL_SCHEME;
+import static io.opentelemetry.semconv.UserAgentAttributes.USER_AGENT_ORIGINAL;
+import static io.restassured.RestAssured.given;
+import static io.smallrye.opentelemetry.observation.test.AttributeKeysStability.get;
+import static java.net.HttpURLConnection.HTTP_OK;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.net.URL;
+import java.util.List;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.ApplicationPath;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.HttpMethod;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.Response;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.junit5.ArquillianExtension;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
+import io.opentelemetry.sdk.trace.data.SpanData;
+import io.smallrye.opentelemetry.api.OpenTelemetryConfig;
+import io.smallrye.opentelemetry.test.InMemoryExporter;
+
+@ExtendWith(ArquillianExtension.class)
+class RestSpanTest {
+ @Deployment
+ public static WebArchive createDeployment() {
+ return ShrinkWrap.create(WebArchive.class);
+ }
+
+ @ArquillianResource
+ URL url;
+ @Inject
+ InMemoryExporter spanExporter;
+
+ @BeforeEach
+ void setUp() {
+ spanExporter.reset();
+ }
+
+ @Test
+ void span() {
+ given().get("/span").then().statusCode(HTTP_OK);
+
+ List spanItems = spanExporter.getFinishedSpanItems(1);
+ assertEquals(1, spanItems.size());
+ SpanData span = spanItems.get(0);
+ assertEquals(SERVER, span.getKind());
+ assertEquals(HttpMethod.GET + " " + url.getPath() + "span", span.getName());
+ assertEquals(HTTP_OK, span.getAttributes().get(HTTP_RESPONSE_STATUS_CODE));
+ assertEquals(HttpMethod.GET, span.getAttributes().get(HTTP_REQUEST_METHOD));
+ assertEquals("http", span.getAttributes().get(NETWORK_PROTOCOL_NAME));
+ assertEquals("UNKNOWN", span.getAttributes().get(NETWORK_PROTOCOL_VERSION));
+
+ assertEquals("tck", span.getResource().getAttribute(SERVICE_NAME));
+ assertEquals("1.0", span.getResource().getAttribute(SERVICE_VERSION));
+
+ InstrumentationScopeInfo libraryInfo = span.getInstrumentationScopeInfo();
+ assertEquals(OpenTelemetryConfig.INSTRUMENTATION_NAME, libraryInfo.getName());
+ // assertEquals(OpenTelemetryConfig.INSTRUMENTATION_VERSION, libraryInfo.getVersion()); FIXME
+ }
+
+ @Test
+ void spanName() {
+ given().get("/span/1").then().statusCode(HTTP_OK);
+
+ List spanItems = spanExporter.getFinishedSpanItems(1);
+ assertEquals(1, spanItems.size());
+ SpanData span = spanItems.get(0);
+ assertEquals(SERVER, span.getKind());
+ assertEquals(HttpMethod.GET + " " + url.getPath() + "span/{name}", span.getName());
+ assertEquals(HTTP_OK, span.getAttributes().get(HTTP_RESPONSE_STATUS_CODE));
+ assertEquals(HttpMethod.GET, span.getAttributes().get(HTTP_REQUEST_METHOD));
+ }
+
+ @Test
+ void spanNameWithoutQueryString() {
+ given().get("/span/1?id=1").then().statusCode(HTTP_OK);
+
+ List spanItems = spanExporter.getFinishedSpanItems(1);
+ assertEquals(1, spanItems.size());
+ SpanData span = spanItems.get(0);
+ assertEquals(SERVER, span.getKind());
+ assertEquals(HttpMethod.GET + " " + url.getPath() + "span/{name}", span.getName());
+ assertEquals(HTTP_OK, span.getAttributes().get(HTTP_RESPONSE_STATUS_CODE));
+ assertEquals(HttpMethod.GET, span.getAttributes().get(HTTP_REQUEST_METHOD));
+ assertEquals(url.getPath() + "span/1", get(span, URL_PATH));
+ assertEquals("id=1", get(span, URL_QUERY));
+ assertEquals(url.getPath() + "span/{name}", span.getAttributes().get(HTTP_ROUTE));
+ }
+
+ @Test
+ void spanPost() {
+ given().body("payload").post("/span").then().statusCode(HTTP_OK);
+
+ List spanItems = spanExporter.getFinishedSpanItems(1);
+ assertEquals(1, spanItems.size());
+ SpanData span = spanItems.get(0);
+ assertEquals(SERVER, span.getKind());
+ assertEquals(HttpMethod.POST + " " + url.getPath() + "span", span.getName());
+
+ // Common Attributes
+ assertEquals(HttpMethod.POST, span.getAttributes().get(HTTP_REQUEST_METHOD));
+ assertEquals(HTTP_OK, span.getAttributes().get(HTTP_RESPONSE_STATUS_CODE));
+ assertNotNull(span.getAttributes().get(USER_AGENT_ORIGINAL));
+ assertNull(span.getAttributes().get(NETWORK_LOCAL_ADDRESS));
+ assertNull(span.getAttributes().get(NETWORK_LOCAL_PORT));
+
+ // Server Attributes
+ assertEquals("http", span.getAttributes().get(URL_SCHEME));
+ assertEquals(url.getPath() + "span", span.getAttributes().get(URL_PATH));
+ assertEquals(url.getPath() + "span", span.getAttributes().get(HTTP_ROUTE));
+ assertNull(span.getAttributes().get(CLIENT_ADDRESS));
+ assertEquals(url.getHost(), span.getAttributes().get(SERVER_ADDRESS));
+ assertEquals(url.getPort(), span.getAttributes().get(SERVER_PORT));
+ }
+
+ @Test
+ void subResource() {
+ given().get("/sub/1").then().statusCode(HTTP_OK);
+
+ List spanItems = spanExporter.getFinishedSpanItems(1);
+ assertEquals(1, spanItems.size());
+ SpanData span = spanItems.get(0);
+ assertEquals(SERVER, span.getKind());
+ assertEquals(HttpMethod.GET, span.getName());
+ assertEquals(HTTP_OK, span.getAttributes().get(HTTP_RESPONSE_STATUS_CODE));
+ assertEquals(HttpMethod.GET, span.getAttributes().get(HTTP_REQUEST_METHOD));
+ }
+
+ @Path("/")
+ public static class SpanResource {
+ @GET
+ @Path("/span")
+ public Response span() {
+ return Response.ok().build();
+ }
+
+ @GET
+ @Path("/span/{name}")
+ public Response spanName(@PathParam(value = "name") String name) {
+ return Response.ok().build();
+ }
+
+ @POST
+ @Path("/span")
+ public Response spanPost(String payload) {
+ return Response.ok(payload).build();
+ }
+
+ @Path("/sub/{id}")
+ public SubResource subResource(@PathParam("id") String id) {
+ return new SubResource(id);
+ }
+ }
+
+ public static class SubResource {
+ private final String id;
+
+ public SubResource(final String id) {
+ this.id = id;
+ }
+
+ @GET
+ public Response get() {
+ return Response.ok().build();
+ }
+ }
+
+ @ApplicationPath("/")
+ public static class RestApplication extends Application {
+
+ }
+}
diff --git a/testsuite/observation/src/test/resources/META-INF/microprofile-config.properties b/testsuite/observation/src/test/resources/META-INF/microprofile-config.properties
new file mode 100644
index 00000000..3275386b
--- /dev/null
+++ b/testsuite/observation/src/test/resources/META-INF/microprofile-config.properties
@@ -0,0 +1,7 @@
+otel.traces.exporter=in-memory
+otel.metrics.exporter=in-memory
+otel.bsp.schedule.delay=100
+otel.metric.export.interval=100
+
+otel.service.name=tck
+otel.resource.attributes=service.version=1.0
diff --git a/testsuite/observation/src/test/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension b/testsuite/observation/src/test/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension
new file mode 100644
index 00000000..cbcad939
--- /dev/null
+++ b/testsuite/observation/src/test/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension
@@ -0,0 +1 @@
+io.smallrye.opentelemetry.observation.test.ArquillianExtension
\ No newline at end of file
diff --git a/testsuite/pom.xml b/testsuite/pom.xml
index a3259ba1..c23de6b3 100644
--- a/testsuite/pom.xml
+++ b/testsuite/pom.xml
@@ -34,6 +34,7 @@
extra
+ observation
tck