diff --git a/implementation/rest/src/main/java/io/smallrye/opentelemetry/implementation/rest/OpenTelemetryClientFilter.java b/implementation/rest/src/main/java/io/smallrye/opentelemetry/implementation/rest/OpenTelemetryClientFilter.java index 33865a97..e7e98990 100644 --- a/implementation/rest/src/main/java/io/smallrye/opentelemetry/implementation/rest/OpenTelemetryClientFilter.java +++ b/implementation/rest/src/main/java/io/smallrye/opentelemetry/implementation/rest/OpenTelemetryClientFilter.java @@ -5,6 +5,8 @@ import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; +import java.net.InetSocketAddress; +import java.net.URI; import java.util.List; import jakarta.inject.Inject; @@ -26,6 +28,8 @@ import io.opentelemetry.instrumentation.api.semconv.http.HttpClientMetrics; import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanStatusExtractor; +import io.opentelemetry.instrumentation.api.semconv.network.NetworkAttributesExtractor; +import io.opentelemetry.semconv.NetworkAttributes; @Provider public class OpenTelemetryClientFilter implements ClientRequestFilter, ClientResponseFilter { @@ -48,6 +52,7 @@ public OpenTelemetryClientFilter(final OpenTelemetry openTelemetry) { this.instrumenter = builder .setSpanStatusExtractor(HttpSpanStatusExtractor.create(clientAttributesExtractor)) + .addAttributesExtractor(NetworkAttributesExtractor.create(new NetworkAttributesGetter())) .addAttributesExtractor(HttpClientAttributesExtractor.create(clientAttributesExtractor)) .addOperationMetrics(HttpClientMetrics.get()) .addOperationMetrics(HttpClientExperimentalMetrics.get()) @@ -101,12 +106,43 @@ public void set(final ClientRequestContext carrier, final String key, final Stri } } + private static class NetworkAttributesGetter implements + io.opentelemetry.instrumentation.api.semconv.network.NetworkAttributesGetter { + @Override + public String getNetworkTransport(final ClientRequestContext request, final ClientResponseContext response) { + return NetworkAttributes.NetworkTransportValues.TCP; + } + + @Override + public String getNetworkProtocolName(final ClientRequestContext request, final ClientResponseContext response) { + return null; + } + + @Override + public String getNetworkProtocolVersion(final ClientRequestContext request, final ClientResponseContext response) { + return null; + } + + @Override + public InetSocketAddress getNetworkPeerInetSocketAddress(final ClientRequestContext request, + final ClientResponseContext response) { + URI uri = request.getUri(); + String serverAddress = uri.getHost(); + Integer serverPort = uri.getPort() > 0 ? uri.getPort() : null; + if (serverAddress != null && serverPort != null) { + return new InetSocketAddress(serverAddress, serverPort); + } + return null; + } + } + @SuppressWarnings("NullableProblems") private static class ClientAttributesExtractor implements HttpClientAttributesGetter { @Override public String getUrlFull(final ClientRequestContext request) { + // TODO - Make sure this does not contain authentication information return request.getUri().toString(); } diff --git a/implementation/rest/src/main/java/io/smallrye/opentelemetry/implementation/rest/OpenTelemetryServerFilter.java b/implementation/rest/src/main/java/io/smallrye/opentelemetry/implementation/rest/OpenTelemetryServerFilter.java index 3c77d846..a22ef376 100644 --- a/implementation/rest/src/main/java/io/smallrye/opentelemetry/implementation/rest/OpenTelemetryServerFilter.java +++ b/implementation/rest/src/main/java/io/smallrye/opentelemetry/implementation/rest/OpenTelemetryServerFilter.java @@ -8,6 +8,8 @@ import static java.util.Collections.singletonList; import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.net.URI; import java.util.List; import jakarta.inject.Inject; @@ -32,6 +34,8 @@ import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanStatusExtractor; import io.opentelemetry.instrumentation.api.semconv.network.NetworkAttributesExtractor; +import io.opentelemetry.instrumentation.api.semconv.network.NetworkAttributesGetter; +import io.opentelemetry.semconv.NetworkAttributes; @Provider public class OpenTelemetryServerFilter implements ContainerRequestFilter, ContainerResponseFilter { @@ -123,6 +127,11 @@ public String get(final ContainerRequestContext carrier, final String key) { private static class NetworkAttributesGetter implements io.opentelemetry.instrumentation.api.semconv.network.NetworkAttributesGetter { + @Override + public String getNetworkTransport(final ContainerRequestContext request, final ContainerResponseContext response) { + return NetworkAttributes.NetworkTransportValues.TCP; + } + @Override public String getNetworkProtocolName(final ContainerRequestContext request, final ContainerResponseContext response) { return (String) request.getProperty(NETWORK_PROTOCOL_NAME.getKey()); @@ -133,6 +142,18 @@ public String getNetworkProtocolVersion(final ContainerRequestContext request, final ContainerResponseContext response) { return (String) request.getProperty(NETWORK_PROTOCOL_VERSION.getKey()); } + + @Override + public InetSocketAddress getNetworkPeerInetSocketAddress(final ContainerRequestContext request, + final ContainerResponseContext response) { + URI uri = request.getUriInfo().getRequestUri(); + String serverAddress = uri.getHost(); + Integer serverPort = uri.getPort() > 0 ? uri.getPort() : null; + if (serverAddress != null && serverPort != null) { + return new InetSocketAddress(serverAddress, serverPort); + } + return null; + } } @SuppressWarnings("NullableProblems") 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 9813d318..47eb82bd 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 @@ -6,7 +6,13 @@ 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.NetworkAttributes.NETWORK_PEER_ADDRESS; +import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PEER_PORT; +import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PROTOCOL_NAME; +import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PROTOCOL_VERSION; +import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_TRANSPORT; import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; +import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT; import static io.opentelemetry.semconv.UrlAttributes.URL_FULL; import static io.opentelemetry.semconv.UrlAttributes.URL_PATH; import static io.opentelemetry.semconv.UrlAttributes.URL_QUERY; @@ -15,6 +21,8 @@ import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR; 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; @@ -50,6 +58,7 @@ import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.annotations.WithSpan; import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.semconv.ErrorAttributes; import io.smallrye.opentelemetry.test.InMemorySpanExporter; @ExtendWith(ArquillianExtension.class) @@ -88,6 +97,7 @@ void span() { assertEquals("http", get(server, URL_SCHEME)); assertEquals(url.getPath() + "span", get(server, URL_PATH)); assertEquals(url.getHost(), get(server, SERVER_ADDRESS)); + assertEquals(url.getPort(), get(server, SERVER_PORT)); SpanData client = spans.get(1); assertEquals(CLIENT, client.getKind()); @@ -172,6 +182,12 @@ void spanError() { assertEquals("http", get(server, URL_SCHEME)); assertEquals(url.getPath() + "span/error", get(server, URL_PATH)); assertEquals(url.getHost(), get(server, SERVER_ADDRESS)); + assertEquals("500", get(server, ErrorAttributes.ERROR_TYPE)); + assertEquals("tcp", get(server, NETWORK_TRANSPORT)); + assertEquals("http", get(server, NETWORK_PROTOCOL_NAME)); + assertEquals("1.1", get(server, NETWORK_PROTOCOL_VERSION)); + assertNotNull(get(server, NETWORK_PEER_ADDRESS)); + assertNotNull(get(server, NETWORK_PEER_PORT)); SpanData client = spans.get(1); assertEquals(CLIENT, client.getKind()); @@ -179,6 +195,11 @@ void spanError() { assertEquals(HTTP_INTERNAL_ERROR, get(client, HTTP_RESPONSE_STATUS_CODE)); assertEquals(HttpMethod.GET, get(client, HTTP_REQUEST_METHOD)); assertEquals(url.toString() + "span/error", get(client, URL_FULL)); + assertEquals("tcp", get(client, NETWORK_TRANSPORT)); + assertNull(get(client, NETWORK_PROTOCOL_NAME)); + assertNull(get(client, NETWORK_PROTOCOL_VERSION)); + assertNotNull(get(client, NETWORK_PEER_ADDRESS)); + assertNotNull(get(client, NETWORK_PEER_PORT)); assertEquals(client.getTraceId(), server.getTraceId()); assertEquals(server.getParentSpanId(), client.getSpanId()); 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 dcb5cabf..374b10fb 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 @@ -7,8 +7,11 @@ 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_PEER_ADDRESS; +import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PEER_PORT; import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PROTOCOL_NAME; import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PROTOCOL_VERSION; +import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_TRANSPORT; import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT; import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_NAME; @@ -19,6 +22,7 @@ import static io.opentelemetry.semconv.UserAgentAttributes.USER_AGENT_ORIGINAL; import static io.restassured.RestAssured.given; import static io.smallrye.opentelemetry.extra.test.AttributeKeysStability.get; +import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR; import static java.net.HttpURLConnection.HTTP_OK; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -48,6 +52,7 @@ import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.semconv.ErrorAttributes; import io.smallrye.opentelemetry.api.OpenTelemetryConfig; import io.smallrye.opentelemetry.test.InMemorySpanExporter; @@ -79,8 +84,19 @@ void span() { assertEquals(HttpMethod.GET + " " + url.getPath() + "span", span.getName()); assertEquals(HTTP_OK, get(span, HTTP_RESPONSE_STATUS_CODE)); assertEquals(HttpMethod.GET, get(span, HTTP_REQUEST_METHOD)); + assertEquals("tcp", get(span, NETWORK_TRANSPORT)); assertEquals("http", get(span, NETWORK_PROTOCOL_NAME)); assertEquals("1.1", get(span, NETWORK_PROTOCOL_VERSION)); + assertNotNull(get(span, NETWORK_PEER_ADDRESS)); + assertNotNull(get(span, NETWORK_PEER_PORT)); + + assertEquals("http", get(span, URL_SCHEME)); + assertEquals(url.getPath() + "span", get(span, URL_PATH)); + assertNull(get(span, URL_QUERY)); + assertEquals(url.getPath() + "span", span.getAttributes().get(HTTP_ROUTE)); + assertEquals(url.getHost(), get(span, SERVER_ADDRESS)); + assertEquals(url.getPort(), get(span, SERVER_PORT)); + assertNotNull(get(span, USER_AGENT_ORIGINAL)); assertEquals("tck", span.getResource().getAttribute(SERVICE_NAME)); assertEquals("1.0", span.getResource().getAttribute(SERVICE_VERSION)); @@ -119,6 +135,38 @@ void spanNameWithoutQueryString() { assertEquals(url.getPath() + "span/{name}", span.getAttributes().get(HTTP_ROUTE)); } + @Test + void spanError() { + given().get("/span/error").then().statusCode(HTTP_INTERNAL_ERROR); + + List spanItems = spanExporter.getFinishedSpanItems(1); + assertEquals(1, spanItems.size()); + SpanData span = spanItems.get(0); + assertEquals(SERVER, span.getKind()); + assertEquals(HttpMethod.GET + " " + url.getPath() + "span/error", span.getName()); + assertEquals(HTTP_INTERNAL_ERROR, get(span, HTTP_RESPONSE_STATUS_CODE)); + assertEquals(HttpMethod.GET, get(span, HTTP_REQUEST_METHOD)); + assertEquals(url.getPath() + "span/error", get(span, URL_PATH)); + assertEquals(url.getPath() + "span/error", span.getAttributes().get(HTTP_ROUTE)); + assertEquals("500", span.getAttributes().get(ErrorAttributes.ERROR_TYPE)); + } + + @Test + void spanException() { + given().get("/span/exception").then().statusCode(HTTP_INTERNAL_ERROR); + + List spanItems = spanExporter.getFinishedSpanItems(1); + assertEquals(1, spanItems.size()); + SpanData span = spanItems.get(0); + assertEquals(SERVER, span.getKind()); + assertEquals(HttpMethod.GET + " " + url.getPath() + "span/exception", span.getName()); + assertEquals(HTTP_INTERNAL_ERROR, get(span, HTTP_RESPONSE_STATUS_CODE)); + assertEquals(HttpMethod.GET, get(span, HTTP_REQUEST_METHOD)); + assertEquals(url.getPath() + "span/exception", get(span, URL_PATH)); + assertEquals(url.getPath() + "span/exception", span.getAttributes().get(HTTP_ROUTE)); + assertEquals("500", span.getAttributes().get(ErrorAttributes.ERROR_TYPE)); + } + @Test void spanPost() { given().body("payload").post("/span").then().statusCode(HTTP_OK); @@ -172,6 +220,18 @@ public Response spanName(@PathParam(value = "name") String name) { return Response.ok().build(); } + @GET + @Path("/span/error") + public Response spanError() { + return Response.serverError().build(); + } + + @GET + @Path("/span/exception") + public Response spanException() { + throw new RuntimeException(); + } + @POST @Path("/span") public Response spanPost(String payload) {