diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md index 74bea2ded278..12ebadfb9b5d 100644 --- a/docs/supported-libraries.md +++ b/docs/supported-libraries.md @@ -39,7 +39,7 @@ These are the supported libraries and frameworks: | [Apache Pulsar](https://pulsar.apache.org/) | 2.8+ | N/A | [Messaging Spans] | | [Apache RocketMQ gRPC/Protobuf-based Client](https://rocketmq.apache.org/) | 5.0+ | N/A | [Messaging Spans] | | [Apache RocketMQ Remoting-based Client](https://rocketmq.apache.org/) | 4.8+ | [opentelemetry-rocketmq-client-4.8](../instrumentation/rocketmq/rocketmq-client/rocketmq-client-4.8/library) | [Messaging Spans] | -| [Apache Struts 2](https://github.com/apache/struts) | 2.3+ | N/A | Provides `http.route` [2], Controller Spans [3] | +| [Apache Struts](https://github.com/apache/struts) | 2.3+ | N/A | Provides `http.route` [2], Controller Spans [3] | | [Apache Tapestry](https://tapestry.apache.org/) | 5.4+ | N/A | Provides `http.route` [2], Controller Spans [3] | | [Apache Wicket](https://wicket.apache.org/) | 8.0+ | N/A | Provides `http.route` [2] | | [Armeria](https://armeria.dev) | 1.3+ | [opentelemetry-armeria-1.3](../instrumentation/armeria/armeria-1.3/library) | [HTTP Client Spans], [HTTP Client Metrics], [HTTP Server Spans], [HTTP Server Metrics] | diff --git a/instrumentation/struts-2.3/javaagent/build.gradle.kts b/instrumentation/struts/struts-2.3/javaagent/build.gradle.kts similarity index 88% rename from instrumentation/struts-2.3/javaagent/build.gradle.kts rename to instrumentation/struts/struts-2.3/javaagent/build.gradle.kts index bdb8a442dda5..4dabe129d9a3 100644 --- a/instrumentation/struts-2.3/javaagent/build.gradle.kts +++ b/instrumentation/struts/struts-2.3/javaagent/build.gradle.kts @@ -6,7 +6,8 @@ muzzle { pass { group.set("org.apache.struts") module.set("struts2-core") - versions.set("[2.3.1,)") + versions.set("[2.1.0,7)") + assertInverse.set(true) } } @@ -24,6 +25,7 @@ dependencies { testInstrumentation(project(":instrumentation:servlet:servlet-3.0:javaagent")) testInstrumentation(project(":instrumentation:servlet:servlet-javax-common:javaagent")) testInstrumentation(project(":instrumentation:jetty:jetty-8.0:javaagent")) + testInstrumentation(project(":instrumentation:struts:struts-7.0:javaagent")) latestDepTestLibrary("org.apache.struts:struts2-core:6.0.+") } diff --git a/instrumentation/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts2/ActionInvocationInstrumentation.java b/instrumentation/struts/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v2_3/ActionInvocationInstrumentation.java similarity index 94% rename from instrumentation/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts2/ActionInvocationInstrumentation.java rename to instrumentation/struts/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v2_3/ActionInvocationInstrumentation.java index e8ed9f464c47..8c831dfd2394 100644 --- a/instrumentation/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts2/ActionInvocationInstrumentation.java +++ b/instrumentation/struts/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v2_3/ActionInvocationInstrumentation.java @@ -3,12 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.struts2; +package io.opentelemetry.javaagent.instrumentation.struts.v2_3; import static io.opentelemetry.instrumentation.api.semconv.http.HttpServerRouteSource.CONTROLLER; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; -import static io.opentelemetry.javaagent.instrumentation.struts2.StrutsSingletons.instrumenter; +import static io.opentelemetry.javaagent.instrumentation.struts.v2_3.StrutsSingletons.instrumenter; import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.isPublic; import static net.bytebuddy.matcher.ElementMatchers.named; diff --git a/instrumentation/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts2/Struts2InstrumentationModule.java b/instrumentation/struts/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v2_3/Struts2InstrumentationModule.java similarity index 91% rename from instrumentation/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts2/Struts2InstrumentationModule.java rename to instrumentation/struts/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v2_3/Struts2InstrumentationModule.java index 82d00204fc99..d44b85d81229 100644 --- a/instrumentation/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts2/Struts2InstrumentationModule.java +++ b/instrumentation/struts/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v2_3/Struts2InstrumentationModule.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.struts2; +package io.opentelemetry.javaagent.instrumentation.struts.v2_3; import static java.util.Collections.singletonList; diff --git a/instrumentation/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts2/StrutsCodeAttributesGetter.java b/instrumentation/struts/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v2_3/StrutsCodeAttributesGetter.java similarity index 90% rename from instrumentation/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts2/StrutsCodeAttributesGetter.java rename to instrumentation/struts/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v2_3/StrutsCodeAttributesGetter.java index 669682173074..a6f21b3f3b7a 100644 --- a/instrumentation/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts2/StrutsCodeAttributesGetter.java +++ b/instrumentation/struts/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v2_3/StrutsCodeAttributesGetter.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.struts2; +package io.opentelemetry.javaagent.instrumentation.struts.v2_3; import com.opensymphony.xwork2.ActionInvocation; import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesGetter; diff --git a/instrumentation/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts2/StrutsServerSpanNaming.java b/instrumentation/struts/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v2_3/StrutsServerSpanNaming.java similarity index 94% rename from instrumentation/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts2/StrutsServerSpanNaming.java rename to instrumentation/struts/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v2_3/StrutsServerSpanNaming.java index 5bf7240bf31e..1d500ef201cf 100644 --- a/instrumentation/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts2/StrutsServerSpanNaming.java +++ b/instrumentation/struts/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v2_3/StrutsServerSpanNaming.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.struts2; +package io.opentelemetry.javaagent.instrumentation.struts.v2_3; import com.opensymphony.xwork2.ActionProxy; import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRouteGetter; diff --git a/instrumentation/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts2/StrutsSingletons.java b/instrumentation/struts/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v2_3/StrutsSingletons.java similarity index 95% rename from instrumentation/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts2/StrutsSingletons.java rename to instrumentation/struts/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v2_3/StrutsSingletons.java index fc1afa0472b0..913db3c99bc1 100644 --- a/instrumentation/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts2/StrutsSingletons.java +++ b/instrumentation/struts/struts-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v2_3/StrutsSingletons.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.struts2; +package io.opentelemetry.javaagent.instrumentation.struts.v2_3; import com.opensymphony.xwork2.ActionInvocation; import io.opentelemetry.api.GlobalOpenTelemetry; diff --git a/instrumentation/struts-2.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/struts2/GreetingAction.java b/instrumentation/struts/struts-2.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/struts/v2_3/GreetingAction.java similarity index 97% rename from instrumentation/struts-2.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/struts2/GreetingAction.java rename to instrumentation/struts/struts-2.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/struts/v2_3/GreetingAction.java index 9741ab90b555..21abf1d876e0 100644 --- a/instrumentation/struts-2.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/struts2/GreetingAction.java +++ b/instrumentation/struts/struts-2.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/struts/v2_3/GreetingAction.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.struts2; +package io.opentelemetry.javaagent.instrumentation.struts.v2_3; import static io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest.controller; diff --git a/instrumentation/struts-2.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/struts2/GreetingServlet.java b/instrumentation/struts/struts-2.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/struts/v2_3/GreetingServlet.java similarity index 87% rename from instrumentation/struts-2.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/struts2/GreetingServlet.java rename to instrumentation/struts/struts-2.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/struts/v2_3/GreetingServlet.java index 2f480affbe7e..d82474b4f65b 100644 --- a/instrumentation/struts-2.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/struts2/GreetingServlet.java +++ b/instrumentation/struts/struts-2.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/struts/v2_3/GreetingServlet.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.struts2; +package io.opentelemetry.javaagent.instrumentation.struts.v2_3; import java.io.IOException; import javax.servlet.http.HttpServlet; diff --git a/instrumentation/struts-2.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/struts2/Struts2ActionSpanTest.java b/instrumentation/struts/struts-2.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/struts/v2_3/Struts2ActionSpanTest.java similarity index 99% rename from instrumentation/struts-2.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/struts2/Struts2ActionSpanTest.java rename to instrumentation/struts/struts-2.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/struts/v2_3/Struts2ActionSpanTest.java index f7d91eed52b1..e4c756910a2c 100644 --- a/instrumentation/struts-2.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/struts2/Struts2ActionSpanTest.java +++ b/instrumentation/struts/struts-2.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/struts/v2_3/Struts2ActionSpanTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.struts2; +package io.opentelemetry.javaagent.instrumentation.struts.v2_3; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; diff --git a/instrumentation/struts-2.3/javaagent/src/test/resources/greeting.ftl b/instrumentation/struts/struts-2.3/javaagent/src/test/resources/greeting.ftl similarity index 100% rename from instrumentation/struts-2.3/javaagent/src/test/resources/greeting.ftl rename to instrumentation/struts/struts-2.3/javaagent/src/test/resources/greeting.ftl diff --git a/instrumentation/struts-2.3/javaagent/src/test/resources/struts.xml b/instrumentation/struts/struts-2.3/javaagent/src/test/resources/struts.xml similarity index 74% rename from instrumentation/struts-2.3/javaagent/src/test/resources/struts.xml rename to instrumentation/struts/struts-2.3/javaagent/src/test/resources/struts.xml index 3cf78125c183..33eeceebd183 100644 --- a/instrumentation/struts-2.3/javaagent/src/test/resources/struts.xml +++ b/instrumentation/struts/struts-2.3/javaagent/src/test/resources/struts.xml @@ -26,16 +26,16 @@ - - - - - - + + + + + - - - + + + diff --git a/instrumentation/struts/struts-7.0/javaagent/build.gradle.kts b/instrumentation/struts/struts-7.0/javaagent/build.gradle.kts new file mode 100644 index 000000000000..6d0d99e84258 --- /dev/null +++ b/instrumentation/struts/struts-7.0/javaagent/build.gradle.kts @@ -0,0 +1,37 @@ +plugins { + id("otel.javaagent-instrumentation") +} + +muzzle { + pass { + group.set("org.apache.struts") + module.set("struts2-core") + versions.set("[7.0.0,)") + assertInverse.set(true) + } +} + +// struts 7 requires java 17 +otelJava { + minJavaVersionSupported.set(JavaVersion.VERSION_17) +} + +dependencies { + bootstrap(project(":instrumentation:servlet:servlet-common:bootstrap")) + + library("org.apache.struts:struts2-core:7.0.0") + + testImplementation(project(":testing-common")) + testImplementation("org.eclipse.jetty:jetty-server:11.0.0") + testImplementation("org.eclipse.jetty:jetty-servlet:11.0.0") + testImplementation("jakarta.servlet:jakarta.servlet-api:5.0.0") + testImplementation("jakarta.servlet.jsp:jakarta.servlet.jsp-api:3.0.0") + + testInstrumentation(project(":instrumentation:servlet:servlet-5.0:javaagent")) + testInstrumentation(project(":instrumentation:jetty:jetty-11.0:javaagent")) + testInstrumentation(project(":instrumentation:struts:struts-2.3:javaagent")) +} + +tasks.withType().configureEach { + jvmArgs("-Dotel.instrumentation.common.experimental.controller-telemetry.enabled=true") +} diff --git a/instrumentation/struts/struts-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v7_0/ActionInvocationInstrumentation.java b/instrumentation/struts/struts-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v7_0/ActionInvocationInstrumentation.java new file mode 100644 index 000000000000..6e751570538e --- /dev/null +++ b/instrumentation/struts/struts-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v7_0/ActionInvocationInstrumentation.java @@ -0,0 +1,84 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.struts.v7_0; + +import static io.opentelemetry.instrumentation.api.semconv.http.HttpServerRouteSource.CONTROLLER; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; +import static io.opentelemetry.javaagent.instrumentation.struts.v7_0.StrutsSingletons.instrumenter; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute; +import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.struts2.ActionInvocation; + +public class ActionInvocationInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher classLoaderOptimization() { + return hasClassesNamed("org.apache.struts2.ActionInvocation"); + } + + @Override + public ElementMatcher typeMatcher() { + return implementsInterface(named("org.apache.struts2.ActionInvocation")); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isMethod().and(isPublic()).and(named("invokeActionOnly")), + this.getClass().getName() + "$InvokeActionOnlyAdvice"); + } + + @SuppressWarnings("unused") + public static class InvokeActionOnlyAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.This ActionInvocation actionInvocation, + @Advice.Local("otelContext") Context context, + @Advice.Local("otelScope") Scope scope) { + Context parentContext = Java8BytecodeBridge.currentContext(); + + HttpServerRoute.update( + parentContext, + CONTROLLER, + StrutsServerSpanNaming.SERVER_SPAN_NAME, + actionInvocation.getProxy()); + + if (!instrumenter().shouldStart(parentContext, actionInvocation)) { + return; + } + + context = instrumenter().start(parentContext, actionInvocation); + scope = context.makeCurrent(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void stopSpan( + @Advice.Thrown Throwable throwable, + @Advice.This ActionInvocation actionInvocation, + @Advice.Local("otelContext") Context context, + @Advice.Local("otelScope") Scope scope) { + if (scope == null) { + return; + } + scope.close(); + + instrumenter().end(context, actionInvocation, null, throwable); + } + } +} diff --git a/instrumentation/struts/struts-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v7_0/Struts2InstrumentationModule.java b/instrumentation/struts/struts-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v7_0/Struts2InstrumentationModule.java new file mode 100644 index 000000000000..a2ca8143df7f --- /dev/null +++ b/instrumentation/struts/struts-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v7_0/Struts2InstrumentationModule.java @@ -0,0 +1,26 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.struts.v7_0; + +import static java.util.Collections.singletonList; + +import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import java.util.List; + +@AutoService(InstrumentationModule.class) +public class Struts2InstrumentationModule extends InstrumentationModule { + + public Struts2InstrumentationModule() { + super("struts", "struts-7.0"); + } + + @Override + public List typeInstrumentations() { + return singletonList(new ActionInvocationInstrumentation()); + } +} diff --git a/instrumentation/struts/struts-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v7_0/StrutsCodeAttributesGetter.java b/instrumentation/struts/struts-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v7_0/StrutsCodeAttributesGetter.java new file mode 100644 index 000000000000..a902faf6cfbb --- /dev/null +++ b/instrumentation/struts/struts-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v7_0/StrutsCodeAttributesGetter.java @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.struts.v7_0; + +import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesGetter; +import org.apache.struts2.ActionInvocation; + +public class StrutsCodeAttributesGetter implements CodeAttributesGetter { + + @Override + public Class getCodeClass(ActionInvocation actionInvocation) { + return actionInvocation.getAction().getClass(); + } + + @Override + public String getMethodName(ActionInvocation actionInvocation) { + return actionInvocation.getProxy().getMethod(); + } +} diff --git a/instrumentation/struts/struts-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v7_0/StrutsServerSpanNaming.java b/instrumentation/struts/struts-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v7_0/StrutsServerSpanNaming.java new file mode 100644 index 000000000000..112ffaf646f3 --- /dev/null +++ b/instrumentation/struts/struts-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v7_0/StrutsServerSpanNaming.java @@ -0,0 +1,37 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.struts.v7_0; + +import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRouteGetter; +import io.opentelemetry.javaagent.bootstrap.servlet.ServletContextPath; +import org.apache.struts2.ActionProxy; + +public class StrutsServerSpanNaming { + + public static final HttpServerRouteGetter SERVER_SPAN_NAME = + (context, actionProxy) -> { + // We take name from the config, because it contains the path pattern from the + // configuration. + String result = actionProxy.getConfig().getName(); + + String actionNamespace = actionProxy.getNamespace(); + if (actionNamespace != null && !actionNamespace.isEmpty()) { + if (actionNamespace.endsWith("/") || result.startsWith("/")) { + result = actionNamespace + result; + } else { + result = actionNamespace + "/" + result; + } + } + + if (!result.startsWith("/")) { + result = "/" + result; + } + + return ServletContextPath.prepend(context, result); + }; + + private StrutsServerSpanNaming() {} +} diff --git a/instrumentation/struts/struts-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v7_0/StrutsSingletons.java b/instrumentation/struts/struts-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v7_0/StrutsSingletons.java new file mode 100644 index 000000000000..b638dd646223 --- /dev/null +++ b/instrumentation/struts/struts-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/struts/v7_0/StrutsSingletons.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.struts.v7_0; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeSpanNameExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.javaagent.bootstrap.internal.ExperimentalConfig; +import org.apache.struts2.ActionInvocation; + +public class StrutsSingletons { + private static final String INSTRUMENTATION_NAME = "io.opentelemetry.struts-7.0"; + + private static final Instrumenter INSTRUMENTER; + + static { + StrutsCodeAttributesGetter codeAttributesGetter = new StrutsCodeAttributesGetter(); + + INSTRUMENTER = + Instrumenter.builder( + GlobalOpenTelemetry.get(), + INSTRUMENTATION_NAME, + CodeSpanNameExtractor.create(codeAttributesGetter)) + .addAttributesExtractor(CodeAttributesExtractor.create(codeAttributesGetter)) + .setEnabled(ExperimentalConfig.get().controllerTelemetryEnabled()) + .buildInstrumenter(); + } + + public static Instrumenter instrumenter() { + return INSTRUMENTER; + } + + private StrutsSingletons() {} +} diff --git a/instrumentation/struts/struts-7.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/struts/v7_0/GreetingAction.java b/instrumentation/struts/struts-7.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/struts/v7_0/GreetingAction.java new file mode 100644 index 000000000000..4b3631be08c6 --- /dev/null +++ b/instrumentation/struts/struts-7.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/struts/v7_0/GreetingAction.java @@ -0,0 +1,92 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.struts.v7_0; + +import static io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest.controller; + +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.struts2.ActionSupport; +import org.apache.struts2.ServletActionContext; +import org.apache.struts2.interceptor.parameter.StrutsParameter; + +public class GreetingAction extends ActionSupport { + + String responseBody = "default"; + + public String success() { + responseBody = controller(ServerEndpoint.SUCCESS, ServerEndpoint.SUCCESS::getBody); + + return "greeting"; + } + + public String redirect() { + responseBody = controller(ServerEndpoint.REDIRECT, ServerEndpoint.REDIRECT::getBody); + return "redirect"; + } + + public String query_param() { + responseBody = controller(ServerEndpoint.QUERY_PARAM, ServerEndpoint.QUERY_PARAM::getBody); + return "greeting"; + } + + public String error() { + controller(ServerEndpoint.ERROR, ServerEndpoint.ERROR::getBody); + return "error"; + } + + public String exception() { + controller( + ServerEndpoint.EXCEPTION, + () -> { + throw new IllegalStateException(ServerEndpoint.EXCEPTION.getBody()); + }); + throw new AssertionError(); // should not reach here + } + + public String path_param() { + controller( + ServerEndpoint.PATH_PARAM, + () -> + "this does nothing, as responseBody is set in setId, but we need this controller span nevertheless"); + return "greeting"; + } + + public String indexed_child() { + responseBody = + controller( + ServerEndpoint.INDEXED_CHILD, + () -> { + ServerEndpoint.INDEXED_CHILD.collectSpanAttributes( + (name) -> ServletActionContext.getRequest().getParameter(name)); + return ServerEndpoint.INDEXED_CHILD.getBody(); + }); + return "greeting"; + } + + public String capture_headers() { + HttpServletRequest request = ServletActionContext.getRequest(); + HttpServletResponse response = ServletActionContext.getResponse(); + response.setHeader("X-Test-Response", request.getHeader("X-Test-Request")); + responseBody = + controller(ServerEndpoint.CAPTURE_HEADERS, ServerEndpoint.CAPTURE_HEADERS::getBody); + return "greeting"; + } + + public String dispatch_servlet() { + return "greetingServlet"; + } + + @StrutsParameter + public void setId(String id) { + responseBody = id; + } + + public String getResponseBody() { + return responseBody; + } +} diff --git a/instrumentation/struts/struts-7.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/struts/v7_0/GreetingServlet.java b/instrumentation/struts/struts-7.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/struts/v7_0/GreetingServlet.java new file mode 100644 index 000000000000..13215454dff8 --- /dev/null +++ b/instrumentation/struts/struts-7.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/struts/v7_0/GreetingServlet.java @@ -0,0 +1,19 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.struts.v7_0; + +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class GreetingServlet extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { + resp.getWriter().write("greeting"); + } +} diff --git a/instrumentation/struts/struts-7.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/struts/v7_0/Struts2ActionSpanTest.java b/instrumentation/struts/struts-7.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/struts/v7_0/Struts2ActionSpanTest.java new file mode 100644 index 000000000000..fb5bb758b910 --- /dev/null +++ b/instrumentation/struts/struts-7.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/struts/v7_0/Struts2ActionSpanTest.java @@ -0,0 +1,151 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.struts.v7_0; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.PATH_PARAM; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.incubating.CodeIncubatingAttributes.CODE_FUNCTION; +import static io.opentelemetry.semconv.incubating.CodeIncubatingAttributes.CODE_NAMESPACE; +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.api.internal.HttpConstants; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.data.StatusData; +import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse; +import jakarta.servlet.DispatcherType; +import java.net.InetSocketAddress; +import java.util.EnumSet; +import java.util.Locale; +import org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.DefaultServlet; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +class Struts2ActionSpanTest extends AbstractHttpServerTest { + + @RegisterExtension + public static final InstrumentationExtension testing = + HttpServerInstrumentationExtension.forAgent(); + + @Override + protected Server setupServer() throws Exception { + Server server = new Server(new InetSocketAddress("localhost", port)); + + ServletContextHandler context = new ServletContextHandler(null, getContextPath()); + + context.addServlet(DefaultServlet.class, "/"); + context.addServlet(GreetingServlet.class, "/greetingServlet"); + context.addFilter( + StrutsPrepareAndExecuteFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); + + server.setHandler(context); + + server.start(); + return server; + } + + @Override + protected void stopServer(Server server) throws Exception { + server.stop(); + server.destroy(); + } + + @Override + protected void configure(HttpServerTestOptions options) { + options.setContextPath("/context"); + options.setTestPathParam(true); + options.setTestErrorBody(false); + options.setTestPathParam(false); + options.setHasHandlerSpan(endpoint -> !endpoint.equals(NOT_FOUND)); + options.setHasResponseSpan( + endpoint -> + endpoint == REDIRECT + || endpoint == ERROR + || endpoint == EXCEPTION + || endpoint == NOT_FOUND); + + options.setExpectedHttpRoute( + (ServerEndpoint endpoint, String method) -> { + if (method.equals(HttpConstants._OTHER)) { + return getContextPath() + endpoint.getPath(); + } + if (endpoint.equals(PATH_PARAM)) { + return getContextPath() + "/path/{id}/param"; + } else if (endpoint.equals(NOT_FOUND)) { + return getContextPath() + "/*"; + } else { + return super.expectedHttpRoute(endpoint, method); + } + }); + } + + @Override + protected SpanDataAssert assertResponseSpan( + SpanDataAssert span, SpanData parentSpan, String method, ServerEndpoint endpoint) { + if (endpoint.equals(REDIRECT)) { + span.satisfies(spanData -> assertThat(spanData.getName()).endsWith(".sendRedirect")); + } else if (endpoint.equals(NOT_FOUND)) { + span.satisfies(spanData -> assertThat(spanData.getName()).endsWith(".sendError")) + .hasParent(parentSpan); + } + + span.hasKind(SpanKind.INTERNAL); + return span; + } + + @Override + protected SpanDataAssert assertHandlerSpan( + SpanDataAssert span, String method, ServerEndpoint endpoint) { + span.hasName("GreetingAction." + endpoint.name().toLowerCase(Locale.ROOT)) + .hasKind(SpanKind.INTERNAL); + + if (endpoint.equals(EXCEPTION)) { + span.hasStatus(StatusData.error()) + .hasException(new IllegalStateException(EXCEPTION.getBody())); + } + + span.hasAttributesSatisfyingExactly( + equalTo(CODE_NAMESPACE, GreetingAction.class.getName()), + equalTo(CODE_FUNCTION, endpoint.name().toLowerCase(Locale.ROOT))); + return span; + } + + // Struts runs from a servlet filter. Test that dispatching from struts action to a servlet + // does not overwrite server span name given by struts instrumentation. + @Test + void testDispatchToServlet() { + AggregatedHttpResponse response = + client.get(address.resolve("dispatch").toString()).aggregate().join(); + + assertThat(response.status().code()).isEqualTo(200); + assertThat(response.contentUtf8()).isEqualTo("greeting"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET " + getContextPath() + "/dispatch") + .hasKind(SpanKind.SERVER) + .hasNoParent(), + span -> + span.hasName("GreetingAction.dispatch_servlet") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)))); + } +} diff --git a/instrumentation/struts/struts-7.0/javaagent/src/test/resources/greeting.ftl b/instrumentation/struts/struts-7.0/javaagent/src/test/resources/greeting.ftl new file mode 100644 index 000000000000..b833223d9494 --- /dev/null +++ b/instrumentation/struts/struts-7.0/javaagent/src/test/resources/greeting.ftl @@ -0,0 +1,2 @@ +<#-- @ftlvariable name="responseBody" type="java.lang.String" --> +${responseBody} \ No newline at end of file diff --git a/instrumentation/struts/struts-7.0/javaagent/src/test/resources/struts.xml b/instrumentation/struts/struts-7.0/javaagent/src/test/resources/struts.xml new file mode 100644 index 000000000000..362b7acb7bfd --- /dev/null +++ b/instrumentation/struts/struts-7.0/javaagent/src/test/resources/struts.xml @@ -0,0 +1,50 @@ + + + + + + + + + /redirected + false + + + 500 + + /greeting.ftl + /greetingServlet + + + + + + + + + + + + + + + + + diff --git a/settings.gradle.kts b/settings.gradle.kts index 26e8a24ef38a..0e4cda313a8d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -571,7 +571,8 @@ include(":instrumentation:spring:spring-ws-2.0:javaagent") include(":instrumentation:spring:starters:spring-boot-starter") include(":instrumentation:spring:starters:zipkin-spring-boot-starter") include(":instrumentation:spymemcached-2.12:javaagent") -include(":instrumentation:struts-2.3:javaagent") +include(":instrumentation:struts:struts-2.3:javaagent") +include(":instrumentation:struts:struts-7.0:javaagent") include(":instrumentation:tapestry-5.4:javaagent") include(":instrumentation:tomcat:tomcat-7.0:javaagent") include(":instrumentation:tomcat:tomcat-10.0:javaagent")