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

Add new akka-http-2.13_10.1.8 instrumentation module #271

Merged
merged 11 commits into from
May 3, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ sourceSets.test.scala.srcDir "src/test/java"
sourceSets.test.java.srcDirs = []

jar {
manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.akka-http-2.4.5' }
manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.akka-http-2.11_2.4.5' }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed the module to make the Scala version clear.

}

dependencies {
Expand All @@ -21,18 +21,21 @@ dependencies {

verifyInstrumentation {
passesOnly 'com.typesafe.akka:akka-http-experimental_2.11:[2.4.5,)'
passesOnly 'com.typesafe.akka:akka-http-experimental_2.10:[2.4.5,)'
passesOnly 'com.typesafe.akka:akka-http_2.10:[2.4.5,)'
passesOnly('com.typesafe.akka:akka-http_2.11:[2.4.5,)') {
passesOnly('com.typesafe.akka:akka-http_2.11:[10.0.0,)') {
compile("com.typesafe.akka:akka-stream_2.11:2.5.19")
}
passesOnly('com.typesafe.akka:akka-http_2.12:[2.4.5,)') {
Copy link
Contributor Author

@jasonjkeller jasonjkeller Apr 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the Scala 2.10 verifier checks from the old instrumentation module because there are no 2.10 compiled versions >= 2.4.5 on akka-http-experimental or akka-http thus they were never being verified against.

I also updated the start range for Scala 2.11 and 2.12 because 10.0.0 is the earliest version available, not 2.4.5.

passesOnly('com.typesafe.akka:akka-http_2.12:[10.0.0,)') {
compile("com.typesafe.akka:akka-stream_2.11:2.5.19")
}
fails('com.typesafe.akka:akka-http_2.13:[10.1.8,)')

excludeRegex 'com.typesafe.akka:akka-http-experimental_2.11:.*(RC|M)[0-9]*$'
excludeRegex 'com.typesafe.akka:akka-http_2.11:.*(RC|M)[0-9]*$'
excludeRegex 'com.typesafe.akka:akka-http_2.12:.*(RC|M)[0-9]*$'
excludeRegex 'com.typesafe.akka:akka-http_2.13:.*(RC|M)[0-9]*$'
excludeRegex 'com.typesafe.akka:akka-http_2.11:.*-[0-9a-f]{8}$'
excludeRegex 'com.typesafe.akka:akka-http_2.12:.*-[0-9a-f]{8}$'
excludeRegex 'com.typesafe.akka:akka-http_2.13:.*-[0-9a-f]{8}$'
}

site {
Expand Down
41 changes: 41 additions & 0 deletions instrumentation/akka-http-2.13_10.1.8/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
apply plugin: 'scala'

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

jar {
manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.akka-http-2.13_10.1.8' }
}

dependencies {
implementation(project(":agent-bridge"))
implementation("com.typesafe.akka:akka-http_2.13:10.1.8")
implementation("com.typesafe.akka:akka-stream_2.13:2.5.23")
implementation("com.typesafe.akka:akka-actor_2.13:2.5.23")

testImplementation(project(":instrumentation:akka-2.2")) { transitive = false }
testImplementation(project(":instrumentation:scala-2.13.0")) { transitive = false }
testImplementation("com.jayway.restassured:rest-assured:2.7.0")
testImplementation("javax.xml.bind:jaxb-api:2.3.0")
}

verifyInstrumentation {
passesOnly('com.typesafe.akka:akka-http_2.13:[10.1.8,)') {
compile("com.typesafe.akka:akka-stream_2.13:2.5.23")
}
excludeRegex 'com.typesafe.akka:akka-http_2.13:.*(RC|M)[0-9]*$'
excludeRegex 'com.typesafe.akka:akka-http_2.13:.*-[0-9a-f]{8}$'
}

site {
title 'Akka Http'
type 'Framework'
}

test {
// our dependency on rest-assured precludes running the tests on java 1.7 due to
// akka http only being able to run on java 1.8 and above from this version onward
onlyIf {
!project.hasProperty("test7")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
*
* * Copyright 2020 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package akka.http.scaladsl.marshalling;

import akka.http.scaladsl.model.HttpRequest;
import akka.http.scaladsl.model.HttpResponse;
import com.agent.instrumentation.akka.http.PathMatcherUtils;
import com.agent.instrumentation.akka.http.RequestWrapper;
import com.newrelic.api.agent.NewRelic;
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;
import scala.concurrent.ExecutionContext;
import scala.concurrent.Future;

@Weave(originalName = "akka.http.scaladsl.marshalling.Marshal")
public class AkkaHttpMarshal<A> {

public Future<HttpResponse> toResponseFor(HttpRequest request, Marshaller<A, HttpResponse> m, ExecutionContext ec) {
NewRelic.getAgent().getTransaction().setWebRequest(new RequestWrapper(request));
PathMatcherUtils.reset();
return Weaver.callOriginal();
}

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

package akka.http.scaladsl.marshalling;

import akka.http.scaladsl.model.HttpResponse;
import com.agent.instrumentation.akka.http.ResponseWrapper;
import com.newrelic.agent.bridge.AgentBridge;
import com.newrelic.api.agent.NewRelic;
import com.newrelic.api.agent.Token;
import com.newrelic.api.agent.Trace;
import com.newrelic.api.agent.Transaction;
import com.newrelic.api.agent.weaver.Weaver;
import scala.runtime.AbstractFunction1;

public class AkkaHttpMarshallerMapper extends AbstractFunction1<HttpResponse, HttpResponse> {

private final Token token;

public AkkaHttpMarshallerMapper(Token token) {
this.token = token;
}

@Override
@Trace(async = true)
public HttpResponse apply(HttpResponse httpResponse) {
try {
if (token != null) {
token.linkAndExpire();
}
ResponseWrapper responseWrapper = new ResponseWrapper(httpResponse);
Transaction transaction = NewRelic.getAgent().getTransaction();
transaction.setWebResponse(responseWrapper);
transaction.addOutboundResponseHeaders();
transaction.markResponseSent();

return responseWrapper.response();
} catch (Throwable t) {
AgentBridge.instrumentation.noticeInstrumentationError(t, Weaver.getImplementationTitle());
return httpResponse;
}
}

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

package akka.http.scaladsl.marshalling;

import akka.http.scaladsl.model.HttpResponse;
import com.newrelic.api.agent.Token;
import com.newrelic.api.agent.weaver.MatchType;
import com.newrelic.api.agent.weaver.NewField;
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;

@Weave(type = MatchType.Interface, originalName = "akka.http.scaladsl.marshalling.ToResponseMarshallable")
public abstract class AkkaHttpToResponseMarshallable {

@NewField
public Token token;

public Marshaller<Object, HttpResponse> marshaller() {
Marshaller<Object, HttpResponse> marshaller = Weaver.callOriginal();
AkkaHttpMarshallerMapper akkaHttpMarshallerMapper = new AkkaHttpMarshallerMapper(token);
return marshaller.map(akkaHttpMarshallerMapper);
}

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

package akka.http.scaladsl.server

import com.agent.instrumentation.akka.http.PathMatcherUtils
import com.newrelic.agent.bridge.AgentBridge
import com.newrelic.api.agent.Trace

import java.util.concurrent.LinkedBlockingDeque
import java.util.concurrent.atomic.{AtomicBoolean, AtomicInteger}
import java.util.logging.Level
import scala.collection.mutable
import scala.concurrent.Future
import scala.runtime.AbstractFunction1

object AkkaHttpContextFunction {

final val retransformed = new AtomicBoolean(false)

def contextWrapper(original: Function1[RequestContext, Future[RouteResult]]): Function1[RequestContext, Future[RouteResult]] = {
if (retransformed.compareAndSet(false, true)) {
AgentBridge.getAgent.getLogger.log(Level.FINER, "Retransforming akka.http.scaladsl.server.AkkaHttpContextFunction")
AgentBridge.instrumentation.retransformUninstrumentedClass(classOf[ContextWrapper])
AgentBridge.getAgent.getLogger.log(Level.FINER, "Retransformed akka.http.scaladsl.server.AkkaHttpContextFunction")
}

new ContextWrapper(original)
}

}

class ContextWrapper(original: Function1[RequestContext, Future[RouteResult]]) extends AbstractFunction1[RequestContext, Future[RouteResult]] {

@Trace(dispatcher = true)
override def apply(ctx: RequestContext): Future[RouteResult] = {
try {
val tracedMethod = AgentBridge.getAgent.getTracedMethod
tracedMethod.setMetricName("AkkaHttp")
// Akka-http 10.1.5 uses CallbackRunnable and we lose transaction context between Directives
AgentBridge.getAgent.getTracedMethod.setTrackCallbackRunnable(true);
val token = AgentBridge.getAgent.getTransaction(false).getToken
PathMatcherUtils.setHttpRequest(ctx.request)
// We use this method to wire up our RequestContext wrapper and start our transaction
val newCtx = new NewRelicRequestContextWrapper(ctx, ctx.asInstanceOf[RequestContextImpl], token,
new LinkedBlockingDeque[String], new AtomicBoolean(false), new AtomicInteger(0), new AtomicInteger(0),
new LinkedBlockingDeque[String], new mutable.HashSet[String], ctx.request, ctx.unmatchedPath, ctx.executionContext, ctx.materializer,
ctx.log, ctx.settings, ctx.parserSettings)
original.apply(newCtx)
} catch {
case t: Throwable => {
AgentBridge.instrumentation.noticeInstrumentationError(t, "akka-http-2.4.5")
original.apply(ctx)
}
}
}

override def compose[A](g: (A) => RequestContext): (A) => Future[RouteResult] = original.compose(g)

override def andThen[A](g: (Future[RouteResult]) => A): (RequestContext) => A = original.andThen(g)

override def toString(): String = original.toString()

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

package akka.http.scaladsl.server;

import akka.http.scaladsl.model.Uri;
import com.agent.instrumentation.akka.http.PathMatcherUtils;
import com.newrelic.api.agent.weaver.MatchType;
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;
import scala.Tuple1;
import scala.runtime.BoxedUnit;

@Weave(type = MatchType.ExactClass, originalName = "akka.http.scaladsl.server.PathMatchers")
public class AkkaHttpPathMatchers {

@Weave(type = MatchType.ExactClass, originalName = "akka.http.scaladsl.server.PathMatchers$Slash$")
public static class AkkaHttpSlash$ {

public PathMatcher.Matching<BoxedUnit> apply(final Uri.Path path) {
PathMatcher.Matching<BoxedUnit> matching = Weaver.callOriginal();
PathMatcherUtils.appendSlash(path, matching);
return matching;
}

}

@Weave(type = MatchType.ExactClass, originalName = "akka.http.scaladsl.server.PathMatchers$Remaining$")
public static class AkkaHttpRemaining$ {

public PathMatcher.Matched<Tuple1<String>> apply(final Uri.Path path) {
PathMatcher.Matched<Tuple1<String>> matched = Weaver.callOriginal();
PathMatcherUtils.appendRemaining("Remaining", path, matched);
return matched;
}

}

@Weave(type = MatchType.ExactClass, originalName = "akka.http.scaladsl.server.PathMatchers$RemainingPath$")
public static class AkkaHttpRemainingPath$ {

public PathMatcher.Matched<Tuple1<String>> apply(final Uri.Path path) {
PathMatcher.Matched<Tuple1<String>> matched = Weaver.callOriginal();
PathMatcherUtils.appendRemaining("RemainingPath", path, matched);
return matched;
}

}

@Weave(type = MatchType.BaseClass, originalName = "akka.http.scaladsl.server.PathMatchers$NumberMatcher")
public static class AkkaHttpNumberMatcher<T> {

public PathMatcher.Matching<Tuple1<T>> apply(final Uri.Path path) {
PathMatcher.Matching<Tuple1<T>> matching = Weaver.callOriginal();
PathMatcherUtils.appendNumberMatch(getClass().getSimpleName().replaceAll("\\$", ""), path, matching);
return matching;
}

}

@Weave(type = MatchType.ExactClass, originalName = "akka.http.scaladsl.server.PathMatchers$Segment$")
public static class AkkaHttpSegment$ {

public PathMatcher.Matching<Tuple1<String>> apply(final Uri.Path path) {
PathMatcher.Matching<Tuple1<String>> matching = Weaver.callOriginal();
PathMatcherUtils.appendSegment(path, matching);
return matching;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
*
* * Copyright 2020 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package akka.http.scaladsl.server

import akka.event.LoggingAdapter
import akka.http.scaladsl.marshalling.AkkaHttpToResponseMarshallable
import akka.http.scaladsl.model._
import akka.http.scaladsl.settings.{ParserSettings, RoutingSettings}
import akka.stream.Materializer
import com.agent.instrumentation.akka.http.PathMatcherUtils
import com.newrelic.api.agent.weaver.{Weave, Weaver}

import java.util.concurrent.LinkedBlockingDeque
import java.util.concurrent.atomic.{AtomicBoolean, AtomicInteger}
import scala.collection.mutable
import scala.concurrent.{ExecutionContextExecutor, Future}

@Weave(originalName = "akka.http.scaladsl.server.RequestContextImpl")
abstract class AkkaHttpRequestContext(request: HttpRequest,
unmatchedPath: Uri.Path,
executionContext: ExecutionContextExecutor,
materializer: Materializer,
log: LoggingAdapter,
settings: RoutingSettings,
parserSettings: ParserSettings) {

def complete(trm: AkkaHttpToResponseMarshallable): Future[RouteResult] = {
val contextWrapper = PathMatcherUtils.nrRequestContext.get()
if (trm != null && contextWrapper != null) {
trm.token = contextWrapper.token
}
Weaver.callOriginal() // This ends up calling complete on our NewRelicRequestContextWrapper
}

def reconfigure(executionContext: ExecutionContextExecutor, materializer: Materializer, log: LoggingAdapter, settings: RoutingSettings): RequestContext = {
Weaver.callOriginal()
}

private def copy(request: HttpRequest,
unmatchedPath: Uri.Path,
executionContext: ExecutionContextExecutor,
materializer: Materializer,
log: LoggingAdapter,
settings: RoutingSettings,
parserSettings: ParserSettings): RequestContextImpl = {
return new NewRelicRequestContextWrapper(this, Weaver.callOriginal(), null, new LinkedBlockingDeque[String](),
new AtomicBoolean(false), new AtomicInteger(0), new AtomicInteger(0), new LinkedBlockingDeque[String], new mutable.HashSet[String], request,
unmatchedPath, executionContext, materializer, log, settings, parserSettings)
}
}
Loading