diff --git a/build.sbt b/build.sbt index c7377b1..7cc8229 100644 --- a/build.sbt +++ b/build.sbt @@ -1,7 +1,7 @@ ThisBuild / tlBaseVersion := "0.6" val http4sVersion = "0.23.33" -val natchezVersion = "0.3.8" +val natchezVersion = "0.3.8-134-e5a1826-SNAPSHOT" val scala212Version = "2.12.21" val scala213Version = "2.13.18" val scala3Version = "3.3.7" @@ -78,6 +78,10 @@ lazy val http4s = crossProject(JSPlatform, JVMPlatform, NativePlatform) "org.http4s" %%% "http4s-client" % http4sVersion, "org.http4s" %%% "http4s-server" % http4sVersion, "org.tpolecat" %%% "natchez-testkit" % natchezVersion % Test, + "com.comcast" %%% "ip4s-test-kit" % "3.7.0" % Test, + "io.opentelemetry" % "opentelemetry-sdk-common" % "1.57.0" % Test, + "io.opentelemetry.semconv" % "opentelemetry-semconv" % "1.37.0" % Test, + "io.opentelemetry.semconv" % "opentelemetry-semconv-incubating" % "1.37.0-alpha" % Test, ) ) .dependsOn(core) diff --git a/modules/http4s/src/main/scala/natchez/http4s/NatchezMiddleware.scala b/modules/http4s/src/main/scala/natchez/http4s/NatchezMiddleware.scala index 39a0649..c15141d 100644 --- a/modules/http4s/src/main/scala/natchez/http4s/NatchezMiddleware.scala +++ b/modules/http4s/src/main/scala/natchez/http4s/NatchezMiddleware.scala @@ -4,23 +4,24 @@ package natchez.http4s -import cats.data.{Kleisli, OptionT} -import cats.syntax.all._ -import cats.effect.{MonadCancel, MonadCancelThrow, Outcome, Resource} -import cats.effect.syntax.all._ -import Outcome._ -import natchez.{Span, Tags, Trace, TraceValue} +import cats.Applicative +import cats.data.{Ior, Kleisli, OptionT} +import cats.effect.Outcome.* +import cats.effect.syntax.all.* +import cats.effect.{MonadCancel, MonadCancelThrow, Resource} +import cats.syntax.all.* import natchez.Span.Options.Defaults import natchez.Span.SpanKind +import natchez.http4s.implicits.* +import natchez.* import org.http4s.client.Client -import org.http4s.HttpRoutes -import org.http4s.{Request, Response} +import org.http4s.headers.Host +import org.http4s.{HttpRoutes, Request, Response, Uri, headers} -import java.io.ByteArrayOutputStream -import java.io.PrintStream +import java.io.{ByteArrayOutputStream, PrintStream} object NatchezMiddleware { - import syntax.kernel._ + import syntax.kernel.* @deprecated("Use NatchezMiddleware.server(routes)", "0.0.3") def apply[F[_]: Trace](routes: HttpRoutes[F])( @@ -42,21 +43,35 @@ object NatchezMiddleware { * - "error.stacktrace" -> Exception stack trace as a multi-line string * - "cancelled" -> true // only present in case of cancellation */ - def server[F[_]: Trace](routes: HttpRoutes[F])( - implicit ev: MonadCancel[F, Throwable] - ): HttpRoutes[F] = + def server[F[_]: Trace : MonadCancelThrow](routes: HttpRoutes[F]): HttpRoutes[F] = + server(routes, useOpenTelemetrySemanticConventions = false) + + def server[F[_]: Trace : MonadCancelThrow](routes: HttpRoutes[F], + useOpenTelemetrySemanticConventions: Boolean, + ): HttpRoutes[F] = Kleisli { req => val addRequestFields: F[Unit] = - Trace[F].put( - Tags.http.method(req.method.name), - Tags.http.url(req.uri.renderString), - ) + if (useOpenTelemetrySemanticConventions) + Trace[F].put( + "http.request.method" -> req.method, + "url.full" -> req.uri, + ) + else + Trace[F].put( + Tags.http.method(req.method.name), + Tags.http.url(req.uri.renderString), + ) def addResponseFields(res: Response[F]): F[Unit] = - Trace[F].put( - Tags.http.status_code(res.status.code.toString) - ) + if (useOpenTelemetrySemanticConventions) + Trace[F].put( + "http.response.status_code" -> res.status.code, + ) + else + Trace[F].put( + Tags.http.status_code(res.status.code.toString) + ) def addErrorFields(e: Throwable): F[Unit] = Trace[F].put( @@ -95,7 +110,12 @@ object NatchezMiddleware { * */ def client[F[_] : Trace : MonadCancelThrow](client: Client[F]): Client[F] = - NatchezMiddleware.client(client, _ => Seq.empty[(String, TraceValue)].pure[F]) + NatchezMiddleware.client(client, useOpenTelemetrySemanticConventions = false) + + def client[F[_] : Trace : MonadCancelThrow](client: Client[F], + useOpenTelemetrySemanticConventions: Boolean, + ): Client[F] = + NatchezMiddleware.client(client, _ => Seq.empty[(String, TraceValue)].pure[F], useOpenTelemetrySemanticConventions) /** * A middleware that adds the current span's kernel to outgoing requests, performs requests in @@ -112,7 +132,13 @@ object NatchezMiddleware { */ def clientWithAttributes[F[_] : Trace : MonadCancelThrow](client: Client[F]) (additionalAttributes: (String, TraceValue)*): Client[F] = - NatchezMiddleware.client(client, (_: Request[F]) => additionalAttributes.pure[F]) + NatchezMiddleware.clientWithAttributes(client, useOpenTelemetrySemanticConventions = false)(additionalAttributes *) + + def clientWithAttributes[F[_] : Trace : MonadCancelThrow](client: Client[F], + useOpenTelemetrySemanticConventions: Boolean, + ) + (additionalAttributes: (String, TraceValue)*): Client[F] = + NatchezMiddleware.client(client, (_: Request[F]) => additionalAttributes.pure[F], useOpenTelemetrySemanticConventions) /** * A middleware that adds the current span's kernel to outgoing requests, performs requests in @@ -130,7 +156,14 @@ object NatchezMiddleware { def clientWithAttributes[F[_] : Trace : MonadCancelThrow](client: Client[F], spanOptions: Span.Options) (additionalAttributes: (String, TraceValue)*): Client[F] = - NatchezMiddleware.client(client, spanOptions, (_: Request[F]) => additionalAttributes.pure[F]) + NatchezMiddleware.clientWithAttributes(client, spanOptions, useOpenTelemetrySemanticConventions = false)(additionalAttributes *) + + def clientWithAttributes[F[_] : Trace : MonadCancelThrow](client: Client[F], + spanOptions: Span.Options, + useOpenTelemetrySemanticConventions: Boolean, + ) + (additionalAttributes: (String, TraceValue)*): Client[F] = + NatchezMiddleware.client(client, spanOptions, (_: Request[F]) => additionalAttributes.pure[F], useOpenTelemetrySemanticConventions) /** * A middleware that adds the current span's kernel to outgoing requests, performs requests in @@ -148,7 +181,13 @@ object NatchezMiddleware { def client[F[_] : Trace : MonadCancelThrow](client: Client[F], additionalAttributesF: Request[F] => F[Seq[(String, TraceValue)]], ): Client[F] = - NatchezMiddleware.client(client, Defaults.withSpanKind(SpanKind.Client), additionalAttributesF) + NatchezMiddleware.client(client, additionalAttributesF, useOpenTelemetrySemanticConventions = false) + + def client[F[_] : Trace : MonadCancelThrow](client: Client[F], + additionalAttributesF: Request[F] => F[Seq[(String, TraceValue)]], + useOpenTelemetrySemanticConventions: Boolean, + ): Client[F] = + NatchezMiddleware.client(client, Defaults.withSpanKind(SpanKind.Client), additionalAttributesF, useOpenTelemetrySemanticConventions) /** * A middleware that adds the current span's kernel to outgoing requests, performs requests in @@ -167,23 +206,74 @@ object NatchezMiddleware { spanOptions: Span.Options, additionalAttributesF: Request[F] => F[Seq[(String, TraceValue)]], ): Client[F] = + NatchezMiddleware.client(client, spanOptions, additionalAttributesF, useOpenTelemetrySemanticConventions = false) + + def client[F[_] : Trace : MonadCancelThrow](client: Client[F], + spanOptions: Span.Options, + additionalAttributesF: Request[F] => F[Seq[(String, TraceValue)]], + useOpenTelemetrySemanticConventions: Boolean, + ): Client[F] = Client { req => Resource.applyFull {poll => - Trace[F].span("http4s-client-request", spanOptions) { + Trace[F].span(spanName(req, useOpenTelemetrySemanticConventions), spanOptions) { for { knl <- Trace[F].kernel _ <- Trace[F].put( - "client.http.uri" -> req.uri.toString(), - "client.http.method" -> req.method.toString + httpUrlKey(useOpenTelemetrySemanticConventions) -> req.uri, + httpMethodKey(useOpenTelemetrySemanticConventions) -> req.method, ) + _ <- addAttributeFromUriAuthorityOrHeader("server.address")(fromUri = _.host.some, fromHeader = _.host.some)(req) + _ <- addAttributeFromUriAuthorityOrHeader("server.port")(fromUri = _.port, fromHeader = _.port)(req) + _ <- req.uri.scheme.asAttribute[F]("url.scheme") additionalAttributes <- additionalAttributesF(req) - _ <- Trace[F].put(additionalAttributes: _*) + _ <- Trace[F].put(additionalAttributes *) reqʹ = req.withHeaders(knl.toHttp4sHeaders ++ req.headers) // prioritize request headers over kernel ones rsrc <- poll(client.run(reqʹ).allocatedCase) - _ <- Trace[F].put("client.http.status_code" -> rsrc._1.status.code.toString()) + _ <- Trace[F].put(httpStatusCodeKey(useOpenTelemetrySemanticConventions) -> rsrc._1.status.code) } yield rsrc } } } + private def addAttributeFromUriAuthorityOrHeader[F[_] : Applicative : Trace, A: TraceableValue, B: TraceableValue](key: String) + (fromUri: Uri.Authority => Option[A], + fromHeader: headers.Host => Option[B]) + (req: Request[F]): F[Unit] = + Ior.fromOptions(req.headers.get[Host].flatMap(fromHeader), req.uri.authority.flatMap(fromUri)) + .map(_.toEither) + .asAttribute(key) + + private implicit class OptionTraceOps[A](val maybeA: Option[A]) extends AnyVal { + def asAttribute[F[_] : Applicative : Trace](key: String)(implicit T: TraceableValue[A]): F[Unit] = + maybeA.traverse_(a => Trace[F].put(key -> a)) + } + + /** + * See https://opentelemetry.io/docs/specs/semconv/http/http-spans/#http-client-span and + * https://opentelemetry.io/docs/specs/semconv/registry/attributes/url/#url-full + */ + private def httpUrlKey(useOpenTelemetrySemanticConventions: Boolean): String = + if (useOpenTelemetrySemanticConventions) "url.full" else "client.http.uri" + + /** + * See https://opentelemetry.io/docs/specs/semconv/http/http-spans/#http-client-span and + * https://opentelemetry.io/docs/specs/semconv/registry/attributes/http/#http-request-method + */ + private def httpMethodKey(useOpenTelemetrySemanticConventions: Boolean): String = + if (useOpenTelemetrySemanticConventions) "http.request.method" else "client.http.method" + + /** + * See https://opentelemetry.io/docs/specs/semconv/http/http-spans/#http-client-span and + * https://opentelemetry.io/docs/specs/semconv/registry/attributes/http/#http-response-status-code + */ + private def httpStatusCodeKey(useOpenTelemetrySemanticConventions: Boolean): String = + if (useOpenTelemetrySemanticConventions) "http.response.status_code" else "client.http.status_code" + + /** + * See https://opentelemetry.io/docs/specs/semconv/http/http-spans/#name + */ + private def spanName[F[_]](request: Request[F], + useOpenTelemetrySemanticConventions: Boolean): String = + if (useOpenTelemetrySemanticConventions) request.method.name else "http4s-client-request" + } diff --git a/modules/http4s/src/main/scala/natchez/http4s/package.scala b/modules/http4s/src/main/scala/natchez/http4s/package.scala index d7f4d22..09a20ed 100644 --- a/modules/http4s/src/main/scala/natchez/http4s/package.scala +++ b/modules/http4s/src/main/scala/natchez/http4s/package.scala @@ -4,10 +4,17 @@ package natchez +import org.http4s.{Method, Uri, headers} + package object http4s { object implicits extends syntax.ToEntryPointOps with syntax.ToKernelOps - with syntax.ToKernelCompanionOps + with syntax.ToKernelCompanionOps { + implicit lazy val traceableValueUri: TraceableValue[Uri] = TraceableValue[String].contramap(_.renderString) + implicit lazy val traceableValueHost: TraceableValue[Uri.Host] = TraceableValue[String].contramap(_.renderString) + implicit lazy val traceableValueMethod: TraceableValue[Method] = TraceableValue[String].contramap(_.renderString) + implicit lazy val traceableValueScheme: TraceableValue[Uri.Scheme] = TraceableValue[String].contramap(_.value) + implicit lazy val traceableValueHostHeader: TraceableValue[headers.Host] = TraceableValue[String].contramap(_.host) + } } - diff --git a/modules/http4s/src/test/scala/natchez/http4s/InMemory.scala b/modules/http4s/src/test/scala/natchez/http4s/InMemory.scala index 38b32bd..0730bf5 100644 --- a/modules/http4s/src/test/scala/natchez/http4s/InMemory.scala +++ b/modules/http4s/src/test/scala/natchez/http4s/InMemory.scala @@ -7,7 +7,11 @@ package http4s import cats.data.Kleisli import cats.effect.{IO, MonadCancelThrow} +import com.comcast.ip4s.Arbitraries.* +import com.comcast.ip4s.{Hostname, Port} import munit.CatsEffectSuite +import org.http4s.{Uri, headers} +import org.scalacheck.{Arbitrary, Gen} import org.typelevel.ci.* trait InMemorySuite @@ -63,4 +67,21 @@ trait InMemorySuite val CustomHeaderName = ci"X-Custom-Header" val CorrelationIdName = ci"X-Correlation-Id" + implicit val arbUriAuthority: Arbitrary[Uri.Authority] = Arbitrary { + for { + host <- Arbitrary.arbitrary[Hostname] + port <- Arbitrary.arbitrary[Option[Port]] + } yield Uri.Authority(host = Uri.Host.fromIp4sHost(host), port = port.map(_.value)) + } + + implicit val arbHostHeader: Arbitrary[headers.Host] = Arbitrary { + for { + host <- Arbitrary.arbitrary[Hostname] + port <- Arbitrary.arbitrary[Option[Port]] + } yield headers.Host(host.toString, port.map(_.value)) + } + + implicit val arbUriScheme: Arbitrary[Uri.Scheme] = Arbitrary { + Gen.oneOf(Uri.Scheme.http, Uri.Scheme.https) + } } diff --git a/modules/http4s/src/test/scala/natchez/http4s/NatchezMiddlewareSuite.scala b/modules/http4s/src/test/scala/natchez/http4s/NatchezMiddlewareSuite.scala index 7d9c872..8eef9bb 100644 --- a/modules/http4s/src/test/scala/natchez/http4s/NatchezMiddlewareSuite.scala +++ b/modules/http4s/src/test/scala/natchez/http4s/NatchezMiddlewareSuite.scala @@ -7,10 +7,12 @@ package natchez.http4s import cats.Monad import cats.data.{Chain, Kleisli} import cats.effect.{IO, MonadCancelThrow, Resource} +import cats.syntax.all.* +import io.opentelemetry.semconv.{HttpAttributes, ServerAttributes, UrlAttributes} import munit.ScalaCheckEffectSuite import natchez.* import natchez.Span.SpanKind -import natchez.TraceValue.StringValue +import natchez.TraceValue.{NumberValue, StringValue} import natchez.http4s.syntax.entrypoint.* import org.http4s.* import org.http4s.client.Client @@ -56,7 +58,7 @@ class NatchezMiddlewareSuite for { ref <- IO.ref(Chain.empty[(Lineage, NatchezCommand)]) ep <- IO.pure(new InMemory.EntryPoint(ref)) - routes <- IO.pure(ep.liftT(httpRoutes[Kleisli[IO, natchez.Span[IO], *]](None))) + routes <- IO.pure(ep.liftT(httpRoutes[Kleisli[IO, natchez.Span[IO], *]](None, useOpenTelemetrySemanticConventions = false))) response <- routes.orNotFound.run(request) } yield { assertEquals(response.status.code, 200) @@ -66,61 +68,109 @@ class NatchezMiddlewareSuite test("generate proper tracing history") { PropF.forAllF { (userSpecifiedTags: List[(String, TraceValue)], - maybeSpanOptions: Option[Span.Options]) => - val request = Request[IO]( + maybeSpanOptions: Option[Span.Options], + maybeUrlScheme: Option[Uri.Scheme], + maybeUriAuthority: Option[Uri.Authority], + maybeHostHeader: Option[headers.Host], + useOpenTelemetrySemanticConventions: Boolean, + ) => + val request = maybeHostHeader.foldl(Request[IO]( method = Method.GET, - uri = uri"/hello/some-name", + uri = uri"/hello/some-name".copy(authority = maybeUriAuthority, scheme = maybeUrlScheme), headers = Headers( Header.Raw(CustomHeaderName, "external"), - Header.Raw(CorrelationIdName, "id-123") + Header.Raw(CorrelationIdName, "id-123"), ) - ) + ))(_.putHeaders(_)) val expectedHistory = { val requestKernel = Kernel( - Map(CustomHeaderName -> "external", CorrelationIdName -> "id-123") + Map(CustomHeaderName -> "external", CorrelationIdName -> "id-123") ++ maybeHostHeader.map { header => + headers.Host.headerInstance.name -> headers.Host.headerInstance.value(header) + }.toMap ) - val clientRequestTags = List( - "client.http.uri" -> StringValue("/some-name"), - "client.http.method" -> StringValue("GET") - ) - - val clientResponseTags = List( - "client.http.status_code" -> StringValue("200") - ) - - val requestTags = List( - "http.method" -> StringValue("GET"), - "http.url" -> StringValue("/hello/some-name") - ) - - val responseTags = List( - "http.status_code" -> StringValue("200") - ) + val requestTagsFromClientMiddleware = + if (useOpenTelemetrySemanticConventions) List( + UrlAttributes.URL_FULL.getKey -> StringValue(request.uri.withPath(Root / "some-name").renderString), + HttpAttributes.HTTP_REQUEST_METHOD.getKey -> StringValue("GET"), + ) + else List( + "client.http.uri" -> StringValue(request.uri.withPath(Root / "some-name").renderString), + "client.http.method" -> StringValue("GET") + ) + + val responseTagsFromClientMiddleware = + if (useOpenTelemetrySemanticConventions) List( + HttpAttributes.HTTP_RESPONSE_STATUS_CODE.getKey -> NumberValue(200) + ) + else List( + "client.http.status_code" -> NumberValue(200) + ) + + val serverAddressTag: Option[(String, TraceValue)] = + maybeUriAuthority.map(_.host.renderString) + .orElse(maybeHostHeader.map(_.host)) + .map(host => ServerAttributes.SERVER_ADDRESS.getKey -> StringValue(host)) + + val serverPortTag: Option[(String, TraceValue)] = + maybeUriAuthority.flatMap(_.port) + .orElse(maybeHostHeader.flatMap(_.port)) + .map(port => ServerAttributes.SERVER_PORT.getKey -> NumberValue(port)) + + val urlSchemeTag: Option[(String, TraceValue)] = + maybeUrlScheme.map(scheme => UrlAttributes.URL_SCHEME.getKey -> StringValue(scheme.value)) + + val requestTagsFromServerMiddleware = + if (useOpenTelemetrySemanticConventions) List( + HttpAttributes.HTTP_REQUEST_METHOD.getKey -> StringValue("GET"), + UrlAttributes.URL_FULL.getKey -> StringValue(request.uri.renderString), + ) + else List( + "http.method" -> StringValue("GET"), + "http.url" -> StringValue(request.uri.renderString) + ) + + val responseTagsFromServerMiddleware = + if (useOpenTelemetrySemanticConventions) List( + HttpAttributes.HTTP_RESPONSE_STATUS_CODE.getKey -> NumberValue(200) + ) + else List( + "http.status_code" -> StringValue("200") + ) val spanOptions = maybeSpanOptions.getOrElse(Span.Options.Defaults.withSpanKind(SpanKind.Client)) val kernel = maybeSpanOptions.flatMap(_.parentKernel) + val clientSpanName = if (useOpenTelemetrySemanticConventions) request.method.name else "http4s-client-request" + List( - (Lineage.Root, NatchezCommand.CreateRootSpan("/hello/some-name", requestKernel, Span.Options.Defaults)), - (Lineage.Root("/hello/some-name"), NatchezCommand.CreateSpan("call-proxy", None, Span.Options.Defaults)), - (Lineage.Root("/hello/some-name") / "call-proxy", NatchezCommand.CreateSpan("http4s-client-request", kernel, spanOptions)), - (Lineage.Root("/hello/some-name") / "call-proxy" / "http4s-client-request", NatchezCommand.AskKernel(requestKernel)), - (Lineage.Root("/hello/some-name") / "call-proxy" / "http4s-client-request", NatchezCommand.Put(clientRequestTags)), - (Lineage.Root("/hello/some-name") / "call-proxy" / "http4s-client-request", NatchezCommand.Put(userSpecifiedTags)), - (Lineage.Root("/hello/some-name") / "call-proxy" / "http4s-client-request", NatchezCommand.Put(clientResponseTags)), - (Lineage.Root("/hello/some-name") / "call-proxy", NatchezCommand.ReleaseSpan("http4s-client-request")), - (Lineage.Root("/hello/some-name"), NatchezCommand.ReleaseSpan("call-proxy")), - (Lineage.Root("/hello/some-name"), NatchezCommand.Put(requestTags)), - (Lineage.Root("/hello/some-name"), NatchezCommand.Put(responseTags)), - (Lineage.Root, NatchezCommand.ReleaseRootSpan("/hello/some-name")) + List( + (Lineage.Root, NatchezCommand.CreateRootSpan("/hello/some-name", requestKernel, Span.Options.Defaults)), + (Lineage.Root("/hello/some-name"), NatchezCommand.CreateSpan("call-proxy", None, Span.Options.Defaults)), + (Lineage.Root("/hello/some-name") / "call-proxy", NatchezCommand.CreateSpan(clientSpanName, kernel, spanOptions)), + (Lineage.Root("/hello/some-name") / "call-proxy" / clientSpanName, NatchezCommand.AskKernel(requestKernel)), + (Lineage.Root("/hello/some-name") / "call-proxy" / clientSpanName, NatchezCommand.Put(requestTagsFromClientMiddleware)), + ), + List(serverAddressTag, serverPortTag, urlSchemeTag).flatMap( + _.map(tag => (Lineage.Root("/hello/some-name") / "call-proxy" / clientSpanName, NatchezCommand.Put(List(tag)))) + ), + List( + (Lineage.Root("/hello/some-name") / "call-proxy" / clientSpanName, NatchezCommand.Put(userSpecifiedTags)), + (Lineage.Root("/hello/some-name") / "call-proxy" / clientSpanName, NatchezCommand.Put(responseTagsFromClientMiddleware)), + (Lineage.Root("/hello/some-name") / "call-proxy", NatchezCommand.ReleaseSpan(clientSpanName)), + (Lineage.Root("/hello/some-name"), NatchezCommand.ReleaseSpan("call-proxy")), + (Lineage.Root("/hello/some-name"), NatchezCommand.Put(requestTagsFromServerMiddleware)), + (Lineage.Root("/hello/some-name"), NatchezCommand.Put(responseTagsFromServerMiddleware)), + (Lineage.Root, NatchezCommand.ReleaseRootSpan("/hello/some-name")), + ) ) + .flatten } for { ep <- InMemory.EntryPoint.create[IO] - routes <- IO.pure(ep.liftT(httpRoutes[Kleisli[IO, natchez.Span[IO], *]](maybeSpanOptions, userSpecifiedTags *))) + routes <- IO.pure(ep.liftT(httpRoutes[Kleisli[IO, natchez.Span[IO], *]](maybeSpanOptions, useOpenTelemetrySemanticConventions, userSpecifiedTags *))) _ <- routes.orNotFound.run(request) history <- ep.ref.get } yield assertEquals(history.toList, expectedHistory) @@ -128,15 +178,16 @@ class NatchezMiddlewareSuite } private def httpRoutes[F[_]: MonadCancelThrow: Trace](maybeSpanOptions: Option[Span.Options], + useOpenTelemetrySemanticConventions: Boolean, additionalAttributes: (String, TraceValue)*): HttpRoutes[F] = { val client = maybeSpanOptions match { case Some(spanOptions) => - NatchezMiddleware.clientWithAttributes(echoHeadersClient[F], spanOptions)(additionalAttributes *) + NatchezMiddleware.clientWithAttributes(echoHeadersClient[F], spanOptions, useOpenTelemetrySemanticConventions)(additionalAttributes *) case None => - NatchezMiddleware.clientWithAttributes(echoHeadersClient[F])(additionalAttributes *) + NatchezMiddleware.clientWithAttributes(echoHeadersClient[F], useOpenTelemetrySemanticConventions)(additionalAttributes *) } - val server = NatchezMiddleware.server(proxyRoutes(client)) + val server = NatchezMiddleware.server(proxyRoutes(client), useOpenTelemetrySemanticConventions) server } @@ -147,12 +198,12 @@ class NatchezMiddlewareSuite private def proxyRoutes[F[_]: Monad: Trace](client: Client[F]): HttpRoutes[F] = { HttpRoutes.of[F] { - case GET -> Root / "hello" / name => - val request = Request[F]( + case incomingRequest @ GET -> Root / "hello" / name => + val request = incomingRequest.headers.get[headers.Host].foldl(Request[F]( method = Method.GET, - uri = Uri(path = Root / name), + uri = incomingRequest.uri.withPath(Root / name), headers = Headers(Header.Raw(CustomHeaderName, "internal")) - ) + ))(_.putHeaders(_)) Trace[F].span("call-proxy") { client.toHttpApp.run(request)