diff --git a/.fossa.yml b/.fossa.yml index 0b6c25d6df66..2a228de35cc5 100644 --- a/.fossa.yml +++ b/.fossa.yml @@ -187,6 +187,9 @@ targets: - type: gradle path: ./ target: ':instrumentation:jdbc:library' + - type: gradle + path: ./ + target: ':instrumentation:jfinal-3.2:javaagent' - type: gradle path: ./ target: ':instrumentation:jmx-metrics:javaagent' diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md index 2eb0694bcc62..50a71271473b 100644 --- a/docs/supported-libraries.md +++ b/docs/supported-libraries.md @@ -96,6 +96,7 @@ These are the supported libraries and frameworks: | [JBoss Log Manager](https://github.com/jboss-logging/jboss-logmanager) | 1.1+ | N/A | none | | [JDBC](https://docs.oracle.com/javase/8/docs/api/java/sql/package-summary.html) | Java 8+ | [opentelemetry-jdbc](../instrumentation/jdbc/library) | [Database Client Spans], [Database Client Metrics] [6] | | [Jedis](https://github.com/xetorthio/jedis) | 1.4+ | N/A | [Database Client Spans], [Database Client Metrics] [6] | +| [JFinal](https://github.com/jfinal/jfinal) | 3.2+ | N/A | Provides `http.route` [2], Controller Spans [3] | | [JMS](https://javaee.github.io/javaee-spec/javadocs/javax/jms/package-summary.html) | 1.1+ | N/A | [Messaging Spans] | | [Jodd Http](https://http.jodd.org/) | 4.2+ | N/A | [HTTP Client Spans], [HTTP Client Metrics] | | [JSP](https://javaee.github.io/javaee-spec/javadocs/javax/servlet/jsp/package-summary.html) | 2.3.x only | N/A | Controller Spans [3] | diff --git a/instrumentation/jfinal-3.2/javaagent/build.gradle.kts b/instrumentation/jfinal-3.2/javaagent/build.gradle.kts new file mode 100644 index 000000000000..5336ad06466d --- /dev/null +++ b/instrumentation/jfinal-3.2/javaagent/build.gradle.kts @@ -0,0 +1,31 @@ +plugins { + id("otel.javaagent-instrumentation") +} + +muzzle { + pass { + group.set("com.jfinal") + module.set("jfinal") + versions.set("[3.2,)") + assertInverse.set(true) + } +} + +if (!(findProperty("testLatestDeps") as Boolean)) { + otelJava { + // jfinal 3.2 doesn't work with Java 9+ + maxJavaVersionForTests.set(JavaVersion.VERSION_1_8) + } +} + +dependencies { + library("com.jfinal:jfinal:3.2") + testLibrary("com.jfinal:jetty-server:2019.3") + testInstrumentation(project(":instrumentation:jetty:jetty-8.0:javaagent")) + testInstrumentation(project(":instrumentation:jetty:jetty-11.0:javaagent")) + testInstrumentation(project(":instrumentation:jetty:jetty-common:javaagent")) +} + +tasks.withType().configureEach { + jvmArgs("-Dotel.instrumentation.common.experimental.controller-telemetry.enabled=true") +} diff --git a/instrumentation/jfinal-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jfinal/v3_2/ActionMappingInstrumentation.java b/instrumentation/jfinal-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jfinal/v3_2/ActionMappingInstrumentation.java new file mode 100644 index 000000000000..a516a440c858 --- /dev/null +++ b/instrumentation/jfinal-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jfinal/v3_2/ActionMappingInstrumentation.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jfinal.v3_2; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +import com.jfinal.core.Action; +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; + +public class ActionMappingInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher typeMatcher() { + return named("com.jfinal.core.ActionMapping"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + named("getAction"), this.getClass().getName() + "$GetActionAdvice"); + } + + @SuppressWarnings("unused") + public static class GetActionAdvice { + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void exitGetAction(@Advice.Return Action action) { + JFinalSingletons.updateRoute(action); + } + } +} diff --git a/instrumentation/jfinal-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jfinal/v3_2/InvocationInstrumentation.java b/instrumentation/jfinal-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jfinal/v3_2/InvocationInstrumentation.java new file mode 100644 index 000000000000..dd636fc5bfd8 --- /dev/null +++ b/instrumentation/jfinal-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jfinal/v3_2/InvocationInstrumentation.java @@ -0,0 +1,92 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jfinal.v3_2; + +import static io.opentelemetry.javaagent.instrumentation.jfinal.v3_2.JFinalSingletons.instrumenter; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments; + +import com.jfinal.aop.Invocation; +import com.jfinal.core.Action; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod; +import io.opentelemetry.javaagent.bootstrap.CallDepth; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import javax.annotation.Nullable; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class InvocationInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher typeMatcher() { + return named("com.jfinal.aop.Invocation"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + named("invoke").and(takesNoArguments()), this.getClass().getName() + "$InvokeAdvice"); + } + + @SuppressWarnings("unused") + public static class InvokeAdvice { + + public static class AdviceScope { + private final CallDepth callDepth; + private final ClassAndMethod request; + private final Context context; + private final Scope scope; + + public AdviceScope( + CallDepth callDepth, ClassAndMethod request, Context context, Scope scope) { + this.callDepth = callDepth; + this.request = request; + this.context = context; + this.scope = scope; + } + + public static AdviceScope start(CallDepth callDepth, Action action) { + if (callDepth.getAndIncrement() > 0 || action == null) { + return new AdviceScope(callDepth, null, null, null); + } + + Context parentContext = Context.current(); + ClassAndMethod request = + ClassAndMethod.create(action.getControllerClass(), action.getMethodName()); + if (!instrumenter().shouldStart(parentContext, request)) { + return new AdviceScope(callDepth, null, null, null); + } + + Context context = instrumenter().start(parentContext, request); + return new AdviceScope(callDepth, request, context, context.makeCurrent()); + } + + public void end(@Nullable Throwable throwable) { + if (callDepth.decrementAndGet() > 0 || scope == null) { + return; + } + scope.close(); + instrumenter().end(context, request, null, throwable); + } + } + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static AdviceScope onEnter(@Advice.FieldValue("action") Action action) { + CallDepth callDepth = CallDepth.forClass(Invocation.class); + return AdviceScope.start(callDepth, action); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void stopTraceOnResponse( + @Advice.Thrown @Nullable Throwable throwable, @Advice.Enter AdviceScope actionScope) { + actionScope.end(throwable); + } + } +} diff --git a/instrumentation/jfinal-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jfinal/v3_2/JFinalInstrumentationModule.java b/instrumentation/jfinal-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jfinal/v3_2/JFinalInstrumentationModule.java new file mode 100644 index 000000000000..5375ffb40f30 --- /dev/null +++ b/instrumentation/jfinal-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jfinal/v3_2/JFinalInstrumentationModule.java @@ -0,0 +1,43 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jfinal.v3_2; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static java.util.Arrays.asList; +import static net.bytebuddy.matcher.ElementMatchers.not; + +import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule; +import java.util.List; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(InstrumentationModule.class) +public class JFinalInstrumentationModule extends InstrumentationModule + implements ExperimentalInstrumentationModule { + + public JFinalInstrumentationModule() { + super("jfinal", "jfinal-3.2"); + } + + @Override + public ElementMatcher.Junction classLoaderMatcher() { + // In version 3.2, TypeConverter is moved from com.jfinal.core + // to com.jfinal.core.converter + return not(hasClassesNamed("com.jfinal.core.TypeConverter")); + } + + @Override + public boolean isIndyReady() { + return true; + } + + @Override + public List typeInstrumentations() { + return asList(new ActionMappingInstrumentation(), new InvocationInstrumentation()); + } +} diff --git a/instrumentation/jfinal-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jfinal/v3_2/JFinalSingletons.java b/instrumentation/jfinal-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jfinal/v3_2/JFinalSingletons.java new file mode 100644 index 000000000000..cfcc9aaf6675 --- /dev/null +++ b/instrumentation/jfinal-3.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jfinal/v3_2/JFinalSingletons.java @@ -0,0 +1,65 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jfinal.v3_2; + +import com.jfinal.core.Action; +import com.jfinal.render.JsonRender; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesGetter; +import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeSpanNameExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute; +import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRouteSource; +import io.opentelemetry.javaagent.bootstrap.internal.ExperimentalConfig; + +public final class JFinalSingletons { + + private static final Instrumenter INSTRUMENTER; + + static { + // see + // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/11465#issuecomment-2137294837 + excludeOtAttrs(); + + CodeAttributesGetter codedAttributesGetter = + ClassAndMethod.codeAttributesGetter(); + INSTRUMENTER = + Instrumenter.builder( + GlobalOpenTelemetry.get(), + "io.opentelemetry.jfinal-3.2", + CodeSpanNameExtractor.create(codedAttributesGetter)) + .setEnabled(ExperimentalConfig.get().controllerTelemetryEnabled()) + .addAttributesExtractor(CodeAttributesExtractor.create(codedAttributesGetter)) + .buildInstrumenter(); + } + + public static Instrumenter instrumenter() { + return INSTRUMENTER; + } + + public static void updateRoute(Action action) { + if (action == null) { + return; + } + String route = action.getActionKey(); + Context context = Context.current(); + if (route != null) { + HttpServerRoute.update(context, HttpServerRouteSource.CONTROLLER, route); + } + } + + private static void excludeOtAttrs() { + JsonRender.addExcludedAttrs( + "io.opentelemetry.javaagent.instrumentation.servlet.ServletHelper.Context", + "trace_id", + "span_id"); + } + + private JFinalSingletons() {} +} diff --git a/instrumentation/jfinal-3.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jfinal/JFinalTest.java b/instrumentation/jfinal-3.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jfinal/JFinalTest.java new file mode 100644 index 000000000000..73587e72a13b --- /dev/null +++ b/instrumentation/jfinal-3.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jfinal/JFinalTest.java @@ -0,0 +1,143 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jfinal; + +import static io.opentelemetry.api.trace.SpanKind.INTERNAL; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; +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.INDEXED_CHILD; +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.QUERY_PARAM; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT; +import static org.assertj.core.api.Assertions.assertThat; + +import com.jfinal.core.JFinalFilter; +import io.opentelemetry.api.common.Attributes; +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.code.SemconvCodeStabilityUtil; +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 java.util.EnumSet; +import java.util.Locale; +import javax.servlet.DispatcherType; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHandler; +import org.junit.jupiter.api.extension.RegisterExtension; + +class JFinalTest extends AbstractHttpServerTest { + + @RegisterExtension + static final InstrumentationExtension testing = HttpServerInstrumentationExtension.forAgent(); + + @Override + protected void configure(HttpServerTestOptions options) { + options.setHasHandlerSpan(endpoint -> endpoint != NOT_FOUND); + options.setHasResponseSpan(endpoint -> endpoint == REDIRECT); + options.setExpectedHttpRoute( + (endpoint, method) -> { + if (endpoint == PATH_PARAM) { + return "/path/123/param"; + } + if (HttpConstants._OTHER.equals(method)) { + return endpoint.getPath(); + } + if (NOT_FOUND.equals(endpoint)) { + return "/*"; + } + return expectedHttpRoute(endpoint, method); + }); + } + + @Override + protected Server setupServer() throws Exception { + Server server = new Server(port); + + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/"); + ServletHandler handler = new ServletHandler(); + + FilterHolder fh = + handler.addFilterWithMapping( + JFinalFilter.class.getName(), "/*", EnumSet.of(DispatcherType.REQUEST)); + fh.setInitParameter("configClass", TestConfig.class.getName()); + + context.addFilter(fh, "/*", EnumSet.of(DispatcherType.REQUEST)); + context.insertHandler(handler); + server.setHandler(context); + server.start(); + return server; + } + + @Override + protected void stopServer(Server server) throws Exception { + server.stop(); + } + + @Override + protected SpanDataAssert assertResponseSpan( + SpanDataAssert span, + SpanData serverSpan, + SpanData controllerSpan, + SpanData handlerSpan, + String method, + ServerEndpoint endpoint) { + return span.satisfies(spanData -> assertThat(spanData.getName()).endsWith(".sendRedirect")) + .hasParent(serverSpan) + .hasKind(SpanKind.INTERNAL) + .hasAttributesSatisfying(Attributes::isEmpty); + } + + @Override + public SpanDataAssert assertHandlerSpan( + SpanDataAssert span, String method, ServerEndpoint endpoint) { + span.hasName(getHandlerSpanName(endpoint)) + .hasKind(INTERNAL) + .hasAttributesSatisfyingExactly( + SemconvCodeStabilityUtil.codeFunctionAssertions( + TestController.class, getHandlerMethod(endpoint))); + + if (endpoint == EXCEPTION) { + span.hasStatus(StatusData.error()); + span.hasException(new IllegalStateException(EXCEPTION.getBody())); + } + return span; + } + + private static String getHandlerMethod(ServerEndpoint endpoint) { + if (QUERY_PARAM.equals(endpoint)) { + return "query"; + } else if (PATH_PARAM.equals(endpoint)) { + return "path"; + } else if (CAPTURE_HEADERS.equals(endpoint)) { + return "captureHeaders"; + } else if (INDEXED_CHILD.equals(endpoint)) { + return "child"; + } + return endpoint.name().toLowerCase(Locale.ROOT); + } + + private static String getHandlerSpanName(ServerEndpoint endpoint) { + if (PATH_PARAM.equals(endpoint)) { + return "TestController.pathParam"; + } else if (NOT_FOUND.equals(endpoint)) { + return "jfinal.handle"; + } else if (ERROR.equals(endpoint)) { + return "TestController.error"; + } + return "TestController." + endpoint.getPath().replace("/", ""); + } +} diff --git a/instrumentation/jfinal-3.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jfinal/TestConfig.java b/instrumentation/jfinal-3.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jfinal/TestConfig.java new file mode 100644 index 000000000000..71817a28e62b --- /dev/null +++ b/instrumentation/jfinal-3.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jfinal/TestConfig.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jfinal; + +import com.jfinal.config.Constants; +import com.jfinal.config.Handlers; +import com.jfinal.config.Interceptors; +import com.jfinal.config.JFinalConfig; +import com.jfinal.config.Plugins; +import com.jfinal.config.Routes; +import com.jfinal.template.Engine; + +public class TestConfig extends JFinalConfig { + @Override + public void configConstant(Constants me) { + me.setDevMode(true); + } + + @Override + public void configRoute(Routes me) { + me.add("/", TestController.class); + } + + @Override + public void configEngine(Engine me) {} + + @Override + public void configPlugin(Plugins me) {} + + @Override + public void configInterceptor(Interceptors me) {} + + @Override + public void configHandler(Handlers me) {} +} diff --git a/instrumentation/jfinal-3.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jfinal/TestController.java b/instrumentation/jfinal-3.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jfinal/TestController.java new file mode 100644 index 000000000000..c5dd61e05ac0 --- /dev/null +++ b/instrumentation/jfinal-3.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jfinal/TestController.java @@ -0,0 +1,114 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jfinal; + +import static io.opentelemetry.instrumentation.testing.GlobalTraceUtil.runWithSpan; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; + +import com.jfinal.core.ActionKey; +import com.jfinal.core.Controller; +import com.jfinal.render.TextRender; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; + +public class TestController extends Controller { + + public void success() { + runWithSpan("controller", () -> renderText(ServerEndpoint.SUCCESS.getBody())); + } + + public void redirect() { + runWithSpan("controller", () -> redirect(ServerEndpoint.REDIRECT.getBody())); + } + + @ActionKey("error-status") + public void error() throws Exception { + runWithSpan( + "controller", () -> renderError(500, new TextRender(ServerEndpoint.ERROR.getBody()))); + } + + public void exception() throws Throwable { + try { + runWithSpan( + "controller", + () -> { + throw new IllegalStateException(EXCEPTION.getBody()); + }); + } catch (Throwable t) { + Span.current().recordException(t); + throw t; + } + } + + public void captureHeaders() { + runWithSpan( + "controller", + () -> { + String header = getHeader("X-Test-Request"); + getResponse().setHeader("X-Test-Response", header); + renderText(ServerEndpoint.CAPTURE_HEADERS.getBody()); + }); + } + + public void captureParameters() { + runWithSpan( + "controller", + () -> { + renderText(ServerEndpoint.CAPTURE_PARAMETERS.getBody()); + }); + } + + public void query() { + + runWithSpan( + "controller", + () -> { + renderText(ServerEndpoint.QUERY_PARAM.getBody()); + }); + } + + @ActionKey("path/123/param") + public void pathVar() { + runWithSpan( + "controller", + () -> { + renderText(ServerEndpoint.PATH_PARAM.getBody()); + }); + } + + public void authRequired() { + runWithSpan( + "controller", + () -> { + renderText(ServerEndpoint.AUTH_REQUIRED.getBody()); + }); + } + + public void login() { + runWithSpan( + "controller", + () -> { + redirect(ServerEndpoint.LOGIN.getBody()); + }); + } + + @ActionKey("basicsecured/endpoint(") + public void basicsecured_endpoint() { + renderText(ServerEndpoint.AUTH_ERROR.getBody()); + } + + public void child() { + runWithSpan( + "controller", + () -> { + ServerEndpoint.INDEXED_CHILD.collectSpanAttributes( + name -> { + return getPara(name); + }); + renderText(ServerEndpoint.INDEXED_CHILD.getBody()); + }); + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3Test.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3Test.java index 26e292cc3a69..e0fd16890141 100644 --- a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3Test.java +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3Test.java @@ -63,6 +63,7 @@ public boolean isAsyncTest() { @Override protected SpanDataAssert assertResponseSpan( SpanDataAssert span, + SpanData serverSpan, SpanData controllerSpan, SpanData handlerSpan, String method, @@ -70,10 +71,11 @@ protected SpanDataAssert assertResponseSpan( if (IS_BEFORE_94 && endpoint.equals(EXCEPTION)) { span.satisfies(it -> assertThat(it.getName()).matches(".*\\.sendError")) .hasKind(SpanKind.INTERNAL) - .hasParent(handlerSpan); + .hasParent(serverSpan); } - return super.assertResponseSpan(span, controllerSpan, handlerSpan, method, endpoint); + return super.assertResponseSpan( + span, serverSpan, controllerSpan, handlerSpan, method, endpoint); } @Override diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServletHandlerTest.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServletHandlerTest.java index 529793e48da2..720feef01309 100644 --- a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServletHandlerTest.java +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServletHandlerTest.java @@ -59,18 +59,19 @@ public boolean hasResponseSpan(ServerEndpoint endpoint) { @Override protected SpanDataAssert assertResponseSpan( SpanDataAssert span, + SpanData serverSpan, SpanData controllerSpan, SpanData handlerSpan, String method, ServerEndpoint endpoint) { - if (JettyServlet3Test.IS_BEFORE_94 && endpoint.equals(EXCEPTION)) { span.satisfies(it -> assertThat(it.getName()).matches(".*\\.sendError")) .hasKind(SpanKind.INTERNAL) - .hasParent(handlerSpan); + .hasParent(serverSpan); } - return super.assertResponseSpan(span, controllerSpan, handlerSpan, method, endpoint); + return super.assertResponseSpan( + span, serverSpan, controllerSpan, handlerSpan, method, endpoint); } @Override diff --git a/settings.gradle.kts b/settings.gradle.kts index a4bf32bfa40d..b0093e323cf0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -386,6 +386,7 @@ include(":instrumentation:jetty-httpclient:jetty-httpclient-9.2:testing") include(":instrumentation:jetty-httpclient:jetty-httpclient-12.0:javaagent") include(":instrumentation:jetty-httpclient:jetty-httpclient-12.0:library") include(":instrumentation:jetty-httpclient:jetty-httpclient-12.0:testing") +include(":instrumentation:jfinal-3.2:javaagent") include(":instrumentation:jms:jms-1.1:javaagent") include(":instrumentation:jms:jms-3.0:javaagent") include(":instrumentation:jms:jms-common:bootstrap") diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/AbstractHttpServerTest.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/AbstractHttpServerTest.java index 1923d657d165..8dbc228eff05 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/AbstractHttpServerTest.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/AbstractHttpServerTest.java @@ -722,10 +722,12 @@ protected void assertTheTraces( }); } + int handlerIndex = -1; if (endpoint != NOT_FOUND) { int parentIndex = 0; if (options.hasHandlerSpan.test(endpoint)) { - parentIndex = spanAssertions.size() - 1; + handlerIndex = spanAssertions.size() - 1; + parentIndex = handlerIndex; } int finalParentIndex = parentIndex; spanAssertions.add( @@ -741,10 +743,16 @@ protected void assertTheTraces( if (options.hasResponseSpan.test(endpoint)) { int parentIndex = spanAssertions.size() - 1; + int finalHandlerIndex = handlerIndex; spanAssertions.add( span -> assertResponseSpan( - span, trace.getSpan(parentIndex), trace.getSpan(0), method, endpoint)); + span, + trace.getSpan(0), + trace.getSpan(parentIndex), + finalHandlerIndex >= 0 ? trace.getSpan(finalHandlerIndex) : null, + method, + endpoint)); } if (options.hasErrorPageSpans.test(endpoint)) { @@ -788,6 +796,7 @@ protected SpanDataAssert assertHandlerSpan( @CanIgnoreReturnValue protected SpanDataAssert assertResponseSpan( SpanDataAssert span, + SpanData serverSpan, SpanData controllerSpan, SpanData handlerSpan, String method,