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

Support for akka-http 10.2.0+ #1955

Merged
merged 3 commits into from
Jun 25, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@

package akka.http.scaladsl.server

import com.agent.instrumentation.akka.http.PathMatcherUtils

import java.util.concurrent.LinkedBlockingDeque
import java.util.concurrent.atomic.{AtomicBoolean, AtomicInteger}
import java.util.logging.Level

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ package akka.http.scaladsl.server

import java.util.concurrent.LinkedBlockingDeque
import java.util.concurrent.atomic.{AtomicBoolean, AtomicInteger}

import akka.event.LoggingAdapter
import akka.http.scaladsl.marshalling.AkkaHttpToResponseMarshallable
import akka.http.scaladsl.model._
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
package akka.http.scaladsl.server

import java.util.concurrent.atomic.{AtomicBoolean, AtomicInteger}

import akka.event.LoggingAdapter
import akka.http.scaladsl.marshalling.ToResponseMarshallable
import akka.http.scaladsl.model.{HttpRequest, Uri}
Expand Down
2 changes: 1 addition & 1 deletion instrumentation/akka-http-2.13_10.1.8/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ dependencies {
}

verifyInstrumentation {
passesOnly('com.typesafe.akka:akka-http_2.13:[10.1.8,)') {
passesOnly('com.typesafe.akka:akka-http_2.13:[10.1.8,10.2.0)') {
implementation("com.typesafe.akka:akka-stream_2.13:2.5.23")
}
excludeRegex 'com.typesafe.akka:akka-http_2.13:.*(RC|M)[0-9]*$'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package akka.http.scaladsl;

import com.newrelic.api.agent.weaver.SkipIfPresent;

@SkipIfPresent(originalName = "akka.http.scaladsl.ServerBuilder")
public class ServerBuilder {
}
37 changes: 37 additions & 0 deletions instrumentation/akka-http-2.13_10.2.0/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
apply plugin: 'scala'

isScalaProjectEnabled(project, "scala-2.13")

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

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

dependencies {
implementation(project(":agent-bridge"))
implementation(project(":newrelic-weaver-api"))
implementation(project(":newrelic-weaver-scala-api"))
implementation("com.typesafe.akka:akka-http_2.13:10.2.0")
implementation("com.typesafe.akka:akka-stream_2.13:2.8.5")
implementation("com.typesafe.akka:akka-actor_2.13:2.8.5")

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("jakarta.xml.ws:jakarta.xml.ws-api:2.3.3")
}

verifyInstrumentation {
passesOnly('com.typesafe.akka:akka-http_2.13:[10.2.0,10.5.0)') {
implementation("com.typesafe.akka:akka-stream_2.13:2.8.5")
}
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'
}
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
Loading