diff --git a/build.sbt b/build.sbt index b7c0c679a..35d778509 100644 --- a/build.sbt +++ b/build.sbt @@ -632,7 +632,7 @@ lazy val `sdk-contrib-aws-xray` = lazy val `oteljava-common` = project .in(file("oteljava/common")) .enablePlugins(BuildInfoPlugin) - .dependsOn(`core-common`.jvm) + .dependsOn(`core-common`.jvm % "compile->compile;test->test") .settings(munitDependencies) .settings( name := "otel4s-oteljava-common", diff --git a/core/all/src/main/scala/org/typelevel/otel4s/Otel4s.scala b/core/all/src/main/scala/org/typelevel/otel4s/Otel4s.scala index ec6de9066..0383a0e4d 100644 --- a/core/all/src/main/scala/org/typelevel/otel4s/Otel4s.scala +++ b/core/all/src/main/scala/org/typelevel/otel4s/Otel4s.scala @@ -17,6 +17,7 @@ package org.typelevel.otel4s import cats.mtl.Local +import org.typelevel.otel4s.baggage.BaggageManager import org.typelevel.otel4s.context.propagation.ContextPropagators import org.typelevel.otel4s.metrics.MeterProvider import org.typelevel.otel4s.trace.TracerProvider @@ -37,4 +38,7 @@ trait Otel4s[F[_]] { /** An entry point of the tracing API. */ def tracerProvider: TracerProvider[F] + + /** A utility for accessing and modifying [[baggage.Baggage `Baggage`]]. */ + def baggageManager: BaggageManager[F] } diff --git a/core/common/src/main/scala/org/typelevel/otel4s/baggage/Baggage.scala b/core/common/src/main/scala/org/typelevel/otel4s/baggage/Baggage.scala index 02382a09b..19d011ec2 100644 --- a/core/common/src/main/scala/org/typelevel/otel4s/baggage/Baggage.scala +++ b/core/common/src/main/scala/org/typelevel/otel4s/baggage/Baggage.scala @@ -50,6 +50,20 @@ sealed trait Baggage { */ def updated(key: String, value: String, metadata: Option[String]): Baggage + /** Adds or updates the entry that has the given `key` if it is present. + * + * @param key + * the key for the entry + * + * @param value + * the value for the entry to associate with the key + * + * @param metadata + * the optional metadata to associate with the key + */ + final def updated(key: String, value: String, metadata: String): Baggage = + updated(key, value, Some(metadata)) + /** Adds or updates the entry that has the given `key` if it is present. * * @param key diff --git a/core/common/src/main/scala/org/typelevel/otel4s/baggage/BaggageManager.scala b/core/common/src/main/scala/org/typelevel/otel4s/baggage/BaggageManager.scala new file mode 100644 index 000000000..5f63b645a --- /dev/null +++ b/core/common/src/main/scala/org/typelevel/otel4s/baggage/BaggageManager.scala @@ -0,0 +1,40 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed 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.typelevel.otel4s.baggage + +import cats.mtl.Local + +/** A utility for accessing and modifying [[`Baggage`]]. */ +trait BaggageManager[F[_]] extends Local[F, Baggage] { + + /** @return the current `Baggage` */ + final def current: F[Baggage] = ask[Baggage] + + /** @return + * the [[Baggage.Entry entry]] to which the specified key is mapped, or `None` if the current `Baggage` contains no + * mapping for the key + */ + def get(key: String): F[Option[Baggage.Entry]] = + reader(_.get(key)) + + /** @return + * the value (without [[Baggage.Metadata metadata]]) to which the specified key is mapped, or `None` if the current + * `Baggage` contains no mapping for the key + */ + def getValue(key: String): F[Option[String]] = + reader(_.get(key).map(_.value)) +} diff --git a/core/common/src/test/scala/org/typelevel/otel4s/baggage/BaggageManagerSuite.scala b/core/common/src/test/scala/org/typelevel/otel4s/baggage/BaggageManagerSuite.scala new file mode 100644 index 000000000..5d5fa6dfe --- /dev/null +++ b/core/common/src/test/scala/org/typelevel/otel4s/baggage/BaggageManagerSuite.scala @@ -0,0 +1,100 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed 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.typelevel.otel4s.baggage + +import cats.effect.IO +import munit.CatsEffectSuite + +abstract class BaggageManagerSuite extends CatsEffectSuite { + protected def baggageManager: IO[BaggageManager[IO]] + + protected final def testManager(name: String)(f: BaggageManager[IO] => IO[Unit]): Unit = + test(name)(baggageManager.flatMap(f)) + + testManager(".ask consistent with .scope") { m => + val b1 = Baggage.empty + .updated("key", "value") + .updated("foo", "bar", "baz") + m.scope(m.ask[Baggage].map(assertEquals(_, b1)))(b1) + } + + testManager(".ask consistent with .local") { m => + m.local { + for (baggage <- m.ask[Baggage]) + yield { + assertEquals(baggage.get("key"), Some(Baggage.Entry("value", None))) + assertEquals( + baggage.get("foo"), + Some(Baggage.Entry("bar", Some(Baggage.Metadata("baz")))) + ) + } + }(_.updated("key", "value").updated("foo", "bar", "baz")) + } + + testManager(".current is equivalent to .ask") { m => + val check = m.scope { + for { + a <- m.ask[Baggage] + b <- m.current + } yield assertEquals(a, b) + }(_) + check(Baggage.empty) + check( + Baggage.empty + .updated("key", "value") + .updated("foo", "bar", "baz") + ) + } + + testManager(".get consistent with .ask") { m => + val check = m.scope { + for { + baggage <- m.ask[Baggage] + v1 <- m.get("key") + v2 <- m.get("foo") + } yield { + assertEquals(v1, baggage.get("key")) + assertEquals(v2, baggage.get("foo")) + } + }(_) + check(Baggage.empty) + check( + Baggage.empty + .updated("key", "value") + .updated("foo", "bar", "baz") + ) + } + + testManager(".getValue consistent with .ask") { m => + val check = m.scope { + for { + baggage <- m.ask[Baggage] + v1 <- m.getValue("key") + v2 <- m.getValue("foo") + } yield { + assertEquals(v1, baggage.get("key").map(_.value)) + assertEquals(v2, baggage.get("foo").map(_.value)) + } + }(_) + check(Baggage.empty) + check( + Baggage.empty + .updated("key", "value") + .updated("foo", "bar", "baz") + ) + } +} diff --git a/oteljava/all/src/main/scala/org/typelevel/otel4s/oteljava/OtelJava.scala b/oteljava/all/src/main/scala/org/typelevel/otel4s/oteljava/OtelJava.scala index bd996c564..f2ab80c4f 100644 --- a/oteljava/all/src/main/scala/org/typelevel/otel4s/oteljava/OtelJava.scala +++ b/oteljava/all/src/main/scala/org/typelevel/otel4s/oteljava/OtelJava.scala @@ -28,9 +28,11 @@ import io.opentelemetry.sdk.autoconfigure.{AutoConfiguredOpenTelemetrySdk => Aut import io.opentelemetry.sdk.autoconfigure.{AutoConfiguredOpenTelemetrySdkBuilder => AutoConfigOtelSdkBuilder} import io.opentelemetry.sdk.common.CompletableResultCode import org.typelevel.otel4s.Otel4s +import org.typelevel.otel4s.baggage.BaggageManager import org.typelevel.otel4s.context.LocalProvider import org.typelevel.otel4s.context.propagation.ContextPropagators import org.typelevel.otel4s.metrics.MeterProvider +import org.typelevel.otel4s.oteljava.baggage.BaggageManagerImpl import org.typelevel.otel4s.oteljava.context.Context import org.typelevel.otel4s.oteljava.context.LocalContext import org.typelevel.otel4s.oteljava.context.LocalContextProvider @@ -47,6 +49,8 @@ final class OtelJava[F[_]] private ( extends Otel4s[F] { type Ctx = Context + val baggageManager: BaggageManager[F] = BaggageManagerImpl.fromLocal + override def toString: String = s"OtelJava{$underlying}" } diff --git a/oteljava/common/src/main/scala/org/typelevel/otel4s/oteljava/AttributeConverters.scala b/oteljava/common/src/main/scala/org/typelevel/otel4s/oteljava/AttributeConverters.scala index 0078f70a2..7e3b22b66 100644 --- a/oteljava/common/src/main/scala/org/typelevel/otel4s/oteljava/AttributeConverters.scala +++ b/oteljava/common/src/main/scala/org/typelevel/otel4s/oteljava/AttributeConverters.scala @@ -44,7 +44,7 @@ import scala.jdk.CollectionConverters._ * import org.typelevel.otel4s.oteljava.AttributeConverters._ * * val attributes: Attributes = - * io.opentelemetry.api.common.Attributes.builder() + * JAttributes.builder() * .put("key", "value") * .build() * .toScala diff --git a/oteljava/common/src/main/scala/org/typelevel/otel4s/oteljava/baggage/BaggageConverters.scala b/oteljava/common/src/main/scala/org/typelevel/otel4s/oteljava/baggage/BaggageConverters.scala new file mode 100644 index 000000000..bacb11e23 --- /dev/null +++ b/oteljava/common/src/main/scala/org/typelevel/otel4s/oteljava/baggage/BaggageConverters.scala @@ -0,0 +1,92 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed 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.typelevel.otel4s.oteljava.baggage + +import io.opentelemetry.api.baggage.{Baggage => JBaggage} +import io.opentelemetry.api.baggage.BaggageEntry +import io.opentelemetry.api.baggage.BaggageEntryMetadata +import org.typelevel.otel4s.baggage.Baggage + +/** This object provides extension methods that convert between Scala and Java `Baggage`s, Java `BaggageEntry`s, and + * Scala `Baggage.Entry`s using `toScala` and `toJava` extension methods. + * + * {{{ + * import io.opentelemetry.api.baggage.{Baggage => JBaggage} + * import org.typelevel.baggage.Baggage + * import org.typelevel.otel4s.oteljava.baggage.BaggageConverters._ + * + * val baggage: Baggage = + * JBaggage.builder() + * .put("key", "value") + * .build() + * .toScala + * }}} + * + * The conversions do not return wrappers. + */ +object BaggageConverters { + + implicit final class BaggageHasToJava(private val baggage: Baggage) extends AnyVal { + + /** Converts a Scala `Baggage` to a Java `Baggage`. */ + def toJava: JBaggage = Explicit.toJava(baggage) + } + + implicit final class BaggageEntryHasToScala(private val entry: BaggageEntry) extends AnyVal { + + /** Converts a Java `BaggageEntry` to a Scala `Baggage.Entry`. */ + def toScala: Baggage.Entry = Explicit.toScala(entry) + } + + implicit final class BaggageHasToScala(private val baggage: JBaggage) extends AnyVal { + + /** Converts a Java `Baggage` to a Scala `Baggage`. */ + def toScala: Baggage = Explicit.toScala(baggage) + } + + private[this] object Explicit { + def toJava(metadata: Option[Baggage.Metadata]): BaggageEntryMetadata = + metadata.fold(BaggageEntryMetadata.empty()) { m => + BaggageEntryMetadata.create(m.value) + } + + def toJava(baggage: Baggage): JBaggage = { + val builder = JBaggage.builder() + baggage.asMap.foreach { case (key, entry) => + builder.put(key, entry.value, toJava(entry.metadata)) + } + builder.build() + } + + def toScala(metadata: BaggageEntryMetadata): Option[Baggage.Metadata] = { + val value = metadata.getValue + Option.unless(value.isEmpty)(Baggage.Metadata(value)) + } + + def toScala(entry: BaggageEntry): Baggage.Entry = + Baggage.Entry(entry.getValue, toScala(entry.getMetadata)) + + def toScala(baggage: JBaggage): Baggage = { + var res = Baggage.empty + baggage.forEach { (key, entry) => + val metadata = entry.getMetadata.getValue + res = res.updated(key, entry.getValue, Option.unless(metadata.isEmpty)(metadata)) + } + res + } + } +} diff --git a/oteljava/common/src/main/scala/org/typelevel/otel4s/oteljava/baggage/BaggageManagerImpl.scala b/oteljava/common/src/main/scala/org/typelevel/otel4s/oteljava/baggage/BaggageManagerImpl.scala new file mode 100644 index 000000000..f954b8043 --- /dev/null +++ b/oteljava/common/src/main/scala/org/typelevel/otel4s/oteljava/baggage/BaggageManagerImpl.scala @@ -0,0 +1,56 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed 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.typelevel.otel4s.oteljava.baggage + +import cats.Applicative +import io.opentelemetry.api.baggage.{Baggage => JBaggage} +import org.typelevel.otel4s.baggage.Baggage +import org.typelevel.otel4s.baggage.BaggageManager +import org.typelevel.otel4s.oteljava.baggage.BaggageConverters._ +import org.typelevel.otel4s.oteljava.context.Context +import org.typelevel.otel4s.oteljava.context.LocalContext + +private final class BaggageManagerImpl[F[_]] private (implicit localContext: LocalContext[F]) + extends BaggageManager[F] { + def applicative: Applicative[F] = localContext.applicative + def ask[E2 >: Baggage]: F[E2] = + localContext.reader { ctx => + Option(JBaggage.fromContextOrNull(ctx.underlying)) + .fold(Baggage.empty)(_.toScala) + } + def local[A](fa: F[A])(f: Baggage => Baggage): F[A] = + localContext.local(fa) { ctx => + val jCtx = ctx.underlying + val jBaggage = JBaggage.fromContext(jCtx) + val updated = f(jBaggage.toScala).toJava + Context.wrap(jCtx.`with`(updated)) + } + override def get(key: String): F[Option[Baggage.Entry]] = + localContext.reader { ctx => + Option(JBaggage.fromContext(ctx.underlying).getEntry(key)) + .map(_.toScala) + } + override def getValue(key: String): F[Option[String]] = + localContext.reader { ctx => + Option(JBaggage.fromContext(ctx.underlying).getEntryValue(key)) + } +} + +private[oteljava] object BaggageManagerImpl { + def fromLocal[F[_]: LocalContext]: BaggageManager[F] = + new BaggageManagerImpl[F] +} diff --git a/oteljava/common/src/test/scala/org/typelevel/otel4s/oteljava/baggage/BaggageConvertersSuite.scala b/oteljava/common/src/test/scala/org/typelevel/otel4s/oteljava/baggage/BaggageConvertersSuite.scala new file mode 100644 index 000000000..d65c4ba97 --- /dev/null +++ b/oteljava/common/src/test/scala/org/typelevel/otel4s/oteljava/baggage/BaggageConvertersSuite.scala @@ -0,0 +1,64 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed 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.typelevel.otel4s.oteljava.baggage + +import io.opentelemetry.api.baggage.{Baggage => JBaggage} +import io.opentelemetry.api.baggage.BaggageEntry +import io.opentelemetry.api.baggage.BaggageEntryMetadata +import munit.FunSuite +import org.typelevel.otel4s.baggage.Baggage +import org.typelevel.otel4s.oteljava.baggage.BaggageConverters._ + +class BaggageConvertersSuite extends FunSuite { + import BaggageConvertersSuite._ + + test("conversion to and from OpenTelemetry Baggage") { + val baggage = Baggage.empty + .updated("key", "value") + .updated("foo", "bar", "baz") + val jBaggage = JBaggage + .builder() + .put("key", "value") + .put("foo", "bar", BaggageEntryMetadata.create("baz")) + .build() + + assertEquals(baggage.toJava, jBaggage) + assertEquals(jBaggage.toScala, baggage) + assertEquals(baggage.toJava.toScala, baggage) + assertEquals(jBaggage.toScala.toJava, jBaggage) + } + + test("conversion from OpenTelemetry BaggageEntry") { + assertEquals(jBaggageEntry("foo").toScala, Baggage.Entry("foo", None)) + assertEquals( + jBaggageEntry("bar", "baz").toScala, + Baggage.Entry("bar", Some(Baggage.Metadata("baz"))) + ) + assertEquals(jBaggageEntry("qux", "").toScala, Baggage.Entry("qux", None)) + } +} + +private object BaggageConvertersSuite { + def jBaggageEntry(value: String): BaggageEntry = + JBaggage.builder().put("key", value).build().getEntry("key") + def jBaggageEntry(value: String, metadata: String): BaggageEntry = + JBaggage + .builder() + .put("key", value, BaggageEntryMetadata.create(metadata)) + .build() + .getEntry("key") +} diff --git a/oteljava/common/src/test/scala/org/typelevel/otel4s/oteljava/baggage/BaggageManagerImplSuite.scala b/oteljava/common/src/test/scala/org/typelevel/otel4s/oteljava/baggage/BaggageManagerImplSuite.scala new file mode 100644 index 000000000..9b105991f --- /dev/null +++ b/oteljava/common/src/test/scala/org/typelevel/otel4s/oteljava/baggage/BaggageManagerImplSuite.scala @@ -0,0 +1,32 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed 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.typelevel.otel4s.oteljava.baggage + +import cats.effect.IO +import cats.effect.IOLocal +import org.typelevel.otel4s.baggage.BaggageManager +import org.typelevel.otel4s.baggage.BaggageManagerSuite +import org.typelevel.otel4s.oteljava.context.Context +import org.typelevel.otel4s.oteljava.context.LocalContext + +class BaggageManagerImplSuite extends BaggageManagerSuite { + override protected def baggageManager: IO[BaggageManager[IO]] = + IOLocal(Context.root).map { iol => + implicit val local: LocalContext[IO] = iol.asLocal + BaggageManagerImpl.fromLocal[IO] + } +} diff --git a/oteljava/testkit/src/main/scala/org/typelevel/otel4s/oteljava/testkit/OtelJavaTestkit.scala b/oteljava/testkit/src/main/scala/org/typelevel/otel4s/oteljava/testkit/OtelJavaTestkit.scala index c1c9f22e5..70aab2f5c 100644 --- a/oteljava/testkit/src/main/scala/org/typelevel/otel4s/oteljava/testkit/OtelJavaTestkit.scala +++ b/oteljava/testkit/src/main/scala/org/typelevel/otel4s/oteljava/testkit/OtelJavaTestkit.scala @@ -22,9 +22,11 @@ import cats.effect.Resource import io.opentelemetry.context.propagation.{TextMapPropagator => JTextMapPropagator} import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder +import org.typelevel.otel4s.baggage.BaggageManager import org.typelevel.otel4s.context.LocalProvider import org.typelevel.otel4s.context.propagation.ContextPropagators import org.typelevel.otel4s.metrics.MeterProvider +import org.typelevel.otel4s.oteljava.baggage.BaggageManagerImpl import org.typelevel.otel4s.oteljava.context.Context import org.typelevel.otel4s.oteljava.context.LocalContext import org.typelevel.otel4s.oteljava.context.LocalContextProvider @@ -42,6 +44,8 @@ sealed abstract class OtelJavaTestkit[F[_]] private (implicit type Ctx = Context + val baggageManager: BaggageManager[F] = BaggageManagerImpl.fromLocal + override def toString: String = s"OtelJavaTestkit{meterProvider=$meterProvider, tracerProvider=$tracerProvider, propagators=$propagators}" } diff --git a/sdk/all/src/main/scala/org/typelevel/otel4s/sdk/OpenTelemetrySdk.scala b/sdk/all/src/main/scala/org/typelevel/otel4s/sdk/OpenTelemetrySdk.scala index 94d45c4d9..811f11d62 100644 --- a/sdk/all/src/main/scala/org/typelevel/otel4s/sdk/OpenTelemetrySdk.scala +++ b/sdk/all/src/main/scala/org/typelevel/otel4s/sdk/OpenTelemetrySdk.scala @@ -28,6 +28,7 @@ import cats.syntax.apply._ import cats.syntax.flatMap._ import cats.syntax.functor._ import org.typelevel.otel4s.Otel4s +import org.typelevel.otel4s.baggage.BaggageManager import org.typelevel.otel4s.context.LocalProvider import org.typelevel.otel4s.context.propagation.ContextPropagators import org.typelevel.otel4s.context.propagation.TextMapPropagator @@ -37,6 +38,7 @@ import org.typelevel.otel4s.sdk.autoconfigure.CommonConfigKeys import org.typelevel.otel4s.sdk.autoconfigure.Config import org.typelevel.otel4s.sdk.autoconfigure.ExportersAutoConfigure import org.typelevel.otel4s.sdk.autoconfigure.TelemetryResourceAutoConfigure +import org.typelevel.otel4s.sdk.baggage.SdkBaggageManager import org.typelevel.otel4s.sdk.context.Context import org.typelevel.otel4s.sdk.context.LocalContext import org.typelevel.otel4s.sdk.context.LocalContextProvider @@ -63,6 +65,8 @@ final class OpenTelemetrySdk[F[_]] private ( type Ctx = Context + val baggageManager: BaggageManager[F] = SdkBaggageManager.fromLocal + override def toString: String = s"OpenTelemetrySdk{meterProvider=$meterProvider, tracerProvider=$tracerProvider, propagators=$propagators}" } diff --git a/sdk/common/shared/src/main/scala/org/typelevel/otel4s/sdk/baggage/SdkBaggageManager.scala b/sdk/common/shared/src/main/scala/org/typelevel/otel4s/sdk/baggage/SdkBaggageManager.scala new file mode 100644 index 000000000..d9bd2ca57 --- /dev/null +++ b/sdk/common/shared/src/main/scala/org/typelevel/otel4s/sdk/baggage/SdkBaggageManager.scala @@ -0,0 +1,49 @@ +/* + * Copyright 2023 Typelevel + * + * Licensed 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.typelevel.otel4s.sdk.baggage + +import cats.Applicative +import cats.effect.SyncIO +import org.typelevel.otel4s.baggage.Baggage +import org.typelevel.otel4s.baggage.BaggageManager +import org.typelevel.otel4s.sdk.context.Context +import org.typelevel.otel4s.sdk.context.LocalContext + +private final class SdkBaggageManager[F[_]](implicit localContext: LocalContext[F]) extends BaggageManager[F] { + import SdkBaggageManager._ + + def applicative: Applicative[F] = localContext.applicative + def ask[E2 >: Baggage]: F[E2] = + localContext.reader(baggageFromContext) + def local[A](fa: F[A])(f: Baggage => Baggage): F[A] = + localContext.local(fa) { ctx => + ctx.updated(BaggageKey, f(baggageFromContext(ctx))) + } +} + +private[sdk] object SdkBaggageManager { + val BaggageKey: Context.Key[Baggage] = + Context.Key + .unique[SyncIO, Baggage]("otel4s-baggage-key") + .unsafeRunSync() + + private def baggageFromContext(context: Context): Baggage = + context.get(BaggageKey).getOrElse(Baggage.empty) + + def fromLocal[F[_]: LocalContext]: BaggageManager[F] = + new SdkBaggageManager[F] +} diff --git a/sdk/common/shared/src/test/scala/org/typelevel/otel4s/sdk/baggage/SdkBaggageManagerSuite.scala b/sdk/common/shared/src/test/scala/org/typelevel/otel4s/sdk/baggage/SdkBaggageManagerSuite.scala new file mode 100644 index 000000000..1868dd0e0 --- /dev/null +++ b/sdk/common/shared/src/test/scala/org/typelevel/otel4s/sdk/baggage/SdkBaggageManagerSuite.scala @@ -0,0 +1,32 @@ +/* + * Copyright 2023 Typelevel + * + * Licensed 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.typelevel.otel4s.sdk.baggage + +import cats.effect.IO +import cats.effect.IOLocal +import org.typelevel.otel4s.baggage.BaggageManager +import org.typelevel.otel4s.baggage.BaggageManagerSuite +import org.typelevel.otel4s.sdk.context.Context +import org.typelevel.otel4s.sdk.context.LocalContext + +class SdkBaggageManagerSuite extends BaggageManagerSuite { + override protected def baggageManager: IO[BaggageManager[IO]] = + IOLocal(Context.root).map { iol => + implicit val local: LocalContext[IO] = iol.asLocal + SdkBaggageManager.fromLocal[IO] + } +} diff --git a/sdk/testkit/src/main/scala/org/typelevel/otel4s/sdk/testkit/OpenTelemetrySdkTestkit.scala b/sdk/testkit/src/main/scala/org/typelevel/otel4s/sdk/testkit/OpenTelemetrySdkTestkit.scala index 4d0ec4e62..8ed306e58 100644 --- a/sdk/testkit/src/main/scala/org/typelevel/otel4s/sdk/testkit/OpenTelemetrySdkTestkit.scala +++ b/sdk/testkit/src/main/scala/org/typelevel/otel4s/sdk/testkit/OpenTelemetrySdkTestkit.scala @@ -21,10 +21,12 @@ import cats.effect.Async import cats.effect.Resource import cats.effect.std.Console import org.typelevel.otel4s.Otel4s +import org.typelevel.otel4s.baggage.BaggageManager import org.typelevel.otel4s.context.LocalProvider import org.typelevel.otel4s.context.propagation.ContextPropagators import org.typelevel.otel4s.context.propagation.TextMapPropagator import org.typelevel.otel4s.metrics.MeterProvider +import org.typelevel.otel4s.sdk.baggage.SdkBaggageManager import org.typelevel.otel4s.sdk.context.Context import org.typelevel.otel4s.sdk.context.LocalContext import org.typelevel.otel4s.sdk.context.LocalContextProvider @@ -50,6 +52,8 @@ sealed abstract class OpenTelemetrySdkTestkit[F[_]] private (implicit type Ctx = Context + val baggageManager: BaggageManager[F] = SdkBaggageManager.fromLocal + override def toString: String = s"OpenTelemetrySdkTestkit{meterProvider=$meterProvider, tracerProvider=$tracerProvider, propagators=$propagators}" } diff --git a/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/SdkContextKeys.scala b/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/SdkContextKeys.scala index f32d1b49b..de28c9585 100644 --- a/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/SdkContextKeys.scala +++ b/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/SdkContextKeys.scala @@ -18,9 +18,12 @@ package org.typelevel.otel4s.sdk.trace import cats.effect.SyncIO import org.typelevel.otel4s.baggage.Baggage +import org.typelevel.otel4s.sdk.baggage.SdkBaggageManager import org.typelevel.otel4s.sdk.context.Context import org.typelevel.otel4s.trace.SpanContext +import scala.annotation.unchecked.uncheckedStable + /** The keys that can be used by a third-party integrations. */ object SdkContextKeys { @@ -48,9 +51,7 @@ object SdkContextKeys { * val baggage: Option[Baggage] = context.get(SdkContextKeys.BaggageKey) * }}} */ - val BaggageKey: Context.Key[Baggage] = - Context.Key - .unique[SyncIO, Baggage]("otel4s-trace-baggage-key") - .unsafeRunSync() + @uncheckedStable + def BaggageKey: Context.Key[Baggage] = SdkBaggageManager.BaggageKey }