diff --git a/buildSrc/src/main/groovy/MuzzlePlugin.groovy b/buildSrc/src/main/groovy/MuzzlePlugin.groovy index f8221de0d4..71420ccb22 100644 --- a/buildSrc/src/main/groovy/MuzzlePlugin.groovy +++ b/buildSrc/src/main/groovy/MuzzlePlugin.groovy @@ -24,6 +24,7 @@ import org.gradle.api.model.ObjectFactory import java.lang.reflect.Method import java.security.SecureClassLoader import java.util.concurrent.atomic.AtomicReference +import java.util.regex.Pattern /** * muzzle task plugin which runs muzzle validation against a range of dependencies. @@ -36,7 +37,8 @@ class MuzzlePlugin implements Plugin { private static final AtomicReference TOOLING_LOADER = new AtomicReference<>() static { RemoteRepository central = new RemoteRepository.Builder("central", "default", "https://repo1.maven.org/maven2/").build() - MUZZLE_REPOS = new ArrayList(Arrays.asList(central)) + RemoteRepository typesafe = new RemoteRepository.Builder("typesafe", "default", "https://repo.typesafe.com/typesafe/releases").build() + MUZZLE_REPOS = new ArrayList(Arrays.asList(central, typesafe)) } @Override @@ -343,6 +345,8 @@ class MuzzlePlugin implements Plugin { return session } + private static final Pattern GIT_SHA_PATTERN = Pattern.compile('^.*-[0-9a-f]{7,}$') + /** * Filter out snapshot-type builds from versions list. */ @@ -357,7 +361,8 @@ class MuzzlePlugin implements Plugin { version.contains(".m") || version.contains("-m") || version.contains("-dev") || - version.contains("public_draft") + version.contains("public_draft") || + version.matches(GIT_SHA_PATTERN) } return list } diff --git a/dd-java-agent/instrumentation/play-2.3/.gitignore b/dd-java-agent/instrumentation/play-2.3/.gitignore new file mode 100644 index 0000000000..5292519a25 --- /dev/null +++ b/dd-java-agent/instrumentation/play-2.3/.gitignore @@ -0,0 +1 @@ +logs/ \ No newline at end of file diff --git a/dd-java-agent/instrumentation/play-2.3/play-2.3.gradle b/dd-java-agent/instrumentation/play-2.3/play-2.3.gradle new file mode 100644 index 0000000000..4e74546bc0 --- /dev/null +++ b/dd-java-agent/instrumentation/play-2.3/play-2.3.gradle @@ -0,0 +1,58 @@ +ext { + minJavaVersionForTests = JavaVersion.VERSION_1_8 + // Play doesn't work with Java 9+ until 2.6.12 + maxJavaVersionForTests = JavaVersion.VERSION_1_8 +} + +muzzle { + pass { + group = 'com.typesafe.play' + module = 'play_2.11' + versions = '[2.3.0,2.4)' + assertInverse = true + } + fail { + group = 'com.typesafe.play' + module = 'play_2.12' + versions = '[,]' + } + fail { + group = 'com.typesafe.play' + module = 'play_2.13' + versions = '[,]' + } +} + +apply from: "${rootDir}/gradle/java.gradle" +apply from: "${rootDir}/gradle/test-with-scala.gradle" + +apply plugin: 'org.unbroken-dome.test-sets' + +testSets { + latestDepTest { + dirName = 'test' + } +} + +dependencies { + main_java8Compile group: 'com.typesafe.play', name: 'play_2.11', version: '2.3.0' + + testCompile project(':dd-java-agent:instrumentation:netty-3.8') + + testCompile group: 'com.typesafe.play', name: 'play-java_2.11', version: '2.3.0' + testCompile group: 'com.typesafe.play', name: 'play-java-ws_2.11', version: '2.3.0' + testCompile(group: 'com.typesafe.play', name: 'play-test_2.11', version: '2.3.0') { + exclude group: 'org.eclipse.jetty', module: 'jetty-websocket' + } + + latestDepTestCompile group: 'com.typesafe.play', name: 'play-java_2.11', version: '2.3.+' + latestDepTestCompile group: 'com.typesafe.play', name: 'play-java-ws_2.11', version: '2.3.+' + latestDepTestCompile(group: 'com.typesafe.play', name: 'play-test_2.11', version: '2.3.+') { + exclude group: 'org.eclipse.jetty', module: 'jetty-websocket' + } +} + +compileLatestDepTestGroovy { + classpath = classpath.plus(files(compileLatestDepTestScala.destinationDir)) + dependsOn compileLatestDepTestScala +} diff --git a/dd-java-agent/instrumentation/play-2.3/src/main/java/datadog/trace/instrumentation/play23/PlayInstrumentation.java b/dd-java-agent/instrumentation/play-2.3/src/main/java/datadog/trace/instrumentation/play23/PlayInstrumentation.java new file mode 100644 index 0000000000..1a7ca983e9 --- /dev/null +++ b/dd-java-agent/instrumentation/play-2.3/src/main/java/datadog/trace/instrumentation/play23/PlayInstrumentation.java @@ -0,0 +1,52 @@ +package datadog.trace.instrumentation.play23; + +import static datadog.trace.agent.tooling.ClassLoaderMatcher.hasClassesNamed; +import static datadog.trace.agent.tooling.bytebuddy.matcher.DDElementMatchers.implementsInterface; +import static java.util.Collections.singletonMap; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import java.util.Map; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(Instrumenter.class) +public final class PlayInstrumentation extends Instrumenter.Default { + + public PlayInstrumentation() { + super("play", "play-action"); + } + + @Override + public ElementMatcher classLoaderMatcher() { + // Optimization for expensive typeMatcher. + return hasClassesNamed("play.api.mvc.Action"); + } + + @Override + public ElementMatcher typeMatcher() { + return implementsInterface(named("play.api.mvc.Action")); + } + + @Override + public String[] helperClassNames() { + return new String[] { + packageName + ".PlayHttpServerDecorator", + packageName + ".RequestCompleteCallback", + packageName + ".PlayHeaders", + }; + } + + @Override + public Map, String> transformers() { + return singletonMap( + named("apply") + .and(takesArgument(0, named("play.api.mvc.Request"))) + .and(returns(named("scala.concurrent.Future"))), + packageName + ".PlayAdvice"); + } +} diff --git a/dd-java-agent/instrumentation/play-2.3/src/main/java8/datadog/trace/instrumentation/play23/PlayAdvice.java b/dd-java-agent/instrumentation/play-2.3/src/main/java8/datadog/trace/instrumentation/play23/PlayAdvice.java new file mode 100644 index 0000000000..7108037f96 --- /dev/null +++ b/dd-java-agent/instrumentation/play-2.3/src/main/java8/datadog/trace/instrumentation/play23/PlayAdvice.java @@ -0,0 +1,68 @@ +package datadog.trace.instrumentation.play23; + +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.propagate; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static datadog.trace.instrumentation.play23.PlayHeaders.GETTER; +import static datadog.trace.instrumentation.play23.PlayHttpServerDecorator.DECORATE; + +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan.Context; +import datadog.trace.bootstrap.instrumentation.api.Tags; +import net.bytebuddy.asm.Advice; +import play.api.mvc.Action; +import play.api.mvc.Request; +import play.api.mvc.Result; +import scala.concurrent.Future; + +public class PlayAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static AgentScope onEnter(@Advice.Argument(0) final Request req) { + final AgentSpan span; + if (activeSpan() == null) { + final Context extractedContext = propagate().extract(req.headers(), GETTER); + span = startSpan("play.request", extractedContext); + } else { + // An upstream framework (e.g. akka-http, netty) has already started the span. + // Do not extract the context. + span = startSpan("play.request"); + } + DECORATE.afterStart(span); + DECORATE.onConnection(span, req); + + final AgentScope scope = activateSpan(span, false); + scope.setAsyncPropagation(true); + return scope; + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void stopTraceOnResponse( + @Advice.Enter final AgentScope playControllerScope, + @Advice.This final Object thisAction, + @Advice.Thrown final Throwable throwable, + @Advice.Argument(0) final Request req, + @Advice.Return(readOnly = false) final Future responseFuture) { + final AgentSpan playControllerSpan = playControllerScope.span(); + + // Call onRequest on return after tags are populated. + DECORATE.onRequest(playControllerSpan, req); + + if (throwable == null) { + responseFuture.onComplete( + new RequestCompleteCallback(playControllerSpan), + ((Action) thisAction).executionContext()); + } else { + DECORATE.onError(playControllerSpan, throwable); + playControllerSpan.setTag(Tags.HTTP_STATUS, 500); + DECORATE.beforeFinish(playControllerSpan); + playControllerSpan.finish(); + } + playControllerScope.close(); + + final AgentSpan rootSpan = activeSpan(); + // set the resource name on the upstream akka/netty span + DECORATE.onRequest(rootSpan, req); + } +} diff --git a/dd-java-agent/instrumentation/play-2.3/src/main/java8/datadog/trace/instrumentation/play23/PlayHeaders.java b/dd-java-agent/instrumentation/play-2.3/src/main/java8/datadog/trace/instrumentation/play23/PlayHeaders.java new file mode 100644 index 0000000000..a0c33050be --- /dev/null +++ b/dd-java-agent/instrumentation/play-2.3/src/main/java8/datadog/trace/instrumentation/play23/PlayHeaders.java @@ -0,0 +1,26 @@ +package datadog.trace.instrumentation.play23; + +import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; +import play.api.mvc.Headers; +import scala.Option; +import scala.collection.JavaConversions; + +public class PlayHeaders implements AgentPropagation.Getter { + + public static final PlayHeaders GETTER = new PlayHeaders(); + + @Override + public Iterable keys(final Headers headers) { + return JavaConversions.asJavaIterable(headers.keys()); + } + + @Override + public String get(final Headers headers, final String key) { + final Option option = headers.get(key); + if (option.isDefined()) { + return option.get(); + } else { + return null; + } + } +} diff --git a/dd-java-agent/instrumentation/play-2.3/src/main/java8/datadog/trace/instrumentation/play23/PlayHttpServerDecorator.java b/dd-java-agent/instrumentation/play-2.3/src/main/java8/datadog/trace/instrumentation/play23/PlayHttpServerDecorator.java new file mode 100644 index 0000000000..09878f9ba0 --- /dev/null +++ b/dd-java-agent/instrumentation/play-2.3/src/main/java8/datadog/trace/instrumentation/play23/PlayHttpServerDecorator.java @@ -0,0 +1,86 @@ +package datadog.trace.instrumentation.play23; + +import datadog.trace.api.DDTags; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.Tags; +import datadog.trace.bootstrap.instrumentation.decorator.HttpServerDecorator; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.UndeclaredThrowableException; +import java.net.URI; +import java.net.URISyntaxException; +import lombok.extern.slf4j.Slf4j; +import play.api.mvc.Request; +import play.api.mvc.Result; +import scala.Option; + +@Slf4j +public class PlayHttpServerDecorator extends HttpServerDecorator { + public static final PlayHttpServerDecorator DECORATE = new PlayHttpServerDecorator(); + + @Override + protected String[] instrumentationNames() { + return new String[] {"play"}; + } + + @Override + protected String component() { + return "play-action"; + } + + @Override + protected String method(final Request httpRequest) { + return httpRequest.method(); + } + + @Override + protected URI url(final Request request) throws URISyntaxException { + return new URI((request.secure() ? "https://" : "http://") + request.host() + request.uri()); + } + + @Override + protected String peerHostIP(final Request request) { + return request.remoteAddress(); + } + + @Override + protected Integer peerPort(final Request request) { + return null; + } + + @Override + protected Integer status(final Result httpResponse) { + return httpResponse.header().status(); + } + + @Override + public AgentSpan onRequest(final AgentSpan span, final Request request) { + super.onRequest(span, request); + if (request != null) { + // more about routes here: + // https://github.com/playframework/playframework/blob/master/documentation/manual/releases/release26/migration26/Migration26.md#router-tags-are-now-attributes + final Option pathOption = request.tags().get("ROUTE_PATTERN"); + if (!pathOption.isEmpty()) { + final String path = (String) pathOption.get(); + span.setTag(DDTags.RESOURCE_NAME, request.method() + " " + path); + } + } + return span; + } + + @Override + public AgentSpan onError(final AgentSpan span, Throwable throwable) { + span.setTag(Tags.HTTP_STATUS, 500); + if (throwable != null + // This can be moved to instanceof check when using Java 8. + && throwable.getClass().getName().equals("java.util.concurrent.CompletionException") + && throwable.getCause() != null) { + throwable = throwable.getCause(); + } + while ((throwable instanceof InvocationTargetException + || throwable instanceof UndeclaredThrowableException) + && throwable.getCause() != null) { + throwable = throwable.getCause(); + } + return super.onError(span, throwable); + } +} diff --git a/dd-java-agent/instrumentation/play-2.3/src/main/java8/datadog/trace/instrumentation/play23/RequestCompleteCallback.java b/dd-java-agent/instrumentation/play-2.3/src/main/java8/datadog/trace/instrumentation/play23/RequestCompleteCallback.java new file mode 100644 index 0000000000..e1c5ff9dc3 --- /dev/null +++ b/dd-java-agent/instrumentation/play-2.3/src/main/java8/datadog/trace/instrumentation/play23/RequestCompleteCallback.java @@ -0,0 +1,40 @@ +package datadog.trace.instrumentation.play23; + +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeScope; +import static datadog.trace.instrumentation.play23.PlayHttpServerDecorator.DECORATE; + +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.context.TraceScope; +import lombok.extern.slf4j.Slf4j; +import play.api.mvc.Result; +import scala.util.Try; + +@Slf4j +public class RequestCompleteCallback extends scala.runtime.AbstractFunction1, Object> { + private final AgentSpan span; + + public RequestCompleteCallback(final AgentSpan span) { + this.span = span; + } + + @Override + public Object apply(final Try result) { + try { + if (result.isFailure()) { + DECORATE.onError(span, result.failed().get()); + } else { + DECORATE.onResponse(span, result.get()); + } + DECORATE.beforeFinish(span); + final TraceScope scope = activeScope(); + if (scope != null) { + scope.setAsyncPropagation(false); + } + } catch (final Throwable t) { + log.debug("error in play instrumentation", t); + } finally { + span.finish(); + } + return null; + } +} diff --git a/dd-java-agent/instrumentation/play-2.3/src/test/groovy/client/PlayWSClientTest.groovy b/dd-java-agent/instrumentation/play-2.3/src/test/groovy/client/PlayWSClientTest.groovy new file mode 100644 index 0000000000..a72b00c7b3 --- /dev/null +++ b/dd-java-agent/instrumentation/play-2.3/src/test/groovy/client/PlayWSClientTest.groovy @@ -0,0 +1,82 @@ +package client + +import datadog.trace.agent.test.base.HttpClientTest +import datadog.trace.instrumentation.netty38.client.NettyHttpClientDecorator +import play.GlobalSettings +import play.libs.ws.WS +import play.test.FakeApplication +import play.test.Helpers +import spock.lang.Shared + +import java.util.concurrent.TimeUnit + +class PlayWSClientTest extends HttpClientTest { + @Shared + def application = new FakeApplication( + new File("."), + FakeApplication.getClassLoader(), + [ + "ws.timeout.connection": CONNECT_TIMEOUT_MS, + "ws.timeout.request" : READ_TIMEOUT_MS + ], + Collections.emptyList(), + new GlobalSettings() + ) + + @Shared + def client + + def setupSpec() { + Helpers.start(application) + client = WS.client() + } + + def cleanupSpec() { + Helpers.stop(application) + } + + @Override + int doRequest(String method, URI uri, Map headers, Closure callback) { + def request = client.url(uri.toString()) + headers.entrySet().each { + request.setHeader(it.key, it.value) + } + + def status = request.execute(method).map({ + callback?.call() + it + }).map({ + it.status + }) + return status.get(1, TimeUnit.SECONDS) + } + + @Override + String component() { + return NettyHttpClientDecorator.DECORATE.component() + } + + @Override + String expectedOperationName() { + return "netty.client.request" + } + + @Override + boolean testRedirects() { + false + } + + @Override + boolean testConnectionFailure() { + false + } + + @Override + boolean testRemoteConnection() { + // On connection failures the operation and resource names end up different from expected. + // This would require a lot of changes to the base client test class to support + // span.operationName = "netty.connect" + // span.resourceName = "netty.connect" + false + } +} diff --git a/dd-java-agent/instrumentation/play-2.3/src/test/groovy/server/NettyServerTestInstrumentation.java b/dd-java-agent/instrumentation/play-2.3/src/test/groovy/server/NettyServerTestInstrumentation.java new file mode 100644 index 0000000000..b94ea96264 --- /dev/null +++ b/dd-java-agent/instrumentation/play-2.3/src/test/groovy/server/NettyServerTestInstrumentation.java @@ -0,0 +1,23 @@ +package server; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.test.base.HttpServerTestAdvice; +import datadog.trace.agent.tooling.Instrumenter; +import net.bytebuddy.agent.builder.AgentBuilder; + +@AutoService(Instrumenter.class) +public class NettyServerTestInstrumentation implements Instrumenter { + + @Override + public AgentBuilder instrument(final AgentBuilder agentBuilder) { + return agentBuilder + .type(named("org.jboss.netty.handler.codec.http.HttpRequestDecoder")) + .transform( + new AgentBuilder.Transformer.ForAdvice() + .advice( + named("createMessage"), + HttpServerTestAdvice.ServerEntryAdvice.class.getName())); + } +} diff --git a/dd-java-agent/instrumentation/play-2.3/src/test/groovy/server/PlayAsyncServerTest.groovy b/dd-java-agent/instrumentation/play-2.3/src/test/groovy/server/PlayAsyncServerTest.groovy new file mode 100644 index 0000000000..71dfc6a9c6 --- /dev/null +++ b/dd-java-agent/instrumentation/play-2.3/src/test/groovy/server/PlayAsyncServerTest.groovy @@ -0,0 +1,12 @@ +package server + +import play.api.test.TestServer + +class PlayAsyncServerTest extends PlayServerTest { + @Override + TestServer startServer(int port) { + def server = AsyncServer.server(port) + server.start() + return server + } +} diff --git a/dd-java-agent/instrumentation/play-2.3/src/test/groovy/server/PlayServerTest.groovy b/dd-java-agent/instrumentation/play-2.3/src/test/groovy/server/PlayServerTest.groovy new file mode 100644 index 0000000000..c8ce2c32c5 --- /dev/null +++ b/dd-java-agent/instrumentation/play-2.3/src/test/groovy/server/PlayServerTest.groovy @@ -0,0 +1,77 @@ +package server + +import datadog.opentracing.DDSpan +import datadog.trace.agent.test.asserts.TraceAssert +import datadog.trace.agent.test.base.HttpServerTest +import datadog.trace.api.DDSpanTypes +import datadog.trace.api.DDTags +import datadog.trace.bootstrap.instrumentation.api.Tags +import datadog.trace.instrumentation.netty38.server.NettyHttpServerDecorator +import datadog.trace.instrumentation.play23.PlayHttpServerDecorator +import play.api.test.TestServer + +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.* + +class PlayServerTest extends HttpServerTest { + @Override + TestServer startServer(int port) { + def server = SyncServer.server(port) + server.start() + return server + } + + @Override + void stopServer(TestServer server) { + server.stop() + } + + @Override + String component() { + return NettyHttpServerDecorator.DECORATE.component() + } + + @Override + String expectedOperationName() { + return "netty.request" + } + + // We don't have instrumentation for this version of netty yet + @Override + boolean hasHandlerSpan() { + true + } + + @Override + // Return the handler span's name + String reorderHandlerSpan() { + "play.request" + } + + @Override + void handlerSpan(TraceAssert trace, int index, Object parent, ServerEndpoint endpoint = SUCCESS) { + trace.span(index) { + serviceName expectedServiceName() + operationName "play.request" + spanType DDSpanTypes.HTTP_SERVER + errored endpoint == ERROR || endpoint == EXCEPTION + childOf(parent as DDSpan) + tags { + "$Tags.COMPONENT" PlayHttpServerDecorator.DECORATE.component() + "$Tags.SPAN_KIND" Tags.SPAN_KIND_SERVER + "$Tags.PEER_HOST_IPV4" { it == null || it == "127.0.0.1" } // Optional + "$Tags.HTTP_URL" String + "$Tags.HTTP_METHOD" String + "$Tags.HTTP_STATUS" Integer + if (endpoint == ERROR) { + "$Tags.ERROR" true + } else if (endpoint == EXCEPTION) { + errorTags(Exception, EXCEPTION.body) + } + if (endpoint.query) { + "$DDTags.HTTP_QUERY" endpoint.query + } + defaultTags() + } + } + } +} diff --git a/dd-java-agent/instrumentation/play-2.3/src/test/scala/server/AsyncServer.scala b/dd-java-agent/instrumentation/play-2.3/src/test/scala/server/AsyncServer.scala new file mode 100644 index 0000000000..a1789635a0 --- /dev/null +++ b/dd-java-agent/instrumentation/play-2.3/src/test/scala/server/AsyncServer.scala @@ -0,0 +1,26 @@ +package server + +import datadog.trace.agent.test.base.HttpServerTest +import datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint._ +import play.api.mvc.{Action, Handler, Results} +import play.api.test.{FakeApplication, TestServer} + +import scala.concurrent.Future + +object AsyncServer { + val routes: PartialFunction[(String, String), Handler] = { + case ("GET", "/success") => Action.async { request => HttpServerTest.controller(SUCCESS, new AsyncControllerClosureAdapter(Future.successful(Results.Status(SUCCESS.getStatus).apply(SUCCESS.getBody)))) } + case ("GET", "/redirect") => Action.async { request => HttpServerTest.controller(REDIRECT, new AsyncControllerClosureAdapter(Future.successful(Results.Redirect(REDIRECT.getBody, REDIRECT.getStatus)))) } + case ("GET", "/query") => Action.async { result => HttpServerTest.controller(QUERY_PARAM, new AsyncControllerClosureAdapter(Future.successful(Results.Status(QUERY_PARAM.getStatus).apply(QUERY_PARAM.getBody)))) } + case ("GET", "/error-status") => Action.async { result => HttpServerTest.controller(ERROR, new AsyncControllerClosureAdapter(Future.successful(Results.Status(ERROR.getStatus).apply(ERROR.getBody)))) } + case ("GET", "/exception") => Action.async { result => + HttpServerTest.controller(EXCEPTION, new AsyncBlockClosureAdapter(() => { + throw new Exception(EXCEPTION.getBody) + })) + } + } + + def server(port: Int): TestServer = { + TestServer(port, FakeApplication(withGlobal = Some(new Settings()), withRoutes = routes)) + } +} diff --git a/dd-java-agent/instrumentation/play-2.3/src/test/scala/server/ControllerClosureAdapter.scala b/dd-java-agent/instrumentation/play-2.3/src/test/scala/server/ControllerClosureAdapter.scala new file mode 100644 index 0000000000..8cf3766917 --- /dev/null +++ b/dd-java-agent/instrumentation/play-2.3/src/test/scala/server/ControllerClosureAdapter.scala @@ -0,0 +1,22 @@ +package server + +import groovy.lang.Closure +import play.api.mvc.Result + +import scala.concurrent.Future + +class ControllerClosureAdapter(response: Result) extends Closure[Result] { + override def call(): Result = response +} + +class BlockClosureAdapter(block: () => Result) extends Closure[Result] { + override def call(): Result = block() +} + +class AsyncControllerClosureAdapter(response: Future[Result]) extends Closure[Future[Result]] { + override def call(): Future[Result] = response +} + +class AsyncBlockClosureAdapter(block: () => Future[Result]) extends Closure[Future[Result]] { + override def call(): Future[Result] = block() +} diff --git a/dd-java-agent/instrumentation/play-2.3/src/test/scala/server/Settings.scala b/dd-java-agent/instrumentation/play-2.3/src/test/scala/server/Settings.scala new file mode 100644 index 0000000000..04add7c804 --- /dev/null +++ b/dd-java-agent/instrumentation/play-2.3/src/test/scala/server/Settings.scala @@ -0,0 +1,12 @@ +package server + +import play.api.GlobalSettings +import play.api.mvc.{RequestHeader, Result, Results} + +import scala.concurrent.Future + +class Settings extends GlobalSettings { + override def onError(request: RequestHeader, ex: Throwable): Future[Result] = { + Future.successful(Results.InternalServerError(ex.getCause.getMessage)) + } +} diff --git a/dd-java-agent/instrumentation/play-2.3/src/test/scala/server/SyncServer.scala b/dd-java-agent/instrumentation/play-2.3/src/test/scala/server/SyncServer.scala new file mode 100644 index 0000000000..602a14c70e --- /dev/null +++ b/dd-java-agent/instrumentation/play-2.3/src/test/scala/server/SyncServer.scala @@ -0,0 +1,32 @@ +package server + +import datadog.trace.agent.test.base.HttpServerTest +import datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint._ +import play.api.mvc.{Action, Handler, Results} +import play.api.test.{FakeApplication, TestServer} + +object SyncServer { + val routes: PartialFunction[(String, String), Handler] = { + case ("GET", "/success") => Action { request => + HttpServerTest.controller(SUCCESS, new ControllerClosureAdapter(Results.Status(SUCCESS.getStatus).apply(SUCCESS.getBody))) + } + case ("GET", "/redirect") => Action { request => + HttpServerTest.controller(REDIRECT, new ControllerClosureAdapter(Results.Redirect(REDIRECT.getBody, REDIRECT.getStatus))) + } + case ("GET", "/query") => Action { request => + HttpServerTest.controller(QUERY_PARAM, new ControllerClosureAdapter(Results.Status(QUERY_PARAM.getStatus).apply(QUERY_PARAM.getBody))) + } + case ("GET", "/error-status") => Action { request => + HttpServerTest.controller(ERROR, new ControllerClosureAdapter(Results.Status(ERROR.getStatus).apply(ERROR.getBody))) + } + case ("GET", "/exception") => Action { request => + HttpServerTest.controller(EXCEPTION, new BlockClosureAdapter(() => { + throw new Exception(EXCEPTION.getBody) + })) + } + } + + def server(port: Int): TestServer = { + TestServer(port, FakeApplication(withGlobal = Some(new Settings()), withRoutes = routes)) + } +} diff --git a/gradle/java.gradle b/gradle/java.gradle index d2ab140903..5f3e53061f 100644 --- a/gradle/java.gradle +++ b/gradle/java.gradle @@ -131,6 +131,9 @@ repositories { maven { url "https://adoptopenjdk.jfrog.io/adoptopenjdk/jmc-libs-snapshots" } + maven { + url "https://repo.typesafe.com/typesafe/releases" + } } dependencies { diff --git a/settings.gradle b/settings.gradle index e184365078..9e75b55c12 100644 --- a/settings.gradle +++ b/settings.gradle @@ -116,6 +116,7 @@ include ':dd-java-agent:instrumentation:netty-3.8' include ':dd-java-agent:instrumentation:netty-4.0' include ':dd-java-agent:instrumentation:netty-4.1' include ':dd-java-agent:instrumentation:okhttp-3' +include ':dd-java-agent:instrumentation:play-2.3' include ':dd-java-agent:instrumentation:play-2.4' include ':dd-java-agent:instrumentation:play-2.6' include ':dd-java-agent:instrumentation:play-ws'