diff --git a/build.sbt b/build.sbt index b26d9618..9a39efaa 100644 --- a/build.sbt +++ b/build.sbt @@ -100,7 +100,9 @@ lazy val core = crossProject(JSPlatform, JVMPlatform, NativePlatform) "org.typelevel" %%% "cats-core" % catsVersion, "org.typelevel" %%% "cats-effect-kernel" % catsEffectVersion, "org.typelevel" %%% "cats-effect" % catsEffectVersion, - "co.fs2" %%% "fs2-io" % fs2Version + "co.fs2" %%% "fs2-io" % fs2Version, + "org.typelevel" %%% "case-insensitive" % "1.3.0", + "org.scala-lang.modules" %%% "scala-collection-compat" % collectionCompatVersion ) ) .nativeSettings(commonNativeSettings) @@ -142,8 +144,7 @@ lazy val opencensus = project name := "natchez-opencensus", description := "Opencensus support for Natchez.", libraryDependencies ++= Seq( - "io.opencensus" % "opencensus-exporter-trace-ocagent" % "0.31.1", - "org.scala-lang.modules" %% "scala-collection-compat" % collectionCompatVersion + "io.opencensus" % "opencensus-exporter-trace-ocagent" % "0.31.1" ) ) @@ -156,8 +157,7 @@ lazy val lightstep = project name := "natchez-lightstep", description := "Lightstep support for Natchez.", libraryDependencies ++= Seq( - "com.lightstep.tracer" % "lightstep-tracer-jre" % "0.30.5", - "org.scala-lang.modules" %% "scala-collection-compat" % collectionCompatVersion + "com.lightstep.tracer" % "lightstep-tracer-jre" % "0.30.5" ) ) @@ -215,7 +215,6 @@ lazy val opentelemetry = project description := "Base OpenTelemetry Utilities for Natchez", tlVersionIntroduced := List("2.12", "2.13", "3").map(_ -> "0.1.7").toMap, libraryDependencies ++= Seq( - "org.scala-lang.modules" %% "scala-collection-compat" % collectionCompatVersion, "io.opentelemetry" % "opentelemetry-sdk" % "1.20.1" ) ) @@ -229,7 +228,6 @@ lazy val datadog = project name := "natchez-datadog", description := "Datadog bindings for Natchez.", libraryDependencies ++= Seq( - "org.scala-lang.modules" %% "scala-collection-compat" % collectionCompatVersion, "com.datadoghq" % "dd-trace-ot" % "1.0.1", "com.datadoghq" % "dd-trace-api" % "1.0.1" ) @@ -261,7 +259,6 @@ lazy val newrelic = project description := "Newrelic bindings for Natchez.", libraryDependencies ++= Seq( "io.circe" %% "circe-core" % "0.14.3", - "org.scala-lang.modules" %% "scala-collection-compat" % collectionCompatVersion, "com.newrelic.telemetry" % "telemetry" % "0.10.0", "com.newrelic.telemetry" % "telemetry-core" % "0.15.0", "com.newrelic.telemetry" % "telemetry-http-okhttp" % "0.15.0" diff --git a/modules/core/shared/src/main/scala/Kernel.scala b/modules/core/shared/src/main/scala/Kernel.scala index f69924f1..1e39c10f 100644 --- a/modules/core/shared/src/main/scala/Kernel.scala +++ b/modules/core/shared/src/main/scala/Kernel.scala @@ -4,8 +4,21 @@ package natchez +import org.typelevel.ci._ + +import java.util +import scala.jdk.CollectionConverters._ + /** An opaque hunk of data that we can hand off to another system (in the form of HTTP headers), * which can then create new spans as children of this one. By this mechanism we allow our trace * to span remote calls. */ -final case class Kernel(toHeaders: Map[String, String]) +final case class Kernel(toHeaders: Map[CIString, String]) { + private[natchez] def toJava: util.Map[String, String] = + toHeaders.map { case (k, v) => k.toString -> v }.asJava +} + +object Kernel { + private[natchez] def fromJava(headers: util.Map[String, String]): Kernel = + apply(headers.asScala.map { case (k, v) => CIString(k) -> v }.toMap) +} diff --git a/modules/datadog/src/main/scala/DDEntryPoint.scala b/modules/datadog/src/main/scala/DDEntryPoint.scala index 313bf651..d444feb4 100644 --- a/modules/datadog/src/main/scala/DDEntryPoint.scala +++ b/modules/datadog/src/main/scala/DDEntryPoint.scala @@ -11,7 +11,6 @@ import io.opentracing.propagation.{Format, TextMapAdapter} import io.{opentracing => ot} import java.net.URI -import scala.jdk.CollectionConverters._ final class DDEntryPoint[F[_]: Sync](tracer: ot.Tracer, uriPrefix: Option[URI]) extends EntryPoint[F] { @@ -26,7 +25,7 @@ final class DDEntryPoint[F[_]: Sync](tracer: ot.Tracer, uriPrefix: Option[URI]) Sync[F].delay { val spanContext = tracer.extract( Format.Builtin.HTTP_HEADERS, - new TextMapAdapter(kernel.toHeaders.asJava) + new TextMapAdapter(kernel.toJava) ) tracer.buildSpan(name).asChildOf(spanContext).start() } diff --git a/modules/datadog/src/main/scala/DDSpan.scala b/modules/datadog/src/main/scala/DDSpan.scala index 25075087..346b506e 100644 --- a/modules/datadog/src/main/scala/DDSpan.scala +++ b/modules/datadog/src/main/scala/DDSpan.scala @@ -35,7 +35,7 @@ final case class DDSpan[F[_]: Sync]( Format.Builtin.HTTP_HEADERS, new TextMapAdapter(m) ) - Kernel(m.asScala.toMap) + Kernel.fromJava(m) } def put(fields: (String, TraceValue)*): F[Unit] = @@ -55,7 +55,7 @@ final case class DDSpan[F[_]: Sync]( override def makeSpan(name: String, options: Span.Options): Resource[F, Span[F]] = { val parent = options.parentKernel.map(k => - tracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapAdapter(k.toHeaders.asJava)) + tracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapAdapter(k.toJava)) ) Span.putErrorFields( Resource diff --git a/modules/docs/src/main/paradox/reference/entrypoints.md b/modules/docs/src/main/paradox/reference/entrypoints.md index 4ec90bc3..eae5ec70 100644 --- a/modules/docs/src/main/paradox/reference/entrypoints.md +++ b/modules/docs/src/main/paradox/reference/entrypoints.md @@ -45,7 +45,7 @@ def continuedRoutes(ep: EntryPoint[IO]): HttpRoutes[IO] = case req@(GET -> Root / "hello" / name) => val k: Kernel = - Kernel(req.headers.headers.map { h => h.name.toString -> h.value }.toMap) + Kernel(req.headers.headers.map { h => h.name -> h.value }.toMap) ep.continueOrElseRoot("hello", k).use { span => span.put("the-name" -> name) *> diff --git a/modules/docs/src/main/paradox/reference/kernels.md b/modules/docs/src/main/paradox/reference/kernels.md index 3199a250..6ccbff06 100644 --- a/modules/docs/src/main/paradox/reference/kernels.md +++ b/modules/docs/src/main/paradox/reference/kernels.md @@ -16,7 +16,6 @@ import org.http4s.{ EntityDecoder, Uri, Header } import org.http4s.Method.GET import org.http4s.client.Client import org.http4s.client.dsl.io._ -import org.typelevel.ci.CIString ``` We can add a `Span`'s kernel to an outgoing `Client` request. If the remote server supports tracing via the same back end, it can extend the trace with child spans. @@ -27,7 +26,7 @@ def makeRequest[A](span: Span[IO], client: Client[IO], uri: Uri)( ): IO[A] = span.kernel.flatMap { k => // turn a Map[String, String] into List[Header] - val http4sHeaders = k.toHeaders.map { case (k, v) => Header.Raw(CIString(k), v) } .toSeq + val http4sHeaders = k.toHeaders.map { case (k, v) => Header.Raw(k, v) } .toSeq client.expect[A](GET(uri).withHeaders(http4sHeaders)) } ``` diff --git a/modules/docs/src/main/scala/Junk.scala b/modules/docs/src/main/scala/Junk.scala index 885589df..9ce8cebf 100644 --- a/modules/docs/src/main/scala/Junk.scala +++ b/modules/docs/src/main/scala/Junk.scala @@ -1,24 +1,44 @@ +/* + * Copyright (c) 2022 Typelevel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + // Copyright (c) 2019-2020 by Rob Norris and Contributors // This software is licensed under the MIT License (MIT). // For more information see LICENSE or https://opensource.org/licenses/MIT import cats.effect.IO -import natchez.{ Span } -import org.http4s.{ EntityDecoder, Uri, Header } +import natchez.Span +import org.http4s.{EntityDecoder, Uri, Header} import org.http4s.Method.GET import org.http4s.client.Client import org.http4s.client.dsl.io._ -import org.typelevel.ci.CIString object X { - def makeRequest[A](span: Span[IO], client: Client[IO], uri: Uri)( - implicit ev: EntityDecoder[IO, A] + def makeRequest[A](span: Span[IO], client: Client[IO], uri: Uri)(implicit + ev: EntityDecoder[IO, A] ): IO[A] = span.kernel.flatMap { k => // turn a Map[String, String] into List[Header] - val http4sHeaders = k.toHeaders.map { case (k, v) => Header.Raw(CIString(k), v) } .toSeq + val http4sHeaders = k.toHeaders.map { case (k, v) => Header.Raw(k, v) }.toSeq client.expect[A](GET(uri).withHeaders(http4sHeaders)) } -} \ No newline at end of file +} diff --git a/modules/honeycomb/src/main/scala/HoneycombSpan.scala b/modules/honeycomb/src/main/scala/HoneycombSpan.scala index 401c6833..c83774af 100644 --- a/modules/honeycomb/src/main/scala/HoneycombSpan.scala +++ b/modules/honeycomb/src/main/scala/HoneycombSpan.scala @@ -14,6 +14,7 @@ import java.time.Instant import java.util.UUID import natchez._ import java.net.URI +import org.typelevel.ci._ private[honeycomb] final case class HoneycombSpan[F[_]: Sync]( client: HoneyClient, @@ -77,8 +78,8 @@ private[honeycomb] final case class HoneycombSpan[F[_]: Sync]( private[honeycomb] object HoneycombSpan { object Headers { - val TraceId = "X-Natchez-Trace-Id" - val SpanId = "X-Natchez-Parent-Span-Id" + val TraceId = ci"X-Natchez-Trace-Id" + val SpanId = ci"X-Natchez-Parent-Span-Id" } private def uuid[F[_]: Sync]: F[UUID] = diff --git a/modules/jaeger/src/main/scala/JaegerEntryPoint.scala b/modules/jaeger/src/main/scala/JaegerEntryPoint.scala index faaceaee..301c3c0b 100644 --- a/modules/jaeger/src/main/scala/JaegerEntryPoint.scala +++ b/modules/jaeger/src/main/scala/JaegerEntryPoint.scala @@ -11,7 +11,6 @@ import io.jaegertracing.internal.exceptions.UnsupportedFormatException import io.opentracing.propagation.{Format, TextMapAdapter} import io.{opentracing => ot} -import scala.jdk.CollectionConverters._ import java.net.URI final class JaegerEntryPoint[F[_]: Sync](tracer: ot.Tracer, uriPrefix: Option[URI]) @@ -22,7 +21,7 @@ final class JaegerEntryPoint[F[_]: Sync](tracer: ot.Tracer, uriPrefix: Option[UR Sync[F].delay { val p = tracer.extract( Format.Builtin.HTTP_HEADERS, - new TextMapAdapter(kernel.toHeaders.asJava) + new TextMapAdapter(kernel.toJava) ) tracer.buildSpan(name).asChildOf(p).start() } diff --git a/modules/jaeger/src/main/scala/JaegerSpan.scala b/modules/jaeger/src/main/scala/JaegerSpan.scala index a1a5800c..d7c2900e 100644 --- a/modules/jaeger/src/main/scala/JaegerSpan.scala +++ b/modules/jaeger/src/main/scala/JaegerSpan.scala @@ -35,7 +35,7 @@ private[jaeger] final case class JaegerSpan[F[_]: Sync]( Format.Builtin.HTTP_HEADERS, new TextMapAdapter(m) ) - Kernel(m.asScala.toMap) + Kernel.fromJava(m) } override def put(fields: (String, TraceValue)*): F[Unit] = @@ -75,7 +75,7 @@ private[jaeger] final case class JaegerSpan[F[_]: Sync]( val p = options.parentKernel.map(k => tracer.extract( Format.Builtin.HTTP_HEADERS, - new TextMapAdapter(k.toHeaders.asJava) + new TextMapAdapter(k.toJava) ) ) Sync[F] diff --git a/modules/lightstep/src/main/scala/LightstepEntryPoint.scala b/modules/lightstep/src/main/scala/LightstepEntryPoint.scala index a3c08af9..51bd2dc4 100644 --- a/modules/lightstep/src/main/scala/LightstepEntryPoint.scala +++ b/modules/lightstep/src/main/scala/LightstepEntryPoint.scala @@ -10,8 +10,6 @@ import cats.syntax.all._ import io.opentracing.Tracer import io.opentracing.propagation.{Format, TextMapAdapter} -import scala.jdk.CollectionConverters._ - final class LightstepEntryPoint[F[_]: Sync](tracer: Tracer) extends EntryPoint[F] { override def root(name: String): Resource[F, Span[F]] = Resource @@ -23,7 +21,7 @@ final class LightstepEntryPoint[F[_]: Sync](tracer: Tracer) extends EntryPoint[F .make( Sync[F].delay { val p = - tracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapAdapter(kernel.toHeaders.asJava)) + tracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapAdapter(kernel.toJava)) tracer.buildSpan(name).asChildOf(p).start() } )(s => Sync[F].delay(s.finish())) diff --git a/modules/lightstep/src/main/scala/LightstepSpan.scala b/modules/lightstep/src/main/scala/LightstepSpan.scala index a21dc590..ddcf03b0 100644 --- a/modules/lightstep/src/main/scala/LightstepSpan.scala +++ b/modules/lightstep/src/main/scala/LightstepSpan.scala @@ -27,7 +27,7 @@ private[lightstep] final case class LightstepSpan[F[_]: Sync]( Sync[F].delay { val m = new java.util.HashMap[String, String] tracer.inject(span.context, Format.Builtin.HTTP_HEADERS, new TextMapAdapter(m)) - Kernel(m.asScala.toMap) + Kernel.fromJava(m) } override def put(fields: (String, TraceValue)*): F[Unit] = @@ -63,7 +63,7 @@ private[lightstep] final case class LightstepSpan[F[_]: Sync]( override def makeSpan(name: String, options: Span.Options): Resource[F, Span[F]] = { val p = options.parentKernel.map(k => - tracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapAdapter(k.toHeaders.asJava)) + tracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapAdapter(k.toJava)) ) Span.putErrorFields( Resource diff --git a/modules/log-odin/src/main/scala/LogSpan.scala b/modules/log-odin/src/main/scala/LogSpan.scala index 935ce353..20f6e5f9 100644 --- a/modules/log-odin/src/main/scala/LogSpan.scala +++ b/modules/log-odin/src/main/scala/LogSpan.scala @@ -19,6 +19,7 @@ import io.circe.syntax._ import io.circe.JsonObject import io.odin.Logger import java.net.URI +import org.typelevel.ci._ private[logodin] final case class LogSpan[F[_]: Sync: Logger]( service: String, @@ -130,8 +131,8 @@ private[logodin] object LogSpan { } object Headers { - val TraceId = "X-Natchez-Trace-Id" - val SpanId = "X-Natchez-Parent-Span-Id" + val TraceId = ci"X-Natchez-Trace-Id" + val SpanId = ci"X-Natchez-Parent-Span-Id" } private def uuid[F[_]: Sync]: F[UUID] = diff --git a/modules/log/shared/src/main/scala/LogSpan.scala b/modules/log/shared/src/main/scala/LogSpan.scala index 4841ff76..b14e3411 100644 --- a/modules/log/shared/src/main/scala/LogSpan.scala +++ b/modules/log/shared/src/main/scala/LogSpan.scala @@ -20,6 +20,7 @@ import io.circe.Encoder import io.circe.syntax._ import io.circe.JsonObject import org.typelevel.log4cats.Logger +import org.typelevel.ci._ import java.net.URI @@ -125,8 +126,8 @@ private[log] object LogSpan { } object Headers { - val TraceId = "X-Natchez-Trace-Id" - val SpanId = "X-Natchez-Parent-Span-Id" + val TraceId = ci"X-Natchez-Trace-Id" + val SpanId = ci"X-Natchez-Parent-Span-Id" } private def uuid[F[_]: Sync]: F[UUID] = diff --git a/modules/newrelic/src/main/scala/natchez/newrelic/NewrelicSpan.scala b/modules/newrelic/src/main/scala/natchez/newrelic/NewrelicSpan.scala index 922b9494..eae9c08c 100644 --- a/modules/newrelic/src/main/scala/natchez/newrelic/NewrelicSpan.scala +++ b/modules/newrelic/src/main/scala/natchez/newrelic/NewrelicSpan.scala @@ -15,6 +15,7 @@ import com.newrelic.telemetry.spans.{Span, SpanBatch, SpanBatchSender} import natchez.TraceValue.{BooleanValue, NumberValue, StringValue} import natchez.newrelic.NewrelicSpan.Headers import natchez.{Kernel, TraceValue} +import org.typelevel.ci._ import scala.jdk.CollectionConverters._ @@ -70,8 +71,8 @@ private[newrelic] final case class NewrelicSpan[F[_]: Sync]( object NewrelicSpan { object Headers { - val TraceId = "X-Natchez-Trace-Id" - val SpanId = "X-Natchez-Parent-Span-Id" + val TraceId = ci"X-Natchez-Trace-Id" + val SpanId = ci"X-Natchez-Parent-Span-Id" } def fromKernel[F[_]: Sync]( diff --git a/modules/opencensus/src/main/scala/OpenCensusSpan.scala b/modules/opencensus/src/main/scala/OpenCensusSpan.scala index a4e28bed..381b0935 100644 --- a/modules/opencensus/src/main/scala/OpenCensusSpan.scala +++ b/modules/opencensus/src/main/scala/OpenCensusSpan.scala @@ -14,6 +14,7 @@ import io.opencensus.trace.{AttributeValue, Sampler, Tracer, Tracing} import io.opencensus.trace.propagation.SpanContextParseException import io.opencensus.trace.propagation.TextFormat.Getter import natchez.TraceValue.{BooleanValue, NumberValue, StringValue} +import org.typelevel.ci._ import scala.collection.mutable import java.net.URI @@ -51,7 +52,7 @@ private[opencensus] final case class OpenCensusSpan[F[_]: Sync]( Sync[F].delay(span.addAnnotation(event)).void override def kernel: F[Kernel] = Sync[F].delay { - val headers: mutable.Map[String, String] = mutable.Map.empty[String, String] + val headers: mutable.Map[CIString, String] = mutable.Map.empty[CIString, String] Tracing.getPropagationComponent.getB3Format .inject(span.getContext, headers, spanContextSetter) Kernel(headers.toMap) @@ -89,9 +90,9 @@ private[opencensus] final case class OpenCensusSpan[F[_]: Sync]( } private[opencensus] object OpenCensusSpan { - private val spanContextSetter = new Setter[mutable.Map[String, String]] { - override def put(carrier: mutable.Map[String, String], key: String, value: String): Unit = { - carrier.put(key, value) + private val spanContextSetter = new Setter[mutable.Map[CIString, String]] { + override def put(carrier: mutable.Map[CIString, String], key: String, value: String): Unit = { + carrier.put(CIString(key), value) () } } @@ -188,5 +189,5 @@ private[opencensus] object OpenCensusSpan { } private val spanContextGetter: Getter[Kernel] = (carrier: Kernel, key: String) => - carrier.toHeaders(key) + carrier.toHeaders(CIString(key)) } diff --git a/modules/opentelemetry/src/main/scala/natchez/opentelemetry/OpenTelemetrySpan.scala b/modules/opentelemetry/src/main/scala/natchez/opentelemetry/OpenTelemetrySpan.scala index fc3ecffd..ec2ac620 100644 --- a/modules/opentelemetry/src/main/scala/natchez/opentelemetry/OpenTelemetrySpan.scala +++ b/modules/opentelemetry/src/main/scala/natchez/opentelemetry/OpenTelemetrySpan.scala @@ -22,6 +22,7 @@ import TraceValue.{BooleanValue, NumberValue, StringValue} import java.net.URI import scala.collection.mutable import io.opentelemetry.api.common.Attributes +import org.typelevel.ci._ private[opentelemetry] final case class OpenTelemetrySpan[F[_]: Sync]( otel: OTel, @@ -64,7 +65,7 @@ private[opentelemetry] final case class OpenTelemetrySpan[F[_]: Sync]( override def kernel: F[Kernel] = Sync[F].delay { - val headers: mutable.Map[String, String] = mutable.Map.empty[String, String] + val headers: mutable.Map[CIString, String] = mutable.Map.empty[CIString, String] otel.getPropagators.getTextMapPropagator.inject( Context.current().`with`(span), headers, @@ -215,15 +216,16 @@ private[opentelemetry] object OpenTelemetrySpan { import scala.jdk.CollectionConverters._ - override def keys(carrier: Kernel): lang.Iterable[String] = carrier.toHeaders.keys.asJava + override def keys(carrier: Kernel): lang.Iterable[String] = + carrier.toHeaders.keys.map(_.toString).asJava override def get(carrier: Kernel, key: String): String = - carrier.toHeaders.getOrElse(key, null) + carrier.toHeaders.getOrElse(CIString(key), null) } - private val spanContextSetter = new TextMapSetter[mutable.Map[String, String]] { - override def set(carrier: mutable.Map[String, String], key: String, value: String): Unit = { - carrier.put(key, value) + private val spanContextSetter = new TextMapSetter[mutable.Map[CIString, String]] { + override def set(carrier: mutable.Map[CIString, String], key: String, value: String): Unit = { + carrier.put(CIString(key), value) () } } diff --git a/modules/xray/src/main/scala/natchez/xray/XRaySpan.scala b/modules/xray/src/main/scala/natchez/xray/XRaySpan.scala index 665b71e1..8f5a3f77 100644 --- a/modules/xray/src/main/scala/natchez/xray/XRaySpan.scala +++ b/modules/xray/src/main/scala/natchez/xray/XRaySpan.scala @@ -21,6 +21,7 @@ import io.circe.syntax._ import cats.effect.kernel.Resource.ExitCase.Canceled import cats.effect.kernel.Resource.ExitCase.Errored import cats.effect.kernel.Resource.ExitCase.Succeeded +import org.typelevel.ci._ import scala.concurrent.duration._ import scala.util.matching.Regex @@ -154,7 +155,7 @@ private[xray] object XRaySpan { case NumberValue(n) => n.doubleValue.asJson } - val Header = "X-Amzn-Trace-Id" + val Header = ci"X-Amzn-Trace-Id" private[xray] def encodeHeader( rootId: String,