Skip to content

Commit

Permalink
Wicket instrumentation (#2139)
Browse files Browse the repository at this point in the history
* Wicket instrumentation

* Change supported version to 8.0, turns out earlier versions didn't work
  • Loading branch information
laurit committed Feb 3, 2021
1 parent 71ceb74 commit 02ca471
Show file tree
Hide file tree
Showing 11 changed files with 334 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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+ |
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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")
}
}
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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!"))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http:https://www.w3.org/1999/xhtml" xmlns:wicket="http:https://wicket.apache.org">
<body>
<span wicket:id="message" id="message">Message goes here</span>
</body>
</html>
21 changes: 21 additions & 0 deletions instrumentation/wicket-8.0/javaagent/wicket-8.0-javaagent.gradle
Original file line number Diff line number Diff line change
@@ -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")
}
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down

0 comments on commit 02ca471

Please sign in to comment.