Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix support for play2.6 versions after play2.6.13 #97

Merged
merged 1 commit into from
Oct 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ test_database*
production/

instrumentation/play-2.6/src/test/scala/router/*.scala
instrumentation/play-2.6.13/src/test/scala/router/*.scala

86 changes: 86 additions & 0 deletions instrumentation/play-2.6.13/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import play.routes.compiler.InjectedRoutesGenerator$
import play.routes.compiler.RoutesCompiler
import play.routes.compiler.RoutesCompiler$
import scala.collection.JavaConversions

apply plugin: 'scala'

repositories {
maven {
url 'https://dl.bintray.com/typesafe/maven-releases/'
}
}

buildscript {
dependencies {
classpath 'com.typesafe.play:routes-compiler_2.11:2.6.0'
}
repositories {
jcenter()
mavenCentral()
}
}

sourceSets.test.scala.srcDir "src/test/java"
sourceSets.test.java.srcDirs = []

compileJava.options.bootstrapClasspath = null

sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8

compileTestJava {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

dependencies {
implementation(project(":newrelic-api"))
implementation(project(":agent-bridge"))
implementation(project(":newrelic-weaver-api"))
implementation(project(":newrelic-weaver-scala-api"))
implementation("org.scala-lang:scala-library:2.11.+")
implementation("com.typesafe.play:play-server_2.11:2.6.13")
testImplementation("com.typesafe.play:play-test_2.11:2.6.13")
testImplementation("com.typesafe.play:play-java_2.11:2.6.13")
testImplementation("com.jayway.restassured:rest-assured:2.7.0")
testImplementation("javax.xml.bind:jaxb-api:2.3.0")
}

jar {
manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.play-2.6.13' }
}

verifyInstrumentation {
passesOnly 'com.typesafe.play:play-server_2.12:[2.6.13,2.7.0-M1)'
passesOnly 'com.typesafe.play:play-server_2.11:[2.6.13,2.7.0-M1)'

// build snapshots
excludeRegex '.*-[0-9]{4}-[0-9]{2}-[0-9]{2}-[a-z0-9]{7}$'
}

test {
onlyIf {
!project.hasProperty('test7')
}
}

compileTestScala {
options.compilerArgs += '-proc:none'

// this manually compiles the conf/routes file into an Routes.scala file, which is subsequently read on startup by our test application
doFirst {
def routeFile = file("src/test/resources/conf/routes")
def outputDirectory = file("src/test/scala")

def RoutesCompiler.RoutesCompilerTask routesCompilerTask = new RoutesCompiler.RoutesCompilerTask(
routeFile, JavaConversions.asScalaBuffer(Collections.<String> emptyList()).toSeq(),
true, false, false)
RoutesCompiler$.MODULE$.compile(routesCompilerTask, InjectedRoutesGenerator$.MODULE$, outputDirectory)
}
}

site {
title 'Play'
type 'Appserver'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
*
* * Copyright 2020 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package com.nr.agent.instrumentation.play26

import com.newrelic.api.agent.Token
import play.api.Application
import play.api.libs.typedmap.TypedKey
import play.api.mvc.{Handler, RequestHeader, Result}

import scala.concurrent.Future

object PlayUtil {

val newRelicToken: TypedKey[Token] = TypedKey.apply("NR-TOKEN")

def appendToken(request: RequestHeader, token: Token) : RequestHeader = {
request.addAttr(newRelicToken, token)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
*
* * Copyright 2020 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package play.api.mvc

import com.newrelic.api.agent.Trace
import com.newrelic.api.agent.weaver.Weaver
import com.newrelic.api.agent.weaver.scala.{ScalaMatchType, ScalaWeave}
import com.nr.agent.instrumentation.play26.PlayUtil

import scala.concurrent.Future

@ScalaWeave(`type` = ScalaMatchType.Trait, `originalName` = "play.api.mvc.Action")
class Action_Instrumentation[A] {
@Trace(async = true, metricName = "Play2Controller")
def apply(request: Request[A]): Future[Result] = {
val tokenOption = request.attrs.get(PlayUtil.newRelicToken)
if (tokenOption.isDefined) {
tokenOption.get.linkAndExpire()
}

Weaver.callOriginal()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
*
* * Copyright 2020 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package play.core.routing

import java.util.logging.Level

import com.newrelic.agent.bridge.AgentBridge
import com.newrelic.api.agent.weaver.Weaver
import com.newrelic.api.agent.weaver.scala.{ScalaMatchType, ScalaWeave}
import com.newrelic.api.agent.{NewRelic, TransactionNamePriority}
import play.api.mvc.Handler
import play.api.routing.HandlerDef

@ScalaWeave(`type` = ScalaMatchType.Trait, `originalName` = "play.core.routing.HandlerInvokerFactory")
class HandlerInvokerFactory_Instrumentation[T] {
def createInvoker(fakeCall: => T, handlerDef: HandlerDef): HandlerInvoker[T] = {
new NewRelicWrapperInvoker(Weaver.callOriginal(), handlerDef)
}
}

//Util classes

class NewRelicWrapperInvoker[A](underlyingInvoker: HandlerInvoker[A], handlerDef: HandlerDef) extends HandlerInvoker[A] {
val PLAY_CONTROLLER_ACTION: String = "PlayControllerAction"
val txName = handlerDef.controller + "." + handlerDef.method

def call(call: => A): Handler = {
try {
NewRelic.getAgent.getLogger.log(Level.FINE, "Setting transaction name to \"{0}\" using Play 2.6 controller action", txName);
NewRelic.getAgent.getTransaction.setTransactionName(TransactionNamePriority.FRAMEWORK_LOW, true, PLAY_CONTROLLER_ACTION, txName);
} catch {
case t: Throwable => AgentBridge.instrumentation.noticeInstrumentationError(t, Weaver.getImplementationTitle());
}
underlyingInvoker.call(call);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
*
* * Copyright 2020 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package play.core.server

import com.newrelic.api.agent.weaver.Weaver
import com.newrelic.api.agent.weaver.scala.{ScalaMatchType, ScalaWeave}
import com.newrelic.api.agent.{NewRelic, Trace}
import com.nr.agent.instrumentation.play26.PlayUtil
import play.api.mvc.{Handler, RequestHeader, Result}
import play.core.ApplicationProvider

import scala.concurrent._;

@ScalaWeave(`type` = ScalaMatchType.Object, `originalName` = "play.core.server.Server")
class Server_Instrumentation {
@Trace(dispatcher = true)
def getHandlerFor(
request: RequestHeader,
applicationProvider: ApplicationProvider
): Either[Future[Result], (RequestHeader, Handler)] = {
val tempResult: Either[Future[Result], (RequestHeader, Handler)] = Weaver.callOriginal()
val result = tempResult.right.map { case (request, handler) =>
// In order to correctly track async work from here we need to stash a token into the request.
val token = NewRelic.getAgent.getTransaction.getToken
(PlayUtil.appendToken(request, token), handler)
}

val tracer = NewRelic.getAgent.getTracedMethod
if (tracer != null) {
tracer.setMetricName("Play2Routing");
}

result
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package play.core.utils

import com.newrelic.api.agent.weaver.scala.{ScalaMatchType, ScalaWeave}

// This is weaved to prevent from matching on version 2.6.12
@ScalaWeave(`type` = ScalaMatchType.Object)
class AsciiSet {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
*
* * Copyright 2020 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package com.nr.agent.instrumentation.play26;

import org.junit.rules.ExternalResource;

import play.Application;
import play.inject.guice.GuiceApplicationBuilder;
import play.test.Helpers;
import play.test.TestServer;

import static play.inject.Bindings.bind;

public class PlayApplicationServerRule extends ExternalResource {

private Application application;
private TestServer testServer;
private final int serverPort;

public PlayApplicationServerRule(int serverPort) {
this.serverPort = serverPort;
}

@Override
protected void before() throws Throwable {
application = new GuiceApplicationBuilder()
.bindings(
bind(SimpleJavaController.class).toSelf().eagerly(),
bind(SimpleScalaController.class).toSelf().eagerly(),
bind(SimpleJavaAction.class).toSelf().eagerly())
.build();

testServer = Helpers.testServer(serverPort, application);
testServer.start();
}

@Override
protected void after() {
testServer.stop();
}

public Application getApplication() {
return application;
}

public TestServer getTestServer() {
return testServer;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
*
* * Copyright 2020 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package com.nr.agent.instrumentation.play26;

import com.newrelic.agent.introspec.InstrumentationTestConfig;
import com.newrelic.agent.introspec.InstrumentationTestRunner;
import com.newrelic.agent.introspec.Introspector;
import com.newrelic.agent.introspec.TracedMetricData;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.io.IOException;
import java.net.ServerSocket;
import java.util.Map;

import static com.jayway.restassured.RestAssured.given;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

@RunWith(InstrumentationTestRunner.class)
@InstrumentationTestConfig(includePrefixes = { "com.nr.agent.instrumentation.play26", "play" })
public class ServerInstrumentationTest {

@Rule
public PlayApplicationServerRule playApplicationServerRule = new PlayApplicationServerRule(InstrumentationTestRunner.getIntrospector().getRandomPort());

@Test
public void testControllerActions() {
given()
.baseUri("http://localhost:" + playApplicationServerRule.getTestServer().port())
.when()
.get("/hello")
.then()
.body(containsString("hello world"))
.statusCode(200);
assertTransactionNameWithMetrics(SimpleJavaController.class.getName(), "hello");

InstrumentationTestRunner.getIntrospector().clear();

given()
.baseUri("http://localhost:" + playApplicationServerRule.getTestServer().port())
.when()
.get("/simple")
.then()
.body(containsString("Simple test"))
.statusCode(200);
assertTransactionNameWithMetrics(SimpleJavaController.class.getName(), "simple");

InstrumentationTestRunner.getIntrospector().clear();

given()
.baseUri("http://localhost:" + playApplicationServerRule.getTestServer().port())
.when()
.get("/index")
.then()
.body(containsString("Ahoy ahoy"))
.statusCode(200);
assertTransactionNameWithMetrics(SimpleJavaController.class.getName(), "index");

InstrumentationTestRunner.getIntrospector().clear();

given()
.baseUri("http://localhost:" + playApplicationServerRule.getTestServer().port())
.when()
.get("/scalaHello")
.then()
.body(containsString("Scala says hello world"))
.statusCode(200);
assertTransactionNameWithMetrics(SimpleScalaController.class.getName(), "scalaHello");

}

private void assertTransactionNameWithMetrics(String controllerName, String controllerMethod) {
// This is not a WebTransaction because our Play instrumentation doesn't set a WebRequest or a
// WebResponse; we instead rely on the Netty and/or Akka instrumentation to do that for us
String expectedTxName = new StringBuilder("OtherTransaction/PlayControllerAction/")
.append(controllerName).append(".").append(controllerMethod)
.toString();

Introspector introspector = InstrumentationTestRunner.getIntrospector();
int finishedTransactionCount = introspector.getFinishedTransactionCount(5000L);
assertEquals(1, finishedTransactionCount);

assertTrue(introspector.getTransactionNames().contains(expectedTxName));

Map<String, TracedMetricData> metricsForTransaction = introspector.getMetricsForTransaction(expectedTxName);
assertTrue(metricsForTransaction.containsKey("Play2Routing"));
}

}
Loading