From 4d51b2b91bec29b7d2291ff145db623e3639e905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladimir=20=C5=A0or?= Date: Mon, 30 Nov 2020 15:47:59 +0200 Subject: [PATCH] Phase1 jetty linux (#46) * PoC for setting app server middleware attributes from splunk distro. * First iteration of jetty test matrix. * Proper end-to-end test for Jetty. * Added copyright headers. * Spotless. * Temporarily build test images before smoke test. * Rename shared to bootstrap and other PR fixes. * Removed middleware submodule. * Verify middleware attributes also in webapp test. * spotless * Add middleware attributes to the server span only. * Add a TODO reference to an important PR. --- agent/build.gradle | 72 +++++++++ agent/build.gradle.kts | 49 ------ bootstrap/build.gradle | 11 ++ .../javaagent/bootstrap/MiddlewareHolder.java | 42 ++++++ custom/build.gradle.kts | 5 + .../MiddlewareAttributeSpanProcessor.java | 71 +++++++++ .../middleware/MiddlewareAttributes.java | 28 ++++ .../MiddlewareBootstrapPackagesProvider.java | 32 ++++ .../MiddlewareTracerCustomizer.java | 30 ++++ instrumentation/build.gradle | 61 ++++++++ instrumentation/jetty/build.gradle | 7 + .../JettyAttributesInstrumentationModule.java | 77 ++++++++++ matrix/build.gradle.kts | 22 ++- .../appservers/javaee/GreetingServlet.java | 71 +++++++++ .../javaee/HeaderDumpingServlet.java | 53 +++++++ matrix/src/main/webapp/WEB-INF/web.xml | 22 +++ settings.gradle.kts | 11 +- smoke-tests/build.gradle.kts | 3 + .../splunk/opentelemetry/AppServerTest.java | 140 ++++++++++++++++++ .../splunk/opentelemetry/JettySmokeTest.java | 52 +++++++ .../com/splunk/opentelemetry/SmokeTest.java | 60 +++----- .../opentelemetry/SpringBootSmokeTest.java | 12 +- .../splunk/opentelemetry/TraceInspector.java | 89 +++++++++++ .../opentelemetry/WebLogicSmokeTest.java | 52 ++++--- 24 files changed, 948 insertions(+), 124 deletions(-) create mode 100644 agent/build.gradle delete mode 100644 agent/build.gradle.kts create mode 100644 bootstrap/build.gradle create mode 100644 bootstrap/src/main/java/com/splunk/opentelemetry/javaagent/bootstrap/MiddlewareHolder.java create mode 100644 custom/src/main/java/com/splunk/opentelemetry/middleware/MiddlewareAttributeSpanProcessor.java create mode 100644 custom/src/main/java/com/splunk/opentelemetry/middleware/MiddlewareAttributes.java create mode 100644 custom/src/main/java/com/splunk/opentelemetry/middleware/MiddlewareBootstrapPackagesProvider.java create mode 100644 custom/src/main/java/com/splunk/opentelemetry/middleware/MiddlewareTracerCustomizer.java create mode 100644 instrumentation/build.gradle create mode 100644 instrumentation/jetty/build.gradle create mode 100644 instrumentation/jetty/src/main/java/com/splunk/opentelemetry/middleware/JettyAttributesInstrumentationModule.java create mode 100644 matrix/src/main/java/com/splunk/opentelemetry/appservers/javaee/GreetingServlet.java create mode 100644 matrix/src/main/java/com/splunk/opentelemetry/appservers/javaee/HeaderDumpingServlet.java create mode 100644 matrix/src/main/webapp/WEB-INF/web.xml create mode 100644 smoke-tests/src/test/java/com/splunk/opentelemetry/AppServerTest.java create mode 100644 smoke-tests/src/test/java/com/splunk/opentelemetry/JettySmokeTest.java create mode 100644 smoke-tests/src/test/java/com/splunk/opentelemetry/TraceInspector.java diff --git a/agent/build.gradle b/agent/build.gradle new file mode 100644 index 000000000..27b9befcf --- /dev/null +++ b/agent/build.gradle @@ -0,0 +1,72 @@ +plugins { + id("com.github.johnrengelman.shadow") version "6.0.0" +} +configurations { + customShadow +} +dependencies { + customShadow project(path: ":custom", configuration: "shadow") + customShadow project(path: ":instrumentation", configuration: "shadow") + implementation "io.opentelemetry.javaagent:opentelemetry-javaagent:${versions.opentelemetryJavaagent}:all" + implementation project(":bootstrap") +} + +archivesBaseName = "splunk-otel-javaagent" + +compileJava { + options.release.set(8) +} + +CopySpec isolateSpec() { + return copySpec { + configurations.customShadow.files.each { + from(zipTree(it)) { + into("inst") + rename("(^.*)\\.class\$", "\$1.classdata") + } + } + } +} + +tasks { + shadowJar { + dependsOn ':custom:shadowJar' + dependsOn ':instrumentation:shadowJar' + dependsOn ':bootstrap:jar' + with isolateSpec() + + /// TODO - with this set to EXCLUDE, service files will not be merged between instrumentation and custom + duplicatesStrategy = DuplicatesStrategy.INCLUDE + + mergeServiceFiles { + include("inst/META-INF/services/*") + } + exclude("**/module-info.class") + + // Prevents conflict with other SLF4J instances. Important for premain. + relocate("org.slf4j", "io.opentelemetry.javaagent.slf4j") + // rewrite dependencies calling Logger.getLogger + relocate("java.util.logging.Logger", "io.opentelemetry.javaagent.bootstrap.PatchLogger") + + // prevents conflict with library instrumentation + relocate("io.opentelemetry.instrumentation.api", "io.opentelemetry.javaagent.shaded.instrumentation.api") + + // relocate OpenTelemetry API + relocate("io.opentelemetry.api", "io.opentelemetry.javaagent.shaded.io.opentelemetry.api") + relocate("io.opentelemetry.context", "io.opentelemetry.javaagent.shaded.io.opentelemetry.context") + + manifest { + attributes.put("Main-Class", "io.opentelemetry.javaagent.OpenTelemetryAgent") + attributes.put("Agent-Class", "com.splunk.opentelemetry.SplunkAgent") + attributes.put("Premain-Class", "com.splunk.opentelemetry.SplunkAgent") + attributes.put("Can-Redefine-Classes", "true") + attributes.put("Can-Retransform-Classes", "true") + attributes.put("Implementation-Vendor", "Splunk") + attributes.put("Implementation-Version", "splunk-${project.version}-otel-${versions["opentelemetryJavaagent"]}") + } + } + + assemble { + dependsOn(shadowJar) + } +} \ No newline at end of file diff --git a/agent/build.gradle.kts b/agent/build.gradle.kts deleted file mode 100644 index ffb390c44..000000000 --- a/agent/build.gradle.kts +++ /dev/null @@ -1,49 +0,0 @@ -plugins { - java - id("com.github.johnrengelman.shadow") version "6.0.0" -} - -val versions: Map by extra - -dependencies { - implementation("io.opentelemetry.javaagent", "opentelemetry-javaagent", version = versions["opentelemetryJavaagent"], classifier = "all") -} - -base.archivesBaseName = "splunk-otel-javaagent" - -tasks { - - compileJava { - options.release.set(8) - } - - processResources { - val customizationShadowTask = project(":custom").tasks.named("shadowJar") - val providerArchive = customizationShadowTask.get().archiveFile - from(zipTree(providerArchive)) { - into("inst") - rename("(^.*)\\.class$", "$1.classdata") - } - dependsOn(customizationShadowTask) - } - - shadowJar { - mergeServiceFiles { - include("inst/META-INF/services/*") - } - exclude("**/module-info.class") - manifest { - attributes.put("Main-Class", "io.opentelemetry.javaagent.OpenTelemetryAgent") - attributes.put("Agent-Class", "com.splunk.opentelemetry.SplunkAgent") - attributes.put("Premain-Class", "com.splunk.opentelemetry.SplunkAgent") - attributes.put("Can-Redefine-Classes", "true") - attributes.put("Can-Retransform-Classes", "true") - attributes.put("Implementation-Vendor", "Splunk") - attributes.put("Implementation-Version", "splunk-${project.version}-otel-${versions["opentelemetryJavaagent"]}") - } - } - - assemble { - dependsOn(shadowJar) - } -} \ No newline at end of file diff --git a/bootstrap/build.gradle b/bootstrap/build.gradle new file mode 100644 index 000000000..44cfc8c79 --- /dev/null +++ b/bootstrap/build.gradle @@ -0,0 +1,11 @@ +plugins { + id "java" +} + +compileJava { + options.release.set(8) +} + +dependencies { + implementation "org.slf4j:slf4j-api:1.7.30" +} diff --git a/bootstrap/src/main/java/com/splunk/opentelemetry/javaagent/bootstrap/MiddlewareHolder.java b/bootstrap/src/main/java/com/splunk/opentelemetry/javaagent/bootstrap/MiddlewareHolder.java new file mode 100644 index 000000000..1bd10662a --- /dev/null +++ b/bootstrap/src/main/java/com/splunk/opentelemetry/javaagent/bootstrap/MiddlewareHolder.java @@ -0,0 +1,42 @@ +/* + * Copyright Splunk Inc. + * + * 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 com.splunk.opentelemetry.javaagent.bootstrap; + +import java.util.concurrent.atomic.AtomicReference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MiddlewareHolder { + + private static final Logger log = LoggerFactory.getLogger(MiddlewareHolder.class); + + public static final AtomicReference middlewareName = new AtomicReference<>(); + public static final AtomicReference middlewareVersion = new AtomicReference<>(); + + public static void trySetName(String name) { + if (!middlewareName.compareAndSet(null, name)) { + log.debug("Trying to re-set middleware name from {} to {}", middlewareName.get(), name); + } + } + + public static void trySetVersion(String version) { + if (!middlewareVersion.compareAndSet(null, version)) { + log.debug( + "Trying to re-set middleware version from {} to {}", middlewareVersion.get(), version); + } + } +} diff --git a/custom/build.gradle.kts b/custom/build.gradle.kts index 6a6c73532..a224f0adc 100644 --- a/custom/build.gradle.kts +++ b/custom/build.gradle.kts @@ -6,9 +6,14 @@ plugins { val versions: Map by extra dependencies { + implementation(project(":bootstrap")) implementation("io.opentelemetry:opentelemetry-sdk:${versions["opentelemetry"]}") implementation("io.opentelemetry:opentelemetry-exporter-jaeger-thrift:${versions["opentelemetry"]}") implementation("io.opentelemetry.javaagent:opentelemetry-javaagent-spi:${versions["opentelemetryJavaagent"]}") + annotationProcessor("com.google.auto.service:auto-service:1.0-rc3") + annotationProcessor("com.google.auto:auto-common:0.8") + implementation("com.google.auto.service:auto-service:1.0-rc3") + implementation("com.google.auto:auto-common:0.8") } tasks { diff --git a/custom/src/main/java/com/splunk/opentelemetry/middleware/MiddlewareAttributeSpanProcessor.java b/custom/src/main/java/com/splunk/opentelemetry/middleware/MiddlewareAttributeSpanProcessor.java new file mode 100644 index 000000000..96434fdb2 --- /dev/null +++ b/custom/src/main/java/com/splunk/opentelemetry/middleware/MiddlewareAttributeSpanProcessor.java @@ -0,0 +1,71 @@ +/* + * Copyright Splunk Inc. + * + * 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 com.splunk.opentelemetry.middleware; + +import com.splunk.opentelemetry.javaagent.bootstrap.MiddlewareHolder; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.trace.ReadWriteSpan; +import io.opentelemetry.sdk.trace.ReadableSpan; +import io.opentelemetry.sdk.trace.SpanProcessor; + +public class MiddlewareAttributeSpanProcessor implements SpanProcessor { + + @Override + public void onStart(Context parentContext, ReadWriteSpan span) { + String middlewareName = MiddlewareHolder.middlewareName.get(); + String middlewareVersion = MiddlewareHolder.middlewareVersion.get(); + // Getting span kind is not the most straightforward or cheap operation apparently. + // TODO: Once this PR is merged and released, use span.getKind directly: + // https://github.com/open-telemetry/opentelemetry-java/pull/2162 + // Let's do quick cheap null checks first and exit quickly. + if ((middlewareName == null && middlewareVersion == null) + || span.toSpanData().getKind() != Span.Kind.SERVER) { + return; + } + if (middlewareName != null) { + span.setAttribute(MiddlewareAttributes.MIDDLEWARE_NAME.key, middlewareName); + } + if (middlewareVersion != null) { + span.setAttribute(MiddlewareAttributes.MIDDLEWARE_VERSION.key, middlewareVersion); + } + } + + @Override + public boolean isStartRequired() { + return true; + } + + @Override + public void onEnd(ReadableSpan span) {} + + @Override + public boolean isEndRequired() { + return false; + } + + @Override + public CompletableResultCode shutdown() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode forceFlush() { + return CompletableResultCode.ofSuccess(); + } +} diff --git a/custom/src/main/java/com/splunk/opentelemetry/middleware/MiddlewareAttributes.java b/custom/src/main/java/com/splunk/opentelemetry/middleware/MiddlewareAttributes.java new file mode 100644 index 000000000..f83efe6ad --- /dev/null +++ b/custom/src/main/java/com/splunk/opentelemetry/middleware/MiddlewareAttributes.java @@ -0,0 +1,28 @@ +/* + * Copyright Splunk Inc. + * + * 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 com.splunk.opentelemetry.middleware; + +public enum MiddlewareAttributes { + MIDDLEWARE_NAME("middleware.name"), + MIDDLEWARE_VERSION("middleware.version"); + + public final String key; + + MiddlewareAttributes(String key) { + this.key = key; + } +} diff --git a/custom/src/main/java/com/splunk/opentelemetry/middleware/MiddlewareBootstrapPackagesProvider.java b/custom/src/main/java/com/splunk/opentelemetry/middleware/MiddlewareBootstrapPackagesProvider.java new file mode 100644 index 000000000..4313ef48b --- /dev/null +++ b/custom/src/main/java/com/splunk/opentelemetry/middleware/MiddlewareBootstrapPackagesProvider.java @@ -0,0 +1,32 @@ +/* + * Copyright Splunk Inc. + * + * 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 com.splunk.opentelemetry.middleware; + +import com.google.auto.service.AutoService; +import com.splunk.opentelemetry.javaagent.bootstrap.MiddlewareHolder; +import io.opentelemetry.javaagent.spi.BootstrapPackagesProvider; +import java.util.Collections; +import java.util.List; + +@AutoService(BootstrapPackagesProvider.class) +public class MiddlewareBootstrapPackagesProvider implements BootstrapPackagesProvider { + + @Override + public List getPackagePrefixes() { + return Collections.singletonList(MiddlewareHolder.class.getPackage().getName()); + } +} diff --git a/custom/src/main/java/com/splunk/opentelemetry/middleware/MiddlewareTracerCustomizer.java b/custom/src/main/java/com/splunk/opentelemetry/middleware/MiddlewareTracerCustomizer.java new file mode 100644 index 000000000..08647d8b9 --- /dev/null +++ b/custom/src/main/java/com/splunk/opentelemetry/middleware/MiddlewareTracerCustomizer.java @@ -0,0 +1,30 @@ +/* + * Copyright Splunk Inc. + * + * 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 com.splunk.opentelemetry.middleware; + +import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.spi.TracerCustomizer; +import io.opentelemetry.sdk.trace.TracerSdkManagement; + +@AutoService(TracerCustomizer.class) +public class MiddlewareTracerCustomizer implements TracerCustomizer { + + @Override + public void configure(TracerSdkManagement tracerManagement) { + tracerManagement.addSpanProcessor(new MiddlewareAttributeSpanProcessor()); + } +} diff --git a/instrumentation/build.gradle b/instrumentation/build.gradle new file mode 100644 index 000000000..81f7de2e7 --- /dev/null +++ b/instrumentation/build.gradle @@ -0,0 +1,61 @@ +plugins { + id("com.github.johnrengelman.shadow") version "6.0.0" +} + +Project instr_project = project +subprojects { + afterEvaluate { Project subProj -> + if (subProj.getPlugins().hasPlugin('java')) { + // Make it so all instrumentation subproject tests can be run with a single command. + instr_project.tasks.test.dependsOn(subProj.tasks.test) + + dependencies { + compileOnly("io.opentelemetry:opentelemetry-sdk:${versions.opentelemetry}") + compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-tooling:${versions.opentelemetryJavaagent}") + compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-spi:${versions.opentelemetryJavaagent}") + compileOnly("net.bytebuddy:byte-buddy:1.10.10") + annotationProcessor("com.google.auto.service:auto-service:1.0-rc3") + annotationProcessor("com.google.auto:auto-common:0.8") + implementation("com.google.auto.service:auto-service:1.0-rc3") + implementation("com.google.auto:auto-common:0.8") + implementation(project(":bootstrap")) + } + + compileJava { + options.release.set(8) + } + + instr_project.dependencies { + implementation(project(subProj.getPath())) + } + } + } +} + +shadowJar { + + mergeServiceFiles() + + exclude '**/module-info.class' + + // Prevents conflict with other SLF4J instances. Important for premain. + relocate 'org.slf4j', 'io.opentelemetry.javaagent.slf4j' + + duplicatesStrategy = DuplicatesStrategy.FAIL + + // rewrite library instrumentation dependencies + relocate("io.opentelemetry.instrumentation", "io.opentelemetry.javaagent.shaded.instrumentation") { + exclude "io.opentelemetry.javaagent.instrumentation.**" + } + + // rewrite dependencies calling Logger.getLogger + relocate 'java.util.logging.Logger', 'io.opentelemetry.javaagent.bootstrap.PatchLogger' + + // prevents conflict with library instrumentation + relocate 'io.opentelemetry.instrumentation.api', 'io.opentelemetry.javaagent.shaded.instrumentation.api' + + // relocate OpenTelemetry API usage + relocate "io.opentelemetry.api", "io.opentelemetry.javaagent.shaded.io.opentelemetry.api" + relocate "io.opentelemetry.context", "io.opentelemetry.javaagent.shaded.io.opentelemetry.context" + +} \ No newline at end of file diff --git a/instrumentation/jetty/build.gradle b/instrumentation/jetty/build.gradle new file mode 100644 index 000000000..d7e878448 --- /dev/null +++ b/instrumentation/jetty/build.gradle @@ -0,0 +1,7 @@ +plugins { + id "java" +} + +dependencies { + compileOnly('org.eclipse.jetty:jetty-server:9.4.35.v20201120') +} \ No newline at end of file diff --git a/instrumentation/jetty/src/main/java/com/splunk/opentelemetry/middleware/JettyAttributesInstrumentationModule.java b/instrumentation/jetty/src/main/java/com/splunk/opentelemetry/middleware/JettyAttributesInstrumentationModule.java new file mode 100644 index 000000000..4a808c9f7 --- /dev/null +++ b/instrumentation/jetty/src/main/java/com/splunk/opentelemetry/middleware/JettyAttributesInstrumentationModule.java @@ -0,0 +1,77 @@ +/* + * Copyright Splunk Inc. + * + * 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 com.splunk.opentelemetry.middleware; + +import static io.opentelemetry.javaagent.tooling.ClassLoaderMatcher.hasClassesNamed; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isProtected; +import static net.bytebuddy.matcher.ElementMatchers.named; + +import com.google.auto.service.AutoService; +import com.splunk.opentelemetry.javaagent.bootstrap.MiddlewareHolder; +import io.opentelemetry.javaagent.tooling.InstrumentationModule; +import io.opentelemetry.javaagent.tooling.TypeInstrumentation; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.eclipse.jetty.server.Server; + +@AutoService(InstrumentationModule.class) +public class JettyAttributesInstrumentationModule extends InstrumentationModule { + + public JettyAttributesInstrumentationModule() { + super("jetty"); + } + + @Override + public ElementMatcher.Junction classLoaderMatcher() { + return hasClassesNamed("org.eclipse.jetty.server.Server"); + } + + @Override + public List typeInstrumentations() { + return Collections.singletonList(new Instrumentation()); + } + + public static class Instrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher typeMatcher() { + return named("org.eclipse.jetty.server.Server"); + } + + @Override + public Map, String> transformers() { + return Collections.singletonMap( + isMethod().and(isProtected()).and(named("doStart")), + JettyAttributesInstrumentationModule.class.getName() + "$MiddlewareInitializedAdvice"); + } + } + + @SuppressWarnings("unused") + public static class MiddlewareInitializedAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter() { + MiddlewareHolder.trySetVersion(Server.getVersion()); + MiddlewareHolder.trySetName("jetty"); + } + } +} diff --git a/matrix/build.gradle.kts b/matrix/build.gradle.kts index 931fbd4cd..daa4ab1de 100644 --- a/matrix/build.gradle.kts +++ b/matrix/build.gradle.kts @@ -3,16 +3,30 @@ import org.apache.tools.ant.filters.ReplaceTokens plugins { base + war id("com.bmuschko.docker-remote-api") version "6.6.1" } +tasks { + compileJava { + options.release.set(8) + } +} + +val versions: Map by extra + +dependencies { + implementation("javax.servlet:javax.servlet-api:3.0.1") + implementation("io.opentelemetry:opentelemetry-extension-auto-annotations:${versions["opentelemetry"]}") +} + fun dockerFileName(template: String) = template.replace("-dockerfile.template", ".dockerfile") val dockerWorkingDir = project.buildDir.resolve("docker") val buildTestImagesTask = tasks.create("buildTestImages") { group = "build" - description = "Builds all Docker images for test matrix" + description = "Builds all Docker images for the test matrix" } val targets = mapOf( @@ -47,12 +61,16 @@ targets.forEach { (server, data) -> val template = "$server-dockerfile.template" val prepareTask = tasks.register("${server}ImagePrepare-$version-jdk$jdk", Copy::class) { + val warTask = project.tasks.named("war").get() + dependsOn(warTask) into(dockerWorkingDir) from("src") { filter(ReplaceTokens::class, "tokens" to mapOf("version" to version, "jdk" to jdk)) rename { f -> dockerFileName(f) } } - from("app.war") + from(warTask.archiveFile) { + rename { _ -> "app.war" } + } } val buildTask = tasks.register("${server}Image-$version-jdk$jdk", DockerBuildImage::class) { diff --git a/matrix/src/main/java/com/splunk/opentelemetry/appservers/javaee/GreetingServlet.java b/matrix/src/main/java/com/splunk/opentelemetry/appservers/javaee/GreetingServlet.java new file mode 100644 index 000000000..405c90ef1 --- /dev/null +++ b/matrix/src/main/java/com/splunk/opentelemetry/appservers/javaee/GreetingServlet.java @@ -0,0 +1,71 @@ +/* + * Copyright Splunk Inc. + * + * 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 com.splunk.opentelemetry.appservers.javaee; + +import io.opentelemetry.extension.auto.annotations.WithSpan; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.net.URLConnection; +import java.util.Objects; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class GreetingServlet extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { + URL url = new URL(req.getParameter("url")); + URLConnection urlConnection = url.openConnection(); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + try (InputStream remoteInputStream = urlConnection.getInputStream()) { + long bytesRead = transfer(remoteInputStream, buffer); + String responseBody = buffer.toString("UTF-8"); + ServletOutputStream outputStream = resp.getOutputStream(); + outputStream.print( + withSpan( + bytesRead + + " bytes read by " + + urlConnection.getClass().getName() + + "\n" + + responseBody)); + outputStream.flush(); + } + } + + @WithSpan + public String withSpan(String responseBody) { + return responseBody; + } + + // We have to run on Java 8, so no Java 9 stream transfer goodies for us. + private long transfer(InputStream from, OutputStream to) throws IOException { + Objects.requireNonNull(to, "out"); + long transferred = 0; + byte[] buffer = new byte[65535]; + int read; + while ((read = from.read(buffer, 0, buffer.length)) >= 0) { + to.write(buffer, 0, read); + transferred += read; + } + return transferred; + } +} diff --git a/matrix/src/main/java/com/splunk/opentelemetry/appservers/javaee/HeaderDumpingServlet.java b/matrix/src/main/java/com/splunk/opentelemetry/appservers/javaee/HeaderDumpingServlet.java new file mode 100644 index 000000000..b4a87da0a --- /dev/null +++ b/matrix/src/main/java/com/splunk/opentelemetry/appservers/javaee/HeaderDumpingServlet.java @@ -0,0 +1,53 @@ +/* + * Copyright Splunk Inc. + * + * 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 com.splunk.opentelemetry.appservers.javaee; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class HeaderDumpingServlet extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { + PrintWriter response = resp.getWriter(); + Enumeration headerNames = req.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String headerName = headerNames.nextElement(); + response.write(headerName + ": "); + + List headers = Collections.list(req.getHeaders(headerName)); + if (headers.size() == 1) { + response.write(headers.get(0)); + } else { + response.write("["); + for (String header : headers) { + response.write(" " + header + ",\n"); + } + response.write("]"); + } + response.write("\n"); + } + + response.flush(); + } +} diff --git a/matrix/src/main/webapp/WEB-INF/web.xml b/matrix/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..aa0a0754e --- /dev/null +++ b/matrix/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,22 @@ + + + Headers + com.splunk.opentelemetry.appservers.javaee.HeaderDumpingServlet + + + Greeting + com.splunk.opentelemetry.appservers.javaee.GreetingServlet + + + Headers + /headers + + + Greeting + /greeting + + \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index f809bea3a..9eab6646d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -9,7 +9,10 @@ gradleEnterprise { } } rootProject.name = "splunk-otel-java" -include("agent", "custom", "smoke-tests", "matrix") - - - +include("agent", + "bootstrap", + "custom", + "instrumentation", + "instrumentation:jetty", + "smoke-tests", + "matrix") \ No newline at end of file diff --git a/smoke-tests/build.gradle.kts b/smoke-tests/build.gradle.kts index 93b13b069..e4fc4132b 100644 --- a/smoke-tests/build.gradle.kts +++ b/smoke-tests/build.gradle.kts @@ -15,6 +15,9 @@ dependencies { } tasks.test { + // TODO: remove once we will have the environment to push built images to. + dependsOn(":matrix:buildTestImages") + useJUnitPlatform() reports { junitXml.isOutputPerTestCase = true diff --git a/smoke-tests/src/test/java/com/splunk/opentelemetry/AppServerTest.java b/smoke-tests/src/test/java/com/splunk/opentelemetry/AppServerTest.java new file mode 100644 index 000000000..aa73c52ac --- /dev/null +++ b/smoke-tests/src/test/java/com/splunk/opentelemetry/AppServerTest.java @@ -0,0 +1,140 @@ +/* + * Copyright Splunk Inc. + * + * 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 com.splunk.opentelemetry; + +import io.opentelemetry.proto.trace.v1.Span; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Set; +import okhttp3.Request; +import okhttp3.Response; +import org.junit.jupiter.api.Assertions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class AppServerTest extends SmokeTest { + + private static final Logger log = LoggerFactory.getLogger(AppServerTest.class); + + /** + * The test case is expected to create and verify the following trace: + * 1. Server span for the initial request to http://localhost:%d/greeting?url=http://localhost:8080/headers + * 2. Client http span to http://localhost:8080/headers + * 3. Server http span for http://localhost:8080/headers + * 4. Span created by the @WithSpan annotation. + * + */ + protected void assertWebAppTrace(ExpectedServerAttributes serverAttributes) + throws IOException, InterruptedException { + String url = + String.format( + "http://localhost:%d/greeting?url=http://localhost:8080/headers", + target.getMappedPort(8080)); + + Request request = new Request.Builder().get().url(url).build(); + Response response = client.newCall(request).execute(); + + TraceInspector traces = waitForTraces(); + + Set traceIds = traces.getTraceIds(); + + Assertions.assertEquals(traceIds.size(), 1, "There is one trace"); + String theOneTraceId = new ArrayList<>(traceIds).get(0); + + String responseBody = response.body().string(); + + Assertions.assertTrue( + responseBody.contains(theOneTraceId), + "trace id is present in the HTTP headers as reported by the called endpoint"); + + Assertions.assertEquals( + 2, + traces.countSpansByKind(Span.SpanKind.SPAN_KIND_SERVER), + "Server spans in the distributed trace"); + Assertions.assertEquals( + 2, + traces.countFilteredAttributes("middleware.name", serverAttributes.middlewareName), + "Middleware name is present on all server spans"); + Assertions.assertEquals( + 2, + traces.countFilteredAttributes("middleware.version", serverAttributes.middlewareVersion), + "Middleware version is present on all server spans"); + + Assertions.assertEquals( + 1, traces.countFilteredAttributes("http.url", url), "The span for the initial web request"); + Assertions.assertEquals( + 2, + traces.countFilteredAttributes("http.url", "http://localhost:8080/headers"), + "Client and server spans for the remote call"); + + Assertions.assertEquals( + 1, traces.countSpansByName("GreetingServlet.withSpan"), "Span for the annotated method"); + + Assertions.assertEquals( + 4, + traces.countFilteredAttributes("otel.library.version", getCurrentAgentVersion()), + "Number of spans tagged with current otel library version"); + } + + protected void assertServerHandler(ExpectedServerAttributes serverAttributes) + throws IOException, InterruptedException { + String url = + String.format( + "http://localhost:%d/this-is-definitely-not-there-but-there-should-be-a-trace-nevertheless", + target.getMappedPort(8080)); + + Request request = new Request.Builder().get().url(url).build(); + Response response = client.newCall(request).execute(); + log.debug("Response for non-existing page: {}", response.body().string()); + Assertions.assertEquals( + 404, + response.code(), + "404 response code is expected from the app-server for a request to a non-existing page."); + var traces = waitForTraces(); + + Assertions.assertEquals(1, traces.size(), "There is one trace from server handler"); + + Assertions.assertEquals( + 1, + traces.countSpansByName(serverAttributes.handlerSpanName), + "Server span has expected name"); + + Assertions.assertEquals( + serverAttributes.middlewareName, + traces.getServerSpanAttribute("middleware.name"), + "Middleware name tag on server span"); + Assertions.assertEquals( + serverAttributes.middlewareVersion, + traces.getServerSpanAttribute("middleware.version"), + "Middleware version tag on server span"); + + resetBackend(); + } + + protected static class ExpectedServerAttributes { + final String handlerSpanName; + final String middlewareName; + final String middlewareVersion; + + public ExpectedServerAttributes( + String handlerSpanName, String middlewareName, String middlewareVersion) { + this.handlerSpanName = handlerSpanName; + this.middlewareName = middlewareName; + this.middlewareVersion = middlewareVersion; + } + } +} diff --git a/smoke-tests/src/test/java/com/splunk/opentelemetry/JettySmokeTest.java b/smoke-tests/src/test/java/com/splunk/opentelemetry/JettySmokeTest.java new file mode 100644 index 000000000..59f063aed --- /dev/null +++ b/smoke-tests/src/test/java/com/splunk/opentelemetry/JettySmokeTest.java @@ -0,0 +1,52 @@ +/* + * Copyright Splunk Inc. + * + * 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 com.splunk.opentelemetry; + +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import java.io.IOException; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class JettySmokeTest extends AppServerTest { + + public static final ExpectedServerAttributes JETTY9_SERVER_ATTRIBUTES = + new ExpectedServerAttributes("HandlerCollection.handle", "jetty", "9.4.35.v20201120"); + public static final ExpectedServerAttributes JETTY10_SERVER_ATTRIBUTES = + new ExpectedServerAttributes("HandlerList.handle", "jetty", "10.0.0.beta3"); + + private static Stream supportedConfigurations() { + return Stream.of( + arguments("splunk-jetty:9.4-jdk8", JETTY9_SERVER_ATTRIBUTES), + arguments("splunk-jetty:9.4-jdk11", JETTY9_SERVER_ATTRIBUTES), + arguments("splunk-jetty:9.4-jdk15", JETTY9_SERVER_ATTRIBUTES), + arguments("splunk-jetty:10.0.0.beta3-jdk11", JETTY10_SERVER_ATTRIBUTES), + arguments("splunk-jetty:10.0.0.beta3-jdk15", JETTY10_SERVER_ATTRIBUTES)); + } + + @ParameterizedTest + @MethodSource("supportedConfigurations") + void jettySmokeTest(String imageName, ExpectedServerAttributes expectedServerAttributes) + throws IOException, InterruptedException { + startTarget(imageName); + + assertServerHandler(expectedServerAttributes); + assertWebAppTrace(expectedServerAttributes); + } +} diff --git a/smoke-tests/src/test/java/com/splunk/opentelemetry/SmokeTest.java b/smoke-tests/src/test/java/com/splunk/opentelemetry/SmokeTest.java index 73df3c657..4f97e9bf3 100644 --- a/smoke-tests/src/test/java/com/splunk/opentelemetry/SmokeTest.java +++ b/smoke-tests/src/test/java/com/splunk/opentelemetry/SmokeTest.java @@ -23,7 +23,6 @@ import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; import io.opentelemetry.proto.common.v1.AnyValue; import io.opentelemetry.proto.common.v1.KeyValue; -import io.opentelemetry.proto.trace.v1.Span; import java.io.IOException; import java.time.Duration; import java.util.Collection; @@ -113,6 +112,10 @@ void startTarget(String targetImageName) { @AfterEach void cleanup() throws IOException { + resetBackend(); + } + + protected void resetBackend() throws IOException { client .newCall( new Request.Builder() @@ -143,36 +146,25 @@ protected static Stream findResourceAttribute( .map(KeyValue::getValue); } - protected static int countSpansByName( - Collection traces, String spanName) { - return (int) getSpanStream(traces).filter(it -> it.getName().equals(spanName)).count(); - } - - protected static Stream getSpanStream(Collection traces) { - return traces.stream() - .flatMap(it -> it.getResourceSpansList().stream()) - .flatMap(it -> it.getInstrumentationLibrarySpansList().stream()) - .flatMap(it -> it.getSpansList().stream()); - } - - protected Collection waitForTraces() - throws IOException, InterruptedException { + protected TraceInspector waitForTraces() throws IOException, InterruptedException { String content = waitForContent(); - return StreamSupport.stream(OBJECT_MAPPER.readTree(content).spliterator(), false) - .map( - it -> { - ExportTraceServiceRequest.Builder builder = ExportTraceServiceRequest.newBuilder(); - // TODO(anuraaga): Register parser into object mapper to avoid de -> re -> - // deserialize. - try { - JsonFormat.parser().merge(OBJECT_MAPPER.writeValueAsString(it), builder); - } catch (InvalidProtocolBufferException | JsonProcessingException e) { - e.printStackTrace(); - } - return builder.build(); - }) - .collect(Collectors.toList()); + return new TraceInspector( + StreamSupport.stream(OBJECT_MAPPER.readTree(content).spliterator(), false) + .map( + it -> { + ExportTraceServiceRequest.Builder builder = + ExportTraceServiceRequest.newBuilder(); + // TODO(anuraaga): Register parser into object mapper to avoid de -> re -> + // deserialize. + try { + JsonFormat.parser().merge(OBJECT_MAPPER.writeValueAsString(it), builder); + } catch (InvalidProtocolBufferException | JsonProcessingException e) { + e.printStackTrace(); + } + return builder.build(); + }) + .collect(Collectors.toList())); } private String waitForContent() throws IOException, InterruptedException { @@ -201,16 +193,6 @@ private String waitForContent() throws IOException, InterruptedException { return content; } - protected long countFilteredAttributes( - Collection traces, String attributeName, Object attributeValue) { - return getSpanStream(traces) - .flatMap(s -> s.getAttributesList().stream()) - .filter(a -> a.getKey().equals(attributeName)) - .map(a -> a.getValue().getStringValue()) - .filter(s -> s.equals(attributeValue)) - .count(); - } - protected String getCurrentAgentVersion() throws IOException { return new JarFile(agentPath) .getManifest() diff --git a/smoke-tests/src/test/java/com/splunk/opentelemetry/SpringBootSmokeTest.java b/smoke-tests/src/test/java/com/splunk/opentelemetry/SpringBootSmokeTest.java index 983c2e36e..7159efb40 100644 --- a/smoke-tests/src/test/java/com/splunk/opentelemetry/SpringBootSmokeTest.java +++ b/smoke-tests/src/test/java/com/splunk/opentelemetry/SpringBootSmokeTest.java @@ -16,9 +16,7 @@ package com.splunk.opentelemetry; -import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; import java.io.IOException; -import java.util.Collection; import okhttp3.Request; import okhttp3.Response; import org.junit.jupiter.api.Assertions; @@ -44,14 +42,14 @@ public void springBootSmokeTestOnJDK(int jdk) throws IOException, InterruptedExc String currentAgentVersion = getCurrentAgentVersion(); Response response = client.newCall(request).execute(); - Collection traces = waitForTraces(); + TraceInspector traces = waitForTraces(); Assertions.assertEquals(response.body().string(), "Hi!"); - Assertions.assertEquals(1, countSpansByName(traces, "/greeting")); - Assertions.assertEquals(1, countSpansByName(traces, "WebController.greeting")); - Assertions.assertEquals(1, countSpansByName(traces, "WebController.withSpan")); + Assertions.assertEquals(1, traces.countSpansByName("/greeting")); + Assertions.assertEquals(1, traces.countSpansByName("WebController.greeting")); + Assertions.assertEquals(1, traces.countSpansByName("WebController.withSpan")); Assertions.assertEquals( - 3, countFilteredAttributes(traces, "otel.library.version", currentAgentVersion)); + 3, traces.countFilteredAttributes("otel.library.version", currentAgentVersion)); stopTarget(); } diff --git a/smoke-tests/src/test/java/com/splunk/opentelemetry/TraceInspector.java b/smoke-tests/src/test/java/com/splunk/opentelemetry/TraceInspector.java new file mode 100644 index 000000000..09fd43046 --- /dev/null +++ b/smoke-tests/src/test/java/com/splunk/opentelemetry/TraceInspector.java @@ -0,0 +1,89 @@ +/* + * Copyright Splunk Inc. + * + * 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 com.splunk.opentelemetry; + +import com.google.protobuf.ByteString; +import io.opentelemetry.api.trace.TraceId; +import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; +import io.opentelemetry.proto.trace.v1.Span; +import java.util.Collection; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class TraceInspector { + final Collection traces; + + public TraceInspector(Collection traces) { + this.traces = traces; + } + + public Stream getSpanStream() { + return traces.stream() + .flatMap(it -> it.getResourceSpansList().stream()) + .flatMap(it -> it.getInstrumentationLibrarySpansList().stream()) + .flatMap(it -> it.getSpansList().stream()); + } + + public long countFilteredAttributes(String attributeName, Object attributeValue) { + return getSpanStream() + .flatMap(s -> s.getAttributesList().stream()) + .filter(a -> a.getKey().equals(attributeName)) + .map(a -> a.getValue().getStringValue()) + .filter(s -> s.equals(attributeValue)) + .count(); + } + + protected int countSpansByName(String spanName) { + return (int) getSpanStream().filter(it -> it.getName().equals(spanName)).count(); + } + + protected int countSpansByKind(Span.SpanKind spanKind) { + return (int) getSpanStream().filter(it -> it.getKind().equals(spanKind)).count(); + } + + public int size() { + return traces.size(); + } + + public Set getTraceIds() { + return getSpanStream() + .map(Span::getTraceId) + .map(ByteString::toByteArray) + .map(TraceId::bytesToHex) + .collect(Collectors.toSet()); + } + + /** + * This method returns the value for the requested attribute of the *first* server span. Be + * careful when using on a distributed trace with several server spans. + */ + public String getServerSpanAttribute(String attributeKey) { + return getSpanStream() + .filter(span -> span.getKind() == Span.SpanKind.SPAN_KIND_SERVER) + .map(Span::getAttributesList) + .flatMap(Collection::stream) + .filter(attr -> attributeKey.equals(attr.getKey())) + .map(keyValue -> keyValue.getValue().getStringValue()) + .findFirst() + .orElseThrow( + () -> + new NoSuchElementException( + "Attribute " + attributeKey + " is not found on server span")); + } +} diff --git a/smoke-tests/src/test/java/com/splunk/opentelemetry/WebLogicSmokeTest.java b/smoke-tests/src/test/java/com/splunk/opentelemetry/WebLogicSmokeTest.java index 8842da72e..b633918d5 100644 --- a/smoke-tests/src/test/java/com/splunk/opentelemetry/WebLogicSmokeTest.java +++ b/smoke-tests/src/test/java/com/splunk/opentelemetry/WebLogicSmokeTest.java @@ -21,10 +21,9 @@ import com.google.protobuf.ByteString; import io.opentelemetry.api.trace.TraceId; -import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; import io.opentelemetry.proto.trace.v1.Span; import java.io.IOException; -import java.util.Collection; +import java.util.ArrayList; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -41,7 +40,7 @@ * there are no WebLogic images installed locally. See the manual in src/weblogic directory for * instructions on how to build required images. */ -class WebLogicSmokeTest extends SmokeTest { +class WebLogicSmokeTest extends AppServerTest { private static Stream supportedWlsConfigurations() { return Stream.of( @@ -60,29 +59,38 @@ public void webLogicSmokeTest(WebLogicConfiguration wlsConfig) startTarget(wlsConfig.getImageName()); + // FIXME: APMI-1300 + // assertServerHandler(new ExpectedServerAttributes("HandlerCollection.handle", "weblogic", + // "12.1")); + + assertWebAppTrace(null); + + stopTarget(); + } + + @Override + protected void assertWebAppTrace(ExpectedServerAttributes serverAttributes) + throws IOException, InterruptedException { String url = String.format( "http://localhost:%d/wls-demo/greetingRemote?url=http://localhost:8080/wls-demo/headers", target.getMappedPort(8080)); Request request = new Request.Builder().get().url(url).build(); - - String currentAgentVersion = getCurrentAgentVersion(); - Response response = client.newCall(request).execute(); - Collection traces = waitForTraces(); - - Set traceIds = - getSpanStream(traces).map(Span::getTraceId).collect(Collectors.toSet()); - Assertions.assertEquals(traceIds.size(), 1, "There is one trace"); + TraceInspector traces = waitForTraces(); - String theOneTraceId = - traceIds.stream() - .findFirst() + Set traceIds = + traces + .getSpanStream() + .map(Span::getTraceId) .map(ByteString::toByteArray) .map(TraceId::bytesToHex) - .orElseThrow(AssertionError::new); + .collect(Collectors.toSet()); + + Assertions.assertEquals(traceIds.size(), 1, "There is one trace"); + String theOneTraceId = new ArrayList<>(traceIds).get(0); String responseBody = response.body().string(); Assertions.assertTrue( @@ -94,28 +102,26 @@ public void webLogicSmokeTest(WebLogicConfiguration wlsConfig) Assertions.assertEquals( 1, - countSpansByName(traces, "/wls-demo/greetingRemote"), + traces.countSpansByName("/wls-demo/greetingRemote"), "The span for the initial web request"); Assertions.assertEquals( 1, - countSpansByName(traces, "/wls-demo/headers"), + traces.countSpansByName("/wls-demo/headers"), "The span for the web request called from the controller"); Assertions.assertEquals( 1, - countSpansByName(traces, "TheController.showRequestHeaders"), + traces.countSpansByName("TheController.showRequestHeaders"), "The span for the web framework controller"); Assertions.assertEquals( 1, - countSpansByName(traces, "TheController.sayRemoteHello"), + traces.countSpansByName("TheController.sayRemoteHello"), "The span for the web framework controller"); Assertions.assertEquals( - 1, countSpansByName(traces, "TheController.withSpan"), "Spans for the annotated methods"); + 1, traces.countSpansByName("TheController.withSpan"), "Spans for the annotated methods"); Assertions.assertEquals( 6, - countFilteredAttributes(traces, "otel.library.version", currentAgentVersion), + traces.countFilteredAttributes("otel.library.version", getCurrentAgentVersion()), "Number of spans tagged with current otel library version"); - - stopTarget(); } static class WebLogicConfiguration {