Skip to content

Commit

Permalink
Support Struts 7.0 (fixes CI muzzle failures) (#12935)
Browse files Browse the repository at this point in the history
Co-authored-by: Lauri Tulmin <[email protected]>
  • Loading branch information
trask and laurit authored Dec 20, 2024
1 parent 8e449ef commit fa32671
Show file tree
Hide file tree
Showing 24 changed files with 582 additions and 21 deletions.
2 changes: 1 addition & 1 deletion docs/supported-libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -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] |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand All @@ -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.+")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,16 @@
<exception-mapping exception="java.lang.Exception" result="error"/>
</global-exception-mappings>

<action name="success" class="io.opentelemetry.javaagent.instrumentation.struts2.GreetingAction" method="success"/>
<action name="redirect" class="io.opentelemetry.javaagent.instrumentation.struts2.GreetingAction" method="redirect"/>
<action name="query" class="io.opentelemetry.javaagent.instrumentation.struts2.GreetingAction" method="query_param"/>
<action name="error-status" class="io.opentelemetry.javaagent.instrumentation.struts2.GreetingAction" method="error"/>
<action name="exception" class="io.opentelemetry.javaagent.instrumentation.struts2.GreetingAction" method="exception"/>
<action name="/path/{id}/param" class="io.opentelemetry.javaagent.instrumentation.struts2.GreetingAction"
<action name="success" class="io.opentelemetry.javaagent.instrumentation.struts.v2_3.GreetingAction" method="success"/>
<action name="redirect" class="io.opentelemetry.javaagent.instrumentation.struts.v2_3.GreetingAction" method="redirect"/>
<action name="query" class="io.opentelemetry.javaagent.instrumentation.struts.v2_3.GreetingAction" method="query_param"/>
<action name="error-status" class="io.opentelemetry.javaagent.instrumentation.struts.v2_3.GreetingAction" method="error"/>
<action name="exception" class="io.opentelemetry.javaagent.instrumentation.struts.v2_3.GreetingAction" method="exception"/>
<action name="/path/{id}/param" class="io.opentelemetry.javaagent.instrumentation.struts.v2_3.GreetingAction"
method="path_param"/>
<action name="child" class="io.opentelemetry.javaagent.instrumentation.struts2.GreetingAction" method="indexed_child"/>
<action name="captureHeaders" class="io.opentelemetry.javaagent.instrumentation.struts2.GreetingAction" method="capture_headers"/>
<action name="dispatch" class="io.opentelemetry.javaagent.instrumentation.struts2.GreetingAction" method="dispatch_servlet"/>
<action name="child" class="io.opentelemetry.javaagent.instrumentation.struts.v2_3.GreetingAction" method="indexed_child"/>
<action name="captureHeaders" class="io.opentelemetry.javaagent.instrumentation.struts.v2_3.GreetingAction" method="capture_headers"/>
<action name="dispatch" class="io.opentelemetry.javaagent.instrumentation.struts.v2_3.GreetingAction" method="dispatch_servlet"/>
</package>


Expand Down
37 changes: 37 additions & 0 deletions instrumentation/struts/struts-7.0/javaagent/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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<Test>().configureEach {
jvmArgs("-Dotel.instrumentation.common.experimental.controller-telemetry.enabled=true")
}
Original file line number Diff line number Diff line change
@@ -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<ClassLoader> classLoaderOptimization() {
return hasClassesNamed("org.apache.struts2.ActionInvocation");
}

@Override
public ElementMatcher<TypeDescription> 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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<TypeInstrumentation> typeInstrumentations() {
return singletonList(new ActionInvocationInstrumentation());
}
}
Original file line number Diff line number Diff line change
@@ -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<ActionInvocation> {

@Override
public Class<?> getCodeClass(ActionInvocation actionInvocation) {
return actionInvocation.getAction().getClass();
}

@Override
public String getMethodName(ActionInvocation actionInvocation) {
return actionInvocation.getProxy().getMethod();
}
}
Original file line number Diff line number Diff line change
@@ -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<ActionProxy> 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() {}
}
Original file line number Diff line number Diff line change
@@ -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<ActionInvocation, Void> INSTRUMENTER;

static {
StrutsCodeAttributesGetter codeAttributesGetter = new StrutsCodeAttributesGetter();

INSTRUMENTER =
Instrumenter.<ActionInvocation, Void>builder(
GlobalOpenTelemetry.get(),
INSTRUMENTATION_NAME,
CodeSpanNameExtractor.create(codeAttributesGetter))
.addAttributesExtractor(CodeAttributesExtractor.create(codeAttributesGetter))
.setEnabled(ExperimentalConfig.get().controllerTelemetryEnabled())
.buildInstrumenter();
}

public static Instrumenter<ActionInvocation, Void> instrumenter() {
return INSTRUMENTER;
}

private StrutsSingletons() {}
}
Loading

0 comments on commit fa32671

Please sign in to comment.