Skip to content

Commit

Permalink
Merge pull request #271 from newrelic/akka-http-2.13_2.4.5
Browse files Browse the repository at this point in the history
Add new akka-http-2.13_10.1.8 instrumentation module
  • Loading branch information
jasonjkeller authored May 3, 2021
2 parents 859bbad + b2debbc commit 3f9d53d
Show file tree
Hide file tree
Showing 59 changed files with 3,889 additions and 6 deletions.
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' }
}

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,)') {
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

0 comments on commit 3f9d53d

Please sign in to comment.