diff --git a/README.md b/README.md index f49966136..a1176a435 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ List of supported frameworks with additional capabilities: | [Apache HttpClient](https://hc.apache.org/index.html) | 4.0+ | | [gRPC](https://github.com/grpc/grpc-java) | 1.5+ | | [JAX-RS Client](https://javaee.github.io/javaee-spec/javadocs/javax/ws/rs/client/package-summary.html) | 2.0+ | +| [Micronaut](https://micronaut.io/) (via Netty and only server) | 1.0+ | | [Netty](https://github.com/netty/netty) (only server) | 4.0+ | | [OkHttp](https://github.com/square/okhttp/) | 3.0+ | | [Servlet](https://javaee.github.io/javaee-spec/javadocs/javax/servlet/package-summary.html) | 2.3+ | diff --git a/instrumentation/micronaut-1.0/build.gradle.kts b/instrumentation/micronaut-1.0/build.gradle.kts new file mode 100644 index 000000000..5b91917ba --- /dev/null +++ b/instrumentation/micronaut-1.0/build.gradle.kts @@ -0,0 +1,47 @@ +plugins { + `java-library` +} + +val versions: Map by extra + +val micronautVersion = "1.0.0" +val micronaut2Version = "2.2.3" +val micronautTestVersion = "1.0.0" + +dependencies { + implementation(project(":instrumentation:netty:netty-4.1")) + implementation("io.opentelemetry.javaagent.instrumentation:opentelemetry-javaagent-netty-4.1:${versions["opentelemetry_java_agent"]}") + + testImplementation(project(":testing-common")) + testImplementation("io.micronaut.test:micronaut-test-junit5:${micronautTestVersion}") + testImplementation("io.micronaut:micronaut-http-server-netty:${micronautVersion}") + testImplementation("io.micronaut:micronaut-runtime:${micronautVersion}") + testImplementation("io.micronaut:micronaut-inject:${micronautVersion}") + testAnnotationProcessor("io.micronaut:micronaut-inject-java:${micronautVersion}") +} + +/** + * Todo this does not run tests + */ +for (version in listOf(micronautVersion, micronaut2Version)) { + val versionedConfiguration = configurations.create("test_${version}") { + extendsFrom(configurations.testRuntimeClasspath.get()) + } + dependencies { + versionedConfiguration(project(":instrumentation:netty:netty-4.1")) + versionedConfiguration("io.opentelemetry.javaagent.instrumentation:opentelemetry-javaagent-netty-4.1:${versions["opentelemetry_java_agent"]}") + versionedConfiguration(project(":testing-common")) + versionedConfiguration("io.micronaut:micronaut-inject-java:${version}") + versionedConfiguration("io.micronaut.test:micronaut-test-junit5:${micronautTestVersion}") + versionedConfiguration("io.micronaut:micronaut-http-server-netty:${version}") + versionedConfiguration("io.micronaut:micronaut-runtime:${version}") + versionedConfiguration("io.micronaut:micronaut-inject:${version}") + } + val versionedTest = task("test_${version}") { + group = "verification" + testClassesDirs = sourceSets["test"].output.classesDirs + classpath = versionedConfiguration + shouldRunAfter("test") + } + tasks.check { dependsOn(versionedTest) } +} diff --git a/instrumentation/micronaut-1.0/src/test/java/io/opentelemetry/javaagent/instrumentation/hypertrace/micronaut/MicronautInstrumentationTest.java b/instrumentation/micronaut-1.0/src/test/java/io/opentelemetry/javaagent/instrumentation/hypertrace/micronaut/MicronautInstrumentationTest.java new file mode 100644 index 000000000..91e8e03e3 --- /dev/null +++ b/instrumentation/micronaut-1.0/src/test/java/io/opentelemetry/javaagent/instrumentation/hypertrace/micronaut/MicronautInstrumentationTest.java @@ -0,0 +1,202 @@ +/* + * Copyright The Hypertrace Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opentelemetry.javaagent.instrumentation.hypertrace.micronaut; + +import io.micronaut.runtime.server.EmbeddedServer; +import io.micronaut.test.annotation.MicronautTest; +import io.opentelemetry.sdk.trace.data.SpanData; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.TimeoutException; +import javax.inject.Inject; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okio.Buffer; +import org.hypertrace.agent.core.instrumentation.HypertraceSemanticAttributes; +import org.hypertrace.agent.testing.AbstractInstrumenterTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +@MicronautTest +public class MicronautInstrumentationTest extends AbstractInstrumenterTest { + + public static final String REQUEST_HEADER_NAME = "reqheader"; + public static final String REQUEST_HEADER_VALUE = "reqheadervalue"; + + @Inject EmbeddedServer server; + + @Test + public void get() throws IOException, TimeoutException, InterruptedException { + Request request = + new Request.Builder() + .url(String.format("http://localhost:%d/get_no_content", server.getPort())) + .header(REQUEST_HEADER_NAME, REQUEST_HEADER_VALUE) + .get() + .build(); + + try (Response response = httpClient.newCall(request).execute()) { + Assertions.assertEquals(204, response.code()); + } + + List> traces = TEST_WRITER.getTraces(); + TEST_WRITER.waitForTraces(1); + Assertions.assertEquals(1, traces.size()); + List trace = traces.get(0); + Assertions.assertEquals(1, trace.size()); + SpanData spanData = trace.get(0); + + Assertions.assertEquals( + REQUEST_HEADER_VALUE, + spanData + .getAttributes() + .get(HypertraceSemanticAttributes.httpRequestHeader(REQUEST_HEADER_NAME))); + Assertions.assertEquals( + TestController.RESPONSE_HEADER_VALUE, + spanData + .getAttributes() + .get( + HypertraceSemanticAttributes.httpResponseHeader( + TestController.RESPONSE_HEADER_NAME))); + Assertions.assertNull( + spanData.getAttributes().get(HypertraceSemanticAttributes.HTTP_REQUEST_BODY)); + Assertions.assertNull( + spanData.getAttributes().get(HypertraceSemanticAttributes.HTTP_RESPONSE_BODY)); + } + + @Test + public void postJson() throws IOException, TimeoutException, InterruptedException { + RequestBody requestBody = requestBody(true, 3000, 75); + Buffer requestBodyBuffer = new Buffer(); + requestBody.writeTo(requestBodyBuffer); + String requestBodyStr = new String(requestBodyBuffer.readByteArray()); + + Request request = + new Request.Builder() + .url(String.format("http://localhost:%d/post", server.getPort())) + .header(REQUEST_HEADER_NAME, REQUEST_HEADER_VALUE) + .header("Transfer-Encoding", "chunked") + .post(requestBody) + .build(); + + try (Response response = httpClient.newCall(request).execute()) { + Assertions.assertEquals(200, response.code()); + Assertions.assertEquals(TestController.RESPONSE_BODY, response.body().string()); + } + + List> traces = TEST_WRITER.getTraces(); + TEST_WRITER.waitForTraces(1); + Assertions.assertEquals(1, traces.size()); + List trace = traces.get(0); + Assertions.assertEquals(1, trace.size()); + SpanData spanData = trace.get(0); + + Assertions.assertEquals( + REQUEST_HEADER_VALUE, + spanData + .getAttributes() + .get(HypertraceSemanticAttributes.httpRequestHeader(REQUEST_HEADER_NAME))); + Assertions.assertEquals( + TestController.RESPONSE_HEADER_VALUE, + spanData + .getAttributes() + .get( + HypertraceSemanticAttributes.httpResponseHeader( + TestController.RESPONSE_HEADER_NAME))); + Assertions.assertEquals( + requestBodyStr, + spanData.getAttributes().get(HypertraceSemanticAttributes.HTTP_REQUEST_BODY)); + Assertions.assertEquals( + TestController.RESPONSE_BODY, + spanData.getAttributes().get(HypertraceSemanticAttributes.HTTP_RESPONSE_BODY)); + } + + @Test + public void stream() throws IOException, TimeoutException, InterruptedException { + Request request = + new Request.Builder() + .url(String.format("http://localhost:%d/stream", server.getPort())) + .header(REQUEST_HEADER_NAME, REQUEST_HEADER_VALUE) + .get() + .build(); + + StringBuilder responseBody = new StringBuilder(); + for (String body : TestController.streamBody()) { + responseBody.append(body); + } + + try (Response response = httpClient.newCall(request).execute()) { + Assertions.assertEquals(200, response.code()); + Assertions.assertEquals(responseBody.toString(), response.body().string()); + } + + List> traces = TEST_WRITER.getTraces(); + TEST_WRITER.waitForTraces(1); + Assertions.assertEquals(1, traces.size()); + List trace = traces.get(0); + Assertions.assertEquals(1, trace.size()); + SpanData spanData = trace.get(0); + + Assertions.assertEquals( + REQUEST_HEADER_VALUE, + spanData + .getAttributes() + .get(HypertraceSemanticAttributes.httpRequestHeader(REQUEST_HEADER_NAME))); + Assertions.assertNull( + spanData.getAttributes().get(HypertraceSemanticAttributes.HTTP_REQUEST_BODY)); + Assertions.assertEquals( + responseBody.toString(), + spanData.getAttributes().get(HypertraceSemanticAttributes.HTTP_RESPONSE_BODY)); + } + + @Test + public void blocking() throws IOException, TimeoutException, InterruptedException { + Request request = + new Request.Builder() + .url(String.format("http://localhost:%d/post", server.getPort())) + .header(REQUEST_HEADER_NAME, REQUEST_HEADER_VALUE) + .header("mockblock", "true") + .get() + .build(); + + try (Response response = httpClient.newCall(request).execute()) { + Assertions.assertEquals(403, response.code()); + Assertions.assertTrue(response.body().string().isEmpty()); + } + + List> traces = TEST_WRITER.getTraces(); + TEST_WRITER.waitForTraces(1); + Assertions.assertEquals(1, traces.size()); + List trace = traces.get(0); + Assertions.assertEquals(1, trace.size()); + SpanData spanData = trace.get(0); + + Assertions.assertEquals( + REQUEST_HEADER_VALUE, + spanData + .getAttributes() + .get(HypertraceSemanticAttributes.httpRequestHeader(REQUEST_HEADER_NAME))); + Assertions.assertNull( + spanData + .getAttributes() + .get( + HypertraceSemanticAttributes.httpResponseHeader( + TestController.RESPONSE_HEADER_NAME))); + Assertions.assertNull( + spanData.getAttributes().get(HypertraceSemanticAttributes.HTTP_RESPONSE_BODY)); + } +} diff --git a/instrumentation/micronaut-1.0/src/test/java/io/opentelemetry/javaagent/instrumentation/hypertrace/micronaut/TestController.java b/instrumentation/micronaut-1.0/src/test/java/io/opentelemetry/javaagent/instrumentation/hypertrace/micronaut/TestController.java new file mode 100644 index 000000000..42943dc30 --- /dev/null +++ b/instrumentation/micronaut-1.0/src/test/java/io/opentelemetry/javaagent/instrumentation/hypertrace/micronaut/TestController.java @@ -0,0 +1,69 @@ +/* + * Copyright The Hypertrace Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opentelemetry.javaagent.instrumentation.hypertrace.micronaut; + +import io.micronaut.http.HttpResponse; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.MediaType; +import io.micronaut.http.annotation.Body; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import io.micronaut.http.annotation.Post; +import io.reactivex.Flowable; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +@Controller("/") +public class TestController { + + static final String RESPONSE_HEADER_NAME = "reqheadername"; + static final String RESPONSE_HEADER_VALUE = "reqheadervalue"; + static final String RESPONSE_BODY = "{\"key\": \"val\"}"; + + @Get(uri = "/get_no_content") + public HttpResponse getNoContent() { + return HttpResponse.status(HttpStatus.NO_CONTENT) + .header(RESPONSE_HEADER_NAME, RESPONSE_HEADER_VALUE); + } + + @Post(uri = "/post") + public HttpResponse post(@Body String body) { + System.out.printf("Received body: %s", body); + return HttpResponse.status(HttpStatus.OK) + .header(RESPONSE_HEADER_NAME, RESPONSE_HEADER_VALUE) + .characterEncoding(StandardCharsets.US_ASCII) + .contentType(MediaType.APPLICATION_JSON) + .body(RESPONSE_BODY); + } + + @Get(value = "/stream", produces = MediaType.APPLICATION_JSON_STREAM) + public Flowable stream() { + return Flowable.fromIterable(streamBody()); + } + + static List streamBody() { + List entities = new ArrayList<>(); + for (int i = 0; i < 1000; i++) { + entities.add( + String.format( + "dog,cats,foo,bar,dog,cats,foo,bar,dog,cats,foo,bar,dog,cats,foo,bar,dog,cats,foo,bar=%d\n", + i)); + } + return entities; + } +} diff --git a/instrumentation/netty/netty-4.0/src/test/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/AbstractNetty40ServerInstrumentationTest.java b/instrumentation/netty/netty-4.0/src/test/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/AbstractNetty40ServerInstrumentationTest.java index fe9d0923e..f413d1b7e 100644 --- a/instrumentation/netty/netty-4.0/src/test/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/AbstractNetty40ServerInstrumentationTest.java +++ b/instrumentation/netty/netty-4.0/src/test/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_0/AbstractNetty40ServerInstrumentationTest.java @@ -22,16 +22,13 @@ import io.opentelemetry.sdk.trace.data.SpanData; import java.io.IOException; -import java.util.Arrays; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; -import okhttp3.MediaType; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import okio.Buffer; -import okio.BufferedSink; import org.hypertrace.agent.core.instrumentation.HypertraceSemanticAttributes; import org.hypertrace.agent.testing.AbstractInstrumenterTest; import org.junit.jupiter.api.AfterEach; @@ -175,28 +172,4 @@ public void blocking() throws IOException, TimeoutException, InterruptedExceptio .getAttributes() .get(HypertraceSemanticAttributes.httpResponseHeader(RESPONSE_BODY))); } - - private RequestBody requestBody(final boolean chunked, final long size, final int writeSize) { - final byte[] buffer = new byte[writeSize]; - Arrays.fill(buffer, (byte) 'x'); - - return new RequestBody() { - @Override - public MediaType contentType() { - return MediaType.get("application/json; charset=utf-8"); - } - - @Override - public long contentLength() throws IOException { - return chunked ? -1L : size; - } - - @Override - public void writeTo(BufferedSink sink) throws IOException { - for (int count = 0; count < size; count += writeSize) { - sink.write(buffer, 0, (int) Math.min(size - count, writeSize)); - } - } - }; - } } diff --git a/instrumentation/netty/netty-4.1/src/test/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_1/AbstractNetty41ServerInstrumentationTest.java b/instrumentation/netty/netty-4.1/src/test/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_1/AbstractNetty41ServerInstrumentationTest.java index dd8787722..733df202c 100644 --- a/instrumentation/netty/netty-4.1/src/test/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_1/AbstractNetty41ServerInstrumentationTest.java +++ b/instrumentation/netty/netty-4.1/src/test/java/io/opentelemetry/javaagent/instrumentation/hypertrace/netty/v4_1/AbstractNetty41ServerInstrumentationTest.java @@ -22,16 +22,13 @@ import io.opentelemetry.sdk.trace.data.SpanData; import java.io.IOException; -import java.util.Arrays; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; -import okhttp3.MediaType; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import okio.Buffer; -import okio.BufferedSink; import org.hypertrace.agent.core.instrumentation.HypertraceSemanticAttributes; import org.hypertrace.agent.testing.AbstractInstrumenterTest; import org.junit.jupiter.api.AfterEach; @@ -175,28 +172,4 @@ public void blocking() throws IOException, TimeoutException, InterruptedExceptio .getAttributes() .get(HypertraceSemanticAttributes.httpResponseHeader(RESPONSE_BODY))); } - - private RequestBody requestBody(final boolean chunked, final long size, final int writeSize) { - final byte[] buffer = new byte[writeSize]; - Arrays.fill(buffer, (byte) 'x'); - - return new RequestBody() { - @Override - public MediaType contentType() { - return MediaType.get("application/json; charset=utf-8"); - } - - @Override - public long contentLength() throws IOException { - return chunked ? -1L : size; - } - - @Override - public void writeTo(BufferedSink sink) throws IOException { - for (int count = 0; count < size; count += writeSize) { - sink.write(buffer, 0, (int) Math.min(size - count, writeSize)); - } - } - }; - } } diff --git a/instrumentation/vertx-web-3.0/build.gradle.kts b/instrumentation/vertx-web-3.0/build.gradle.kts index 2d8c6ff76..cdb43b739 100644 --- a/instrumentation/vertx-web-3.0/build.gradle.kts +++ b/instrumentation/vertx-web-3.0/build.gradle.kts @@ -24,12 +24,10 @@ afterEvaluate{ val versions: Map by extra dependencies { - api("io.opentelemetry.javaagent.instrumentation:opentelemetry-javaagent-vertx-web-3.0:${versions["opentelemetry_java_agent"]}") - - implementation("io.vertx:vertx-web:3.0.0") - testImplementation(project(":testing-common")) testImplementation(project(":instrumentation:netty:netty-4.0")) testImplementation("io.opentelemetry.javaagent.instrumentation:opentelemetry-javaagent-netty-4.0:${versions["opentelemetry_java_agent"]}") + testImplementation("io.opentelemetry.javaagent.instrumentation:opentelemetry-javaagent-vertx-web-3.0:${versions["opentelemetry_java_agent"]}") + testImplementation("io.vertx:vertx-web:3.0.0") } diff --git a/settings.gradle.kts b/settings.gradle.kts index 66eb8415a..607d04683 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -53,3 +53,5 @@ include("instrumentation:netty:netty-4.1") findProject(":instrumentation:netty:netty-4.1")?.name = "netty-4.1" include("instrumentation:spring:spring-webflux-5.0") findProject(":instrumentation:spring:spring-webflux-5.0")?.name = "spring-webflux-5.0" +include("instrumentation:micronaut-1.0") +findProject(":instrumentation:micronaut-1.0")?.name = "micronaut-1.0" diff --git a/testing-common/src/main/java/org/hypertrace/agent/testing/AbstractInstrumenterTest.java b/testing-common/src/main/java/org/hypertrace/agent/testing/AbstractInstrumenterTest.java index ca837aba7..ce699709c 100644 --- a/testing-common/src/main/java/org/hypertrace/agent/testing/AbstractInstrumenterTest.java +++ b/testing-common/src/main/java/org/hypertrace/agent/testing/AbstractInstrumenterTest.java @@ -24,12 +24,17 @@ import io.opentelemetry.javaagent.tooling.AgentInstaller; import io.opentelemetry.javaagent.tooling.config.ConfigInitializer; import io.opentelemetry.sdk.OpenTelemetrySdk; +import java.io.IOException; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; +import java.util.Arrays; import java.util.Collections; import java.util.concurrent.TimeUnit; import net.bytebuddy.agent.ByteBuddyAgent; +import okhttp3.MediaType; import okhttp3.OkHttpClient; +import okhttp3.RequestBody; +import okio.BufferedSink; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.slf4j.LoggerFactory; @@ -96,4 +101,31 @@ public static void beforeAll() { public void beforeEach() { TEST_WRITER.clear(); } + + public static RequestBody requestBody( + final boolean chunked, final long size, final int writeSize) { + final byte[] buffer = new byte[writeSize]; + Arrays.fill(buffer, (byte) 'x'); + + return new RequestBody() { + @Override + public MediaType contentType() { + return MediaType.get("application/json; charset=utf-8"); + } + + @Override + public long contentLength() throws IOException { + return chunked ? -1L : size; + } + + @Override + public void writeTo(BufferedSink sink) throws IOException { + sink.write("{\"body\":\"".getBytes()); + for (int count = 0; count < size; count += writeSize) { + sink.write(buffer, 0, (int) Math.min(size - count, writeSize)); + } + sink.write("\"}".getBytes()); + } + }; + } }