This is an automated email from the ASF dual-hosted git repository.

pjfanning pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko-http.git


The following commit(s) were added to refs/heads/main by this push:
     new 85d7243e7 Document Opentelemetry dependencies (#1240)
85d7243e7 is described below

commit 85d7243e745d68c987b79c5c4d56958f582a5c34
Author: PJ Fanning <[email protected]>
AuthorDate: Sun Aug 30 11:59:51 2026 +0100

    Document Opentelemetry dependencies (#1240)
    
    * Document Opentelemetry dependency in startPoolInterface
    
    Added note about Opentelemetry Java Instrumentation dependency in 
startPoolInterface method.
    
    * more doc
---
 .../http/impl/engine/client/PoolMasterActor.scala  |  9 ++-
 .../pekko/http/impl/engine/http2/Http2.scala       | 21 ++++++
 .../impl/engine/server/HttpServerBluePrint.scala   | 11 +++
 .../org/apache/pekko/http/scaladsl/Http.scala      | 14 ++++
 .../http/impl/InstrumentationPointsSpec.scala      | 78 ++++++++++++++++++++++
 .../server/RouteInstrumentationPointsSpec.scala    | 49 ++++++++++++++
 .../pekko/http/scaladsl/server/Directive.scala     |  7 ++
 .../pekko/http/scaladsl/server/PathMatcher.scala   | 15 +++++
 8 files changed, 203 insertions(+), 1 deletion(-)

diff --git 
a/http-core/src/main/scala/org/apache/pekko/http/impl/engine/client/PoolMasterActor.scala
 
b/http-core/src/main/scala/org/apache/pekko/http/impl/engine/client/PoolMasterActor.scala
index fd9521420..ce15c4801 100644
--- 
a/http-core/src/main/scala/org/apache/pekko/http/impl/engine/client/PoolMasterActor.scala
+++ 
b/http-core/src/main/scala/org/apache/pekko/http/impl/engine/client/PoolMasterActor.scala
@@ -136,11 +136,18 @@ private[http] final class PoolMasterActor extends Actor 
with ActorLogging {
   /**
    * Start a new pool interface actor, register it in our maps, and watch its 
death. No actor should
    * currently exist for this pool.
-   *
+   * <p>
+   *   Opentelemetry Java Instrumentation relies on this method so avoid 
changing it. The agent matches
+   *   the mangled name 
`org$apache$pekko$http$impl$engine$client$PoolMasterActor$$startPoolInterface`,
+   *   which only exists in the bytecode because the closure below captures 
this method.
+   *   See https://github.com/apache/pekko-http/issues/1241
+   * </p>
    * @param poolId the pool id this pool corresponds to
    * @param fm the materializer to use for this pool
    * @return the newly created actor ref
    */
+  // see 
https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/6f9ca5672ce84edbbe36ce0e14386c31d68f479f/instrumentation/pekko/pekko-http-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pekkohttp/v1_0/client/PoolMasterActorInstrumentation.java
+  @noinline // Not inlined so that the agent can match the method in the 
bytecode
   private def startPoolInterface(poolId: PoolId)(implicit fm: Materializer): 
PoolInterface = {
     if (statusById.contains(poolId)) {
       throw new IllegalStateException(s"pool interface actor for $poolId 
already exists")
diff --git 
a/http-core/src/main/scala/org/apache/pekko/http/impl/engine/http2/Http2.scala 
b/http-core/src/main/scala/org/apache/pekko/http/impl/engine/http2/Http2.scala
index 66af36641..e4d2b3879 100644
--- 
a/http-core/src/main/scala/org/apache/pekko/http/impl/engine/http2/Http2.scala
+++ 
b/http-core/src/main/scala/org/apache/pekko/http/impl/engine/http2/Http2.scala
@@ -59,6 +59,13 @@ import scala.util.{ Failure, Success }
  * INTERNAL API
  *
  * Internal entry points for Http/2 server
+ *
+ * <p>
+ *   Opentelemetry Java Instrumentation relies on this class so avoid changing 
it. Besides
+ *   [[Http2Ext.bindAndHandleAsync]] the agent also reads the `system` member 
to obtain a dispatcher for
+ *   the response future, so keep that accessor around too.
+ *   See https://github.com/apache/pekko-http/issues/1241
+ * </p>
  */
 @InternalApi
 private[http] final class Http2Ext(implicit val system: ActorSystem)
@@ -73,6 +80,13 @@ private[http] final class Http2Ext(implicit val system: 
ActorSystem)
   val telemetry = TelemetrySpi.create(system)
 
   // TODO: split up similarly to what `Http` does into `serverLayer`, 
`bindAndHandle`, etc.
+  /**
+   * <p>
+   *   Opentelemetry Java Instrumentation relies on this method so avoid 
changing it. HTTP/2 bindings do
+   *   not go through `HttpExt`, so this is the only server entry point the 
agent can hook for HTTP/2.
+   *   See https://github.com/apache/pekko-http/issues/1241
+   * </p>
+   */
   @noinline // Not inlined to permit instrumentation to pass params 
(interface, port) as context to constructed implementation flows
   def bindAndHandleAsync(
       handler: HttpRequest => Future[HttpResponse],
@@ -298,6 +312,13 @@ private[http] final class Http2Ext(implicit val system: 
ActorSystem)
 @InternalApi
 private[http] object Http2 extends ExtensionId[Http2Ext] with 
ExtensionIdProvider {
 
+  /**
+   * <p>
+   *   Opentelemetry Java Instrumentation relies on this attribute key so 
avoid changing it. The agent uses
+   *   it to recognise a request that was replayed through the HTTP/2 stack 
after an h2c upgrade.
+   *   See https://github.com/apache/pekko-http/issues/1241
+   * </p>
+   */
   val streamId = AttributeKey[Int]("x-http2-stream-id")
 
   override def get(system: ActorSystem): Http2Ext = super.get(system)
diff --git 
a/http-core/src/main/scala/org/apache/pekko/http/impl/engine/server/HttpServerBluePrint.scala
 
b/http-core/src/main/scala/org/apache/pekko/http/impl/engine/server/HttpServerBluePrint.scala
index b29c0eb5f..e08901e36 100644
--- 
a/http-core/src/main/scala/org/apache/pekko/http/impl/engine/server/HttpServerBluePrint.scala
+++ 
b/http-core/src/main/scala/org/apache/pekko/http/impl/engine/server/HttpServerBluePrint.scala
@@ -101,6 +101,17 @@ private[http] object HttpServerBluePrint {
       : BidiFlow[HttpResponse, ResponseRenderingContext, RequestOutput, 
RequestOutput, NotUsed] =
     BidiFlow.fromGraph(new ControllerStage(settings, log)).reversed
 
+  /**
+   * Wraps the HTTP/1.1 request/response bidi flow.
+   * <p>
+   *   Opentelemetry Java Instrumentation relies on this method so avoid 
changing it. The agent wraps the
+   *   returned [[BidiFlow]] to start and end server spans, and matches on the 
method name and on the
+   *   [[BidiFlow]] return type.
+   *   See https://github.com/apache/pekko-http/issues/1241
+   * </p>
+   */
+  // see 
https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/6f9ca5672ce84edbbe36ce0e14386c31d68f479f/instrumentation/pekko/pekko-http-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pekkohttp/v1_0/server/HttpServerBluePrintInstrumentation.java
+  @noinline // Not inlined so that the agent can match the method in the 
bytecode
   def requestPreparation(
       settings: ServerSettings): BidiFlow[HttpResponse, HttpResponse, 
RequestOutput, HttpRequest, NotUsed] =
     BidiFlow.fromFlows(Flow[HttpResponse], new PrepareRequests(settings))
diff --git a/http-core/src/main/scala/org/apache/pekko/http/scaladsl/Http.scala 
b/http-core/src/main/scala/org/apache/pekko/http/scaladsl/Http.scala
index 1af6756e1..b79573dc7 100644
--- a/http-core/src/main/scala/org/apache/pekko/http/scaladsl/Http.scala
+++ b/http-core/src/main/scala/org/apache/pekko/http/scaladsl/Http.scala
@@ -589,7 +589,14 @@ class HttpExt @InternalStableApi /* constructor signature 
is hardcoded in Teleme
    * for setting up the HTTPS connection pool, if the request is targeted 
towards an `https` endpoint.
    *
    * Note that the request must have an absolute URI, otherwise the future 
will be completed with an error.
+   *
+   * <p>
+   *   Opentelemetry Java Instrumentation relies on this method so avoid 
changing it. It is the client
+   *   entry point the agent instruments to create HTTP client spans and to 
inject the propagated context.
+   *   See https://github.com/apache/pekko-http/issues/1241
+   * </p>
    */
+  // see 
https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/6f9ca5672ce84edbbe36ce0e14386c31d68f479f/instrumentation/pekko/pekko-http-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pekkohttp/v1_0/client/HttpExtClientInstrumentation.java
   def singleRequest(
       request: HttpRequest,
       connectionContext: HttpsConnectionContext = defaultClientHttpsContext,
@@ -951,7 +958,14 @@ object Http extends ExtensionId[HttpExt] with 
ExtensionIdProvider {
     /**
      * Handles the connection with the given flow, which is materialized 
exactly once
      * and the respective materialization result returned.
+     *
+     * <p>
+     *   Opentelemetry Java Instrumentation relies on this method so avoid 
changing it. It is the server
+     *   entry point the agent instruments for bindings created from a 
connection source.
+     *   See https://github.com/apache/pekko-http/issues/1241
+     * </p>
      */
+    // see 
https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/6f9ca5672ce84edbbe36ce0e14386c31d68f479f/instrumentation/pekko/pekko-http-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pekkohttp/v1_0/server/PekkoHttpServerSourceInstrumentation.java
     def handleWith[Mat](handler: Flow[HttpRequest, HttpResponse, 
Mat])(implicit fm: Materializer): Mat =
       flow.joinMat(handler)(Keep.right).run()
 
diff --git 
a/http-core/src/test/scala/org/apache/pekko/http/impl/InstrumentationPointsSpec.scala
 
b/http-core/src/test/scala/org/apache/pekko/http/impl/InstrumentationPointsSpec.scala
new file mode 100644
index 000000000..4e79325f0
--- /dev/null
+++ 
b/http-core/src/test/scala/org/apache/pekko/http/impl/InstrumentationPointsSpec.scala
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pekko.http.impl
+
+import java.lang.reflect.Method
+
+import org.scalatest.matchers.should.Matchers
+import org.scalatest.wordspec.AnyWordSpec
+
+/**
+ * Guards the pekko-http internals that the OpenTelemetry Java agent attaches 
bytecode advice to.
+ *
+ * The agent matches these by name and signature. When a match stops applying 
the instrumentation is
+ * silently disabled, so this spec fails loudly instead. Keep it in sync with
+ * https://github.com/apache/pekko-http/issues/1241
+ */
+class InstrumentationPointsSpec extends AnyWordSpec with Matchers {
+
+  private def methods(className: String): Array[Method] =
+    Class.forName(className, false, getClass.getClassLoader).getMethods
+
+  private def declaredMethods(className: String): Array[Method] =
+    Class.forName(className, false, getClass.getClassLoader).getDeclaredMethods
+
+  "The methods instrumented by the OpenTelemetry Java agent" should {
+
+    "include HttpExt.singleRequest taking an HttpRequest" in {
+      methods("org.apache.pekko.http.scaladsl.HttpExt").filter(_.getName == 
"singleRequest").exists(
+        _.getParameterTypes.headOption.exists(
+          _.getName == "org.apache.pekko.http.scaladsl.model.HttpRequest")) 
shouldBe true
+    }
+
+    "include Http.IncomingConnection.handleWith taking a Flow" in {
+      
methods("org.apache.pekko.http.scaladsl.Http$IncomingConnection").filter(_.getName
 == "handleWith").exists(
+        _.getParameterTypes.headOption.exists(
+          _.getName == "org.apache.pekko.stream.scaladsl.Flow")) shouldBe true
+    }
+
+    "include HttpServerBluePrint.requestPreparation returning a BidiFlow" in {
+      
methods("org.apache.pekko.http.impl.engine.server.HttpServerBluePrint$").filter(
+        _.getName == "requestPreparation").exists(
+        _.getReturnType.getName == 
"org.apache.pekko.stream.scaladsl.BidiFlow") shouldBe true
+    }
+
+    // the agent matches the mangled name, which only exists because a closure 
in the method body captures it
+    "include the mangled accessor for PoolMasterActor.startPoolInterface" in {
+      
declaredMethods("org.apache.pekko.http.impl.engine.client.PoolMasterActor").exists(
+        _.getName == 
"org$apache$pekko$http$impl$engine$client$PoolMasterActor$$startPoolInterface") 
shouldBe true
+    }
+
+    "include Http2Ext.bindAndHandleAsync and Http2Ext.system" in {
+      val http2Ext = 
methods("org.apache.pekko.http.impl.engine.http2.Http2Ext")
+      http2Ext.exists(_.getName == "bindAndHandleAsync") shouldBe true
+      http2Ext.filter(_.getName == "system").exists(
+        _.getReturnType.getName == "org.apache.pekko.actor.ActorSystem") 
shouldBe true
+    }
+
+    "include Http2.streamId" in {
+      
methods("org.apache.pekko.http.impl.engine.http2.Http2$").filter(_.getName == 
"streamId").exists(
+        _.getReturnType.getName == 
"org.apache.pekko.http.scaladsl.model.AttributeKey") shouldBe true
+    }
+  }
+}
diff --git 
a/http-tests/src/test/scala/org/apache/pekko/http/scaladsl/server/RouteInstrumentationPointsSpec.scala
 
b/http-tests/src/test/scala/org/apache/pekko/http/scaladsl/server/RouteInstrumentationPointsSpec.scala
new file mode 100644
index 000000000..48fc61afc
--- /dev/null
+++ 
b/http-tests/src/test/scala/org/apache/pekko/http/scaladsl/server/RouteInstrumentationPointsSpec.scala
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pekko.http.scaladsl.server
+
+import org.scalatest.matchers.should.Matchers
+import org.scalatest.wordspec.AnyWordSpec
+
+/**
+ * Guards the route building blocks that the OpenTelemetry Java agent attaches 
bytecode advice to in order
+ * to derive the `http.route` attribute. See 
https://github.com/apache/pekko-http/issues/1241
+ */
+class RouteInstrumentationPointsSpec extends AnyWordSpec with Matchers {
+
+  "The route methods instrumented by the OpenTelemetry Java agent" should {
+
+    "include the single argument Directive.tapply" in {
+      classOf[Directive[Unit]].getMethods.filter(_.getName == "tapply").exists(
+        _.getParameterTypes.length == 1) shouldBe true
+    }
+
+    "include PathMatcher.apply taking a Uri.Path on concrete matchers" in {
+      PathMatchers.Segment.getClass.getMethods.filter(_.getName == 
"apply").exists(
+        _.getParameterTypes.headOption.exists(
+          _.getName == "org.apache.pekko.http.scaladsl.model.Uri$Path")) 
shouldBe true
+    }
+
+    "include PathMatcher.apply taking a Uri.Path and returning a PathMatcher" 
in {
+      PathMatcher.getClass.getMethods.filter(_.getName == "apply").exists { m 
=>
+        m.getParameterTypes.headOption.exists(_.getName == 
"org.apache.pekko.http.scaladsl.model.Uri$Path") &&
+        m.getReturnType.getName == 
"org.apache.pekko.http.scaladsl.server.PathMatcher"
+      } shouldBe true
+    }
+  }
+}
diff --git 
a/http/src/main/scala/org/apache/pekko/http/scaladsl/server/Directive.scala 
b/http/src/main/scala/org/apache/pekko/http/scaladsl/server/Directive.scala
index ea5c50121..f20a7a5ab 100644
--- a/http/src/main/scala/org/apache/pekko/http/scaladsl/server/Directive.scala
+++ b/http/src/main/scala/org/apache/pekko/http/scaladsl/server/Directive.scala
@@ -26,6 +26,13 @@ import pekko.http.scaladsl.util.FastFuture._
 
 /**
  * A directive that provides a tuple of values of type `L` to create an inner 
route.
+ *
+ * <p>
+ *   Opentelemetry Java Instrumentation relies on the single-argument `tapply` 
method of this class (and of
+ *   every subclass) so avoid changing it. The agent uses it to derive the 
`http.route` attribute.
+ *   See https://github.com/apache/pekko-http/issues/1241 and
+ *   
https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/6f9ca5672ce84edbbe36ce0e14386c31d68f479f/instrumentation/pekko/pekko-http-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pekkohttp/v1_0/server/route/DirectiveInstrumentation.java
+ * </p>
  */
 //#basic
 abstract class Directive[L](implicit val ev: Tuple[L]) {
diff --git 
a/http/src/main/scala/org/apache/pekko/http/scaladsl/server/PathMatcher.scala 
b/http/src/main/scala/org/apache/pekko/http/scaladsl/server/PathMatcher.scala
index 58dc010e5..6618368cc 100644
--- 
a/http/src/main/scala/org/apache/pekko/http/scaladsl/server/PathMatcher.scala
+++ 
b/http/src/main/scala/org/apache/pekko/http/scaladsl/server/PathMatcher.scala
@@ -28,6 +28,13 @@ import pekko.http.scaladsl.server.util.TupleOps._
 /**
  * A PathMatcher tries to match a prefix of a given string and returns either 
a PathMatcher.Matched instance
  * if matched, otherwise PathMatcher.Unmatched.
+ *
+ * <p>
+ *   Opentelemetry Java Instrumentation relies on the `apply(Path)` method of 
this class (and of every
+ *   subclass) so avoid changing it. The agent uses it to derive the 
`http.route` attribute.
+ *   See https://github.com/apache/pekko-http/issues/1241 and
+ *   
https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/6f9ca5672ce84edbbe36ce0e14386c31d68f479f/instrumentation/pekko/pekko-http-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pekkohttp/v1_0/server/route/PathMatcherStaticInstrumentation.java
+ * </p>
  */
 abstract class PathMatcher[L](implicit val ev: Tuple[L]) extends (Path => 
PathMatcher.Matching[L]) { self =>
   import PathMatcher._
@@ -164,7 +171,15 @@ object PathMatcher extends ImplicitPathMatcherConstruction 
{
   /**
    * Creates a PathMatcher that matches and consumes the given path prefix and 
extracts the given list of extractions.
    * If the given prefix is empty the returned PathMatcher matches always and 
consumes nothing.
+   *
+   * <p>
+   *   Opentelemetry Java Instrumentation relies on this method so avoid 
changing it. The agent matches on the
+   *   method name, on the [[Path]] first parameter and on the [[PathMatcher]] 
return type, and uses it to
+   *   derive the `http.route` attribute.
+   *   See https://github.com/apache/pekko-http/issues/1241
+   * </p>
    */
+  // see 
https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/6f9ca5672ce84edbbe36ce0e14386c31d68f479f/instrumentation/pekko/pekko-http-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pekkohttp/v1_0/server/route/PathMatcherInstrumentation.java
   def apply[L](prefix: Path, extractions: L)(implicit ev: Tuple[L]): 
PathMatcher[L] =
     if (prefix.isEmpty) provide(extractions)
     else new PathMatcher[L] {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to