Wicket instrumentation (#2139)
* Wicket instrumentation * Change supported version to 8.0, turns out earlier versions didn't work
This commit is contained in:
parent
71ceb7486c
commit
02ca471577
|
@ -318,6 +318,7 @@ These are the supported libraries and frameworks:
|
|||
| [Akka HTTP](https://doc.akka.io/docs/akka-http/current/index.html) | 10.0+ |
|
||||
| [Apache HttpAsyncClient](https://hc.apache.org/index.html) | 4.1+ |
|
||||
| [Apache HttpClient](https://hc.apache.org/index.html) | 2.0+ |
|
||||
| [Apache Wicket](https://wicket.apache.org/) | 8.0+ |
|
||||
| [Armeria](https://armeria.dev) | 1.3+ |
|
||||
| [AsyncHttpClient](https://github.com/AsyncHttpClient/async-http-client) | 1.9+ (not including 2.x yet) |
|
||||
| [AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/java-handler.html) | 1.0+ |
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.wicket;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
|
||||
|
||||
import io.opentelemetry.api.trace.Span;
|
||||
import io.opentelemetry.instrumentation.api.tracer.BaseTracer;
|
||||
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Collections;
|
||||
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.apache.wicket.WicketRuntimeException;
|
||||
|
||||
public class DefaultExceptionMapperInstrumentation implements TypeInstrumentation {
|
||||
|
||||
@Override
|
||||
public ElementMatcher<? super TypeDescription> typeMatcher() {
|
||||
return named("org.apache.wicket.DefaultExceptionMapper");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||
return Collections.singletonMap(
|
||||
named("mapUnexpectedExceptions").and(takesArgument(0, named(Exception.class.getName()))),
|
||||
DefaultExceptionMapperInstrumentation.class.getName() + "$ExceptionAdvice");
|
||||
}
|
||||
|
||||
public static class ExceptionAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void onExit(@Advice.Argument(0) Exception exception) {
|
||||
Span serverSpan = BaseTracer.getCurrentServerSpan();
|
||||
if (serverSpan != null) {
|
||||
// unwrap exception
|
||||
Throwable throwable = exception;
|
||||
while (throwable.getCause() != null
|
||||
&& (throwable instanceof WicketRuntimeException
|
||||
|| throwable instanceof InvocationTargetException)) {
|
||||
throwable = throwable.getCause();
|
||||
}
|
||||
// as we don't create a span for wicket we record exception on server span
|
||||
serverSpan.recordException(throwable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.wicket;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
|
||||
|
||||
import io.opentelemetry.api.trace.Span;
|
||||
import io.opentelemetry.instrumentation.api.servlet.ServletContextPath;
|
||||
import io.opentelemetry.instrumentation.api.tracer.BaseTracer;
|
||||
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
|
||||
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
|
||||
import java.util.Collections;
|
||||
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.apache.wicket.core.request.handler.IPageClassRequestHandler;
|
||||
import org.apache.wicket.request.IRequestHandler;
|
||||
import org.apache.wicket.request.cycle.RequestCycle;
|
||||
|
||||
public class RequestHandlerExecutorInstrumentation implements TypeInstrumentation {
|
||||
|
||||
@Override
|
||||
public ElementMatcher<? super TypeDescription> typeMatcher() {
|
||||
return named("org.apache.wicket.request.RequestHandlerExecutor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||
return Collections.singletonMap(
|
||||
named("execute").and(takesArgument(0, named("org.apache.wicket.request.IRequestHandler"))),
|
||||
RequestHandlerExecutorInstrumentation.class.getName() + "$ExecuteAdvice");
|
||||
}
|
||||
|
||||
public static class ExecuteAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void onExit(@Advice.Argument(0) IRequestHandler handler) {
|
||||
Span serverSpan = BaseTracer.getCurrentServerSpan();
|
||||
if (serverSpan == null) {
|
||||
return;
|
||||
}
|
||||
if (handler instanceof IPageClassRequestHandler) {
|
||||
// using class name as page name
|
||||
String pageName = ((IPageClassRequestHandler) handler).getPageClass().getName();
|
||||
// wicket filter mapping without wildcard, if wicket filter is mapped to /*
|
||||
// this will be an empty string
|
||||
String filterPath = RequestCycle.get().getRequest().getFilterPath();
|
||||
serverSpan.updateName(
|
||||
ServletContextPath.prepend(
|
||||
Java8BytecodeBridge.currentContext(), filterPath + "/" + pageName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.wicket;
|
||||
|
||||
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.ClassLoaderMatcher.hasClassesNamed;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import io.opentelemetry.javaagent.tooling.InstrumentationModule;
|
||||
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
|
||||
@AutoService(InstrumentationModule.class)
|
||||
public class WicketInstrumentationModule extends InstrumentationModule {
|
||||
|
||||
public WicketInstrumentationModule() {
|
||||
super("wicket", "wicket-8.0");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
|
||||
// missing before 8.0
|
||||
return hasClassesNamed("org.apache.wicket.request.RequestHandlerExecutor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TypeInstrumentation> typeInstrumentations() {
|
||||
return Arrays.asList(
|
||||
new RequestHandlerExecutorInstrumentation(), new DefaultExceptionMapperInstrumentation());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import static io.opentelemetry.instrumentation.test.utils.TraceUtils.basicSpan
|
||||
|
||||
import hello.HelloApplication
|
||||
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
|
||||
import io.opentelemetry.instrumentation.test.base.HttpServerTestTrait
|
||||
import javax.servlet.DispatcherType
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.Response
|
||||
import org.apache.wicket.protocol.http.WicketFilter
|
||||
import org.eclipse.jetty.server.Server
|
||||
import org.eclipse.jetty.servlet.DefaultServlet
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler
|
||||
import org.eclipse.jetty.util.resource.FileResource
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
class WicketTest extends AgentInstrumentationSpecification implements HttpServerTestTrait<Server> {
|
||||
|
||||
@Override
|
||||
Server startServer(int port) {
|
||||
def server = new Server(port)
|
||||
ServletContextHandler context = new ServletContextHandler(0)
|
||||
context.setContextPath(getContextPath())
|
||||
def resource = new FileResource(getClass().getResource("/"))
|
||||
context.setBaseResource(resource)
|
||||
server.setHandler(context)
|
||||
|
||||
context.addServlet(DefaultServlet, "/")
|
||||
def registration = context.getServletContext().addFilter("WicketApplication", WicketFilter)
|
||||
registration.setInitParameter("applicationClassName", HelloApplication.getName())
|
||||
registration.setInitParameter("filterMappingUrlPattern", "/wicket-test/*")
|
||||
registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/wicket-test/*")
|
||||
|
||||
server.start()
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
@Override
|
||||
void stopServer(Server server) {
|
||||
server.stop()
|
||||
server.destroy()
|
||||
}
|
||||
|
||||
@Override
|
||||
String getContextPath() {
|
||||
return "/jetty-context"
|
||||
}
|
||||
|
||||
def "test hello"() {
|
||||
setup:
|
||||
def url = HttpUrl.get(address.resolve("wicket-test/")).newBuilder().build()
|
||||
def request = request(url, "GET", null).build()
|
||||
Response response = client.newCall(request).execute()
|
||||
def doc = Jsoup.parse(response.body().string())
|
||||
|
||||
expect:
|
||||
response.code() == 200
|
||||
doc.selectFirst("#message").text() == "Hello World!"
|
||||
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
basicSpan(it, 0, getContextPath() + "/wicket-test/hello.HelloPage")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "test exception"() {
|
||||
setup:
|
||||
def url = HttpUrl.get(address.resolve("wicket-test/exception")).newBuilder().build()
|
||||
def request = request(url, "GET", null).build()
|
||||
Response response = client.newCall(request).execute()
|
||||
|
||||
expect:
|
||||
response.code() == 500
|
||||
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
basicSpan(it, 0, getContextPath() + "/wicket-test/org.apache.wicket.markup.html.pages.InternalErrorPage", null, new Exception("test exception"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Request.Builder request(HttpUrl url, String method, RequestBody body) {
|
||||
return new Request.Builder()
|
||||
.url(url)
|
||||
.method(method, body)
|
||||
.header("User-Agent", TEST_USER_AGENT)
|
||||
.header("X-Forwarded-For", TEST_CLIENT_IP)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package hello
|
||||
|
||||
import org.apache.wicket.markup.html.WebPage
|
||||
|
||||
class ExceptionPage extends WebPage {
|
||||
ExceptionPage() {
|
||||
throw new Exception("test exception")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package hello
|
||||
|
||||
import org.apache.wicket.Page
|
||||
import org.apache.wicket.RuntimeConfigurationType
|
||||
import org.apache.wicket.protocol.http.WebApplication
|
||||
|
||||
class HelloApplication extends WebApplication {
|
||||
@Override
|
||||
Class<? extends Page> getHomePage() {
|
||||
HelloPage
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init()
|
||||
|
||||
mountPage("/exception", ExceptionPage)
|
||||
}
|
||||
|
||||
@Override
|
||||
RuntimeConfigurationType getConfigurationType() {
|
||||
return RuntimeConfigurationType.DEPLOYMENT
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package hello
|
||||
|
||||
import org.apache.wicket.markup.html.WebPage
|
||||
import org.apache.wicket.markup.html.basic.Label
|
||||
|
||||
class HelloPage extends WebPage {
|
||||
HelloPage() {
|
||||
add(new Label("message", "Hello World!"))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
|
||||
<body>
|
||||
<span wicket:id="message" id="message">Message goes here</span>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,21 @@
|
|||
apply from: "$rootDir/gradle/instrumentation.gradle"
|
||||
|
||||
muzzle {
|
||||
pass {
|
||||
group = 'org.apache.wicket'
|
||||
module = 'wicket'
|
||||
versions = "[8.0.0,]"
|
||||
assertInverse = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
library group: 'org.apache.wicket', name: 'wicket', version: '8.0.0'
|
||||
|
||||
testImplementation(project(':testing-common'))
|
||||
testImplementation group: 'org.jsoup', name: 'jsoup', version: '1.13.1'
|
||||
testImplementation group: 'org.eclipse.jetty', name: 'jetty-server', version: '8.0.0.v20110901'
|
||||
testImplementation group: 'org.eclipse.jetty', name: 'jetty-servlet', version: '8.0.0.v20110901'
|
||||
|
||||
testInstrumentation project(":instrumentation:servlet:servlet-3.0:javaagent")
|
||||
}
|
|
@ -213,6 +213,7 @@ include ':instrumentation:twilio-6.6:javaagent'
|
|||
include ':instrumentation:undertow:javaagent'
|
||||
include ':instrumentation:vertx-web-3.0'
|
||||
include ':instrumentation:vertx-reactive-3.5:javaagent'
|
||||
include ':instrumentation:wicket-8.0:javaagent'
|
||||
|
||||
include ':instrumentation-core:reactor-3.1'
|
||||
include ':instrumentation-core:servlet-2.2'
|
||||
|
|
Loading…
Reference in New Issue