Skip to content

Commit

Permalink
Add support for GWT (#2652)
Browse files Browse the repository at this point in the history
* Add support for GWT

* formatting

* review fixes
  • Loading branch information
laurit authored Mar 29, 2021
1 parent b999e8a commit 3c11613
Show file tree
Hide file tree
Showing 13 changed files with 587 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/supported-libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ These are the supported libraries and frameworks:
| [Google HTTP Client](https://github.com/googleapis/google-http-java-client) | 1.19+ |
| [Grizzly](https://javaee.github.io/grizzly/httpserverframework.html) | 2.0+ (disabled by default) |
| [gRPC](https://github.com/grpc/grpc-java) | 1.5+ |
| [GWT](http://www.gwtproject.org/) | 2.0+ |
| [Hibernate](https://github.com/hibernate/hibernate-orm) | 3.3+ |
| [HttpURLConnection](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/HttpURLConnection.html) | Java 7+ |
| [http4k <sup>&dagger;</sup>](https://www.http4k.org/guide/modules/opentelemetry/) | 3.270.0+ |
Expand Down
93 changes: 93 additions & 0 deletions instrumentation/gwt-2.0/javaagent/gwt-2.0-javaagent.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
apply from: "$rootDir/gradle/instrumentation.gradle"

muzzle {
pass {
group = "com.google.gwt"
module = "gwt-servlet"
versions = "[2.0.0,)"
assertInverse = true
}
}

sourceSets {
testapp {
java
resources {
srcDirs("src/webapp")
}
java.outputDir = file("$buildDir/testapp/classes")
compileClasspath += sourceSets.main.compileClasspath
}
}

dependencies {
// these are needed for compileGwt task
if (findProperty('testLatestDeps')) {
compileOnly 'com.google.gwt:gwt-user:+'
compileOnly 'com.google.gwt:gwt-dev:+'
} else {
compileOnly 'com.google.gwt:gwt-user:2.0.0'
compileOnly 'com.google.gwt:gwt-dev:2.0.0'
}

library 'com.google.gwt:gwt-servlet:2.0.0'

testInstrumentation project(':instrumentation:servlet:servlet-3.0:javaagent')
testInstrumentation project(':instrumentation:servlet:servlet-javax-common:javaagent')
testInstrumentation project(':instrumentation:jetty-8.0:javaagent')

testImplementation "org.testcontainers:selenium:${versions.testcontainers}"
testImplementation 'org.seleniumhq.selenium:selenium-java:3.141.59'

testImplementation(project(':testing-common')) {
exclude group: 'org.eclipse.jetty', module: 'jetty-server'
}
testImplementation group: 'org.eclipse.jetty', name: 'jetty-webapp', version: '9.4.35.v20201120'
}

task copyTestWebapp(type: Copy) {
from file("src/testapp/webapp")
into file("$buildDir/testapp/war")
}

task compileGwt(dependsOn: classes, type: JavaExec) {
// versions before 2.9 require java8
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(8)
}

def extraDir = "$buildDir/testapp/extra"
def warDir = "$buildDir/testapp/war"

outputs.dir warDir

doFirst {
file(warDir).mkdirs()
}

main = 'com.google.gwt.dev.Compiler'

classpath {
[
sourceSets.testapp.java.srcDirs,
sourceSets.testapp.compileClasspath
]
}

args = [
'test.gwt.Greeting', // gwt module
'-war', warDir,
'-logLevel', 'INFO',
'-localWorkers', '2',
'-compileReport',
'-extra', extraDir,
'-draftCompile' // makes compile a bit faster
]
}

test.dependsOn sourceSets.testapp.output, compileGwt, copyTestWebapp

test {
// add test app classes to classpath
classpath = project.sourceSets.test.runtimeClasspath + files("$buildDir/testapp/classes")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.gwt;

import static io.opentelemetry.javaagent.instrumentation.gwt.GwtTracer.tracer;
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.ClassLoaderMatcher.hasClassesNamed;
import static java.util.Collections.singletonList;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

import com.google.auto.service.AutoService;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.tooling.InstrumentationModule;
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
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;

@AutoService(InstrumentationModule.class)
public class GwtInstrumentationModule extends InstrumentationModule {

public GwtInstrumentationModule() {
super("gwt", "gwt-2.0");
}

@Override
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
// class added in gwt 2.0
return hasClassesNamed("com.google.gwt.uibinder.client.UiBinder");
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return singletonList(new RpcInstrumentation());
}

public static class RpcInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<? super TypeDescription> typeMatcher() {
return named("com.google.gwt.user.server.rpc.RPC");
}

@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
Map<ElementMatcher<MethodDescription>, String> transformers = new HashMap<>();

transformers.put(
named("invokeAndEncodeResponse")
.and(takesArguments(5))
.and(takesArgument(0, Object.class))
.and(takesArgument(1, Method.class))
.and(takesArgument(2, Object[].class))
.and(takesArgument(3, named("com.google.gwt.user.server.rpc.SerializationPolicy")))
.and(takesArgument(4, int.class)),
RpcInstrumentation.class.getName() + "$RpcInvokeAdvice");

// encodeResponseForFailure is called by invokeAndEncodeResponse in case of failure
transformers.put(
named("encodeResponseForFailure")
.and(takesArguments(4))
.and(takesArgument(0, Method.class))
.and(takesArgument(1, Throwable.class))
.and(takesArgument(2, named("com.google.gwt.user.server.rpc.SerializationPolicy")))
.and(takesArgument(3, int.class)),
RpcInstrumentation.class.getName() + "$RpcFailureAdvice");

return transformers;
}

public static class RpcInvokeAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.Argument(0) Object target,
@Advice.Argument(1) Method method,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {

context = tracer().startRpcSpan(target, method);
scope = context.makeCurrent();
}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void onExit(
@Advice.Thrown Throwable throwable,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
scope.close();

tracer().endSpan(context, throwable);
}
}

public static class RpcFailureAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(@Advice.Argument(1) Throwable throwable) {
if (throwable == null) {
return;
}
tracer().rpcFailure(throwable);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.gwt;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextKey;
import io.opentelemetry.instrumentation.api.tracer.BaseTracer;
import java.lang.reflect.Method;

public class GwtTracer extends BaseTracer {
private static final ContextKey<Object> RPC_CONTEXT_KEY =
ContextKey.named("opentelemetry-gwt-rpc-context-key");

private static final GwtTracer TRACER = new GwtTracer();

public static GwtTracer tracer() {
return TRACER;
}

private GwtTracer() {
super(GlobalOpenTelemetry.get());
}

public Context startRpcSpan(Object target, Method method) {
String spanName = spanNameForMethod(target.getClass(), method);
Context context = super.startSpan(spanName);
return context.with(RPC_CONTEXT_KEY, Boolean.TRUE);
}

public void endSpan(Context context, Throwable throwable) {
if (throwable != null) {
endExceptionally(context, throwable);
} else {
end(context);
}
}

public void rpcFailure(Throwable throwable) {
Context context = Context.current();
if (context.get(RPC_CONTEXT_KEY) == null) {
// not inside rpc invocation
return;
}

tracer().onException(context, throwable);
}

@Override
protected String getInstrumentationName() {
return "io.opentelemetry.javaagent.gwt-2.0";
}
}
Loading

0 comments on commit 3c11613

Please sign in to comment.