From ac4175c2d9830dbb08a23a25497062c41d266295 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Fri, 12 May 2023 12:09:34 -0400 Subject: [PATCH 01/15] Experimental IOLocalContextStorage --- .../otel4s/java/IOLocalContextStorage.scala | 36 ++++++++++++++++ .../otel4s/java/ContextStorageSuite.scala | 43 +++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala create mode 100644 java/common/src/test/scala/org/typelevel/otel4s/java/ContextStorageSuite.scala diff --git a/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala b/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala new file mode 100644 index 000000000..b4cf1f6b2 --- /dev/null +++ b/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala @@ -0,0 +1,36 @@ +package org.typelevel.otel4s.java + +import cats.effect.IOLocal +import cats.effect.LiftIO +import cats.effect.std.Dispatcher +import io.opentelemetry.context.Context +import io.opentelemetry.context.ContextStorage +import io.opentelemetry.context.Scope + +class IOLocalContextStorage[F[_]: LiftIO]( + dispatcher: Dispatcher[F], + ioLocal: IOLocal[Context] +) extends ContextStorage { + + override def attach(toAttach: Context): Scope = + dispatcher.unsafeRunSync( + currentOrRoot + .flatMap { old => + ioLocal + .set(toAttach) + .as(new Scope { + def close() = dispatcher.unsafeRunSync(ioLocal.set(old).to[F]) + }) + } + .to[F] + ) + + override def current(): Context = { + dispatcher.unsafeRunSync(currentOrRoot.to[F]) + } + + private def currentOrRoot = ioLocal.get.map { + case null => Context.root() + case ctx => ctx + } +} diff --git a/java/common/src/test/scala/org/typelevel/otel4s/java/ContextStorageSuite.scala b/java/common/src/test/scala/org/typelevel/otel4s/java/ContextStorageSuite.scala new file mode 100644 index 000000000..7c71b26e1 --- /dev/null +++ b/java/common/src/test/scala/org/typelevel/otel4s/java/ContextStorageSuite.scala @@ -0,0 +1,43 @@ +package org.typelevel.otel4s.java + +import cats.effect.IO +import cats.effect.IOLocal +import cats.effect.Resource +import cats.effect.std.Dispatcher +import io.opentelemetry.context.Context +import io.opentelemetry.context.ContextKey +import io.opentelemetry.context.ContextStorage +import java.util.logging._ + +object Run extends cats.effect.IOApp.Simple { + + val key = ContextKey.named[String]("test") + + def run = + Dispatcher.parallel[IO].use { dispatcher => + for { + _ <- IO { + val rootLog = Logger.getLogger("") + rootLog.setLevel(Level.FINE) + rootLog.getHandlers().head.setLevel(Level.FINE) + } + ioLocal <- IOLocal(null: Context) + storage = new IOLocalContextStorage(dispatcher, ioLocal) + _ <- IO(ContextStorage.addWrapper(_ => storage)) + _ <- ioLocal.set(null) + _ <- IO.println(ContextStorage.get().getClass) + ctx = Context.root() + _ <- Resource + .make(IO(ctx.`with`(key, "hello").makeCurrent()))(scope => + IO(scope.close()) + ) + .surround { + for { + key <- IO(Context.current()) + _ <- IO.println(key) + } yield () + } + _ <- IO(Option(Context.current().get(key))).flatMap(v => IO.println(v)) + } yield () + } +} From 682abb65818c5a353fa1041429737e64d4458cee Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Wed, 17 May 2023 23:04:39 -0400 Subject: [PATCH 02/15] Try typelevel/cats-effect#3636 --- build.sbt | 5 ++- .../main/scala/ContextStorageExample.scala | 38 ++++++++++++++++ .../otel4s/java/IOLocalContextStorage.scala | 34 +++++---------- .../otel4s/java/ContextStorageSuite.scala | 43 ------------------- 4 files changed, 52 insertions(+), 68 deletions(-) create mode 100644 examples/src/main/scala/ContextStorageExample.scala delete mode 100644 java/common/src/test/scala/org/typelevel/otel4s/java/ContextStorageSuite.scala diff --git a/build.sbt b/build.sbt index f9c78feb2..f185752e1 100644 --- a/build.sbt +++ b/build.sbt @@ -26,7 +26,7 @@ ThisBuild / crossScalaVersions := Seq(Scala213, "3.2.2") ThisBuild / scalaVersion := Scala213 // the default Scala val CatsVersion = "2.9.0" -val CatsEffectVersion = "3.5.0" +val CatsEffectVersion = "3.5.0-30-0a69caf" val CatsMtlVersion = "1.3.1" val FS2Version = "3.6.1" val MUnitVersion = "1.0.0-M7" @@ -154,7 +154,7 @@ lazy val `java-common` = project .settings( name := "otel4s-java-common", libraryDependencies ++= Seq( - "org.typelevel" %%% "cats-effect-kernel" % CatsEffectVersion, + "org.typelevel" %%% "cats-effect" % CatsEffectVersion, "org.typelevel" %%% "cats-mtl" % CatsMtlVersion, "io.opentelemetry" % "opentelemetry-sdk" % OpenTelemetryVersion, "org.scalameta" %%% "munit" % MUnitVersion % Test @@ -241,6 +241,7 @@ lazy val examples = project ), run / fork := true, javaOptions += "-Dotel.java.global-autoconfigure.enabled=true", + javaOptions += "-Dcats.effect.tracing.dumpLocals", envVars ++= Map( "OTEL_PROPAGATORS" -> "b3multi", "OTEL_SERVICE_NAME" -> "Trace Example" diff --git a/examples/src/main/scala/ContextStorageExample.scala b/examples/src/main/scala/ContextStorageExample.scala new file mode 100644 index 000000000..5f786edfc --- /dev/null +++ b/examples/src/main/scala/ContextStorageExample.scala @@ -0,0 +1,38 @@ +import cats.effect.IO +import cats.effect.IOApp +import cats.effect.IOLocal +import cats.effect.Resource +import cats.effect.unsafe.IOLocals +import io.opentelemetry.context.Context +import io.opentelemetry.context.ContextKey +import io.opentelemetry.context.ContextStorage +import org.typelevel.otel4s.java.IOLocalContextStorage +import java.util.logging._ + +object ContextStorageExample extends IOApp.Simple { + + val key = ContextKey.named[String]("test") + + val printKey = + IO(Option(Context.current().get(key))).flatMap(v => IO.println(v)) + + def run = + for { + _ <- IO { + val rootLog = Logger.getLogger("") + rootLog.setLevel(Level.FINE) + rootLog.getHandlers().head.setLevel(Level.FINE) + } + ioLocal <- IOLocal(null: Context) + storage = new IOLocalContextStorage(ioLocal) + _ <- IO(ContextStorage.addWrapper(_ => storage)) + ctx = Context.root() + _ = IOLocals.set(ioLocal, Context.root()) + _ <- Resource + .make(IO(ctx.`with`(key, "hello").makeCurrent()))(scope => + IO(scope.close()) + ) + .surround(printKey) + _ <- printKey + } yield () +} diff --git a/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala b/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala index b4cf1f6b2..77adcf39a 100644 --- a/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala +++ b/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala @@ -1,36 +1,24 @@ package org.typelevel.otel4s.java import cats.effect.IOLocal -import cats.effect.LiftIO -import cats.effect.std.Dispatcher +import cats.effect.unsafe.IOLocals import io.opentelemetry.context.Context import io.opentelemetry.context.ContextStorage import io.opentelemetry.context.Scope -class IOLocalContextStorage[F[_]: LiftIO]( - dispatcher: Dispatcher[F], +class IOLocalContextStorage( ioLocal: IOLocal[Context] ) extends ContextStorage { - override def attach(toAttach: Context): Scope = - dispatcher.unsafeRunSync( - currentOrRoot - .flatMap { old => - ioLocal - .set(toAttach) - .as(new Scope { - def close() = dispatcher.unsafeRunSync(ioLocal.set(old).to[F]) - }) - } - .to[F] - ) - - override def current(): Context = { - dispatcher.unsafeRunSync(currentOrRoot.to[F]) + override def attach(toAttach: Context): Scope = { + val previous = current() + IOLocals.set(ioLocal, toAttach) + new Scope { + def close() = IOLocals.set(ioLocal, previous) + } } - private def currentOrRoot = ioLocal.get.map { - case null => Context.root() - case ctx => ctx - } + override def current(): Context = + IOLocals.get(ioLocal) + } diff --git a/java/common/src/test/scala/org/typelevel/otel4s/java/ContextStorageSuite.scala b/java/common/src/test/scala/org/typelevel/otel4s/java/ContextStorageSuite.scala deleted file mode 100644 index 7c71b26e1..000000000 --- a/java/common/src/test/scala/org/typelevel/otel4s/java/ContextStorageSuite.scala +++ /dev/null @@ -1,43 +0,0 @@ -package org.typelevel.otel4s.java - -import cats.effect.IO -import cats.effect.IOLocal -import cats.effect.Resource -import cats.effect.std.Dispatcher -import io.opentelemetry.context.Context -import io.opentelemetry.context.ContextKey -import io.opentelemetry.context.ContextStorage -import java.util.logging._ - -object Run extends cats.effect.IOApp.Simple { - - val key = ContextKey.named[String]("test") - - def run = - Dispatcher.parallel[IO].use { dispatcher => - for { - _ <- IO { - val rootLog = Logger.getLogger("") - rootLog.setLevel(Level.FINE) - rootLog.getHandlers().head.setLevel(Level.FINE) - } - ioLocal <- IOLocal(null: Context) - storage = new IOLocalContextStorage(dispatcher, ioLocal) - _ <- IO(ContextStorage.addWrapper(_ => storage)) - _ <- ioLocal.set(null) - _ <- IO.println(ContextStorage.get().getClass) - ctx = Context.root() - _ <- Resource - .make(IO(ctx.`with`(key, "hello").makeCurrent()))(scope => - IO(scope.close()) - ) - .surround { - for { - key <- IO(Context.current()) - _ <- IO.println(key) - } yield () - } - _ <- IO(Option(Context.current().get(key))).flatMap(v => IO.println(v)) - } yield () - } -} From 5f460eec0f5c88f5ba552ef5a4b6ab19d25b8b24 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Thu, 18 May 2023 17:45:21 -0400 Subject: [PATCH 03/15] Apply suggestions from code review Co-authored-by: Arman Bilge --- build.sbt | 2 +- examples/src/main/scala/ContextStorageExample.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index f185752e1..b6ce8cbb6 100644 --- a/build.sbt +++ b/build.sbt @@ -241,7 +241,7 @@ lazy val examples = project ), run / fork := true, javaOptions += "-Dotel.java.global-autoconfigure.enabled=true", - javaOptions += "-Dcats.effect.tracing.dumpLocals", + javaOptions += "-Dcats.effect.tracing.dumpLocals=true", envVars ++= Map( "OTEL_PROPAGATORS" -> "b3multi", "OTEL_SERVICE_NAME" -> "Trace Example" diff --git a/examples/src/main/scala/ContextStorageExample.scala b/examples/src/main/scala/ContextStorageExample.scala index 5f786edfc..8967b49c8 100644 --- a/examples/src/main/scala/ContextStorageExample.scala +++ b/examples/src/main/scala/ContextStorageExample.scala @@ -27,7 +27,7 @@ object ContextStorageExample extends IOApp.Simple { storage = new IOLocalContextStorage(ioLocal) _ <- IO(ContextStorage.addWrapper(_ => storage)) ctx = Context.root() - _ = IOLocals.set(ioLocal, Context.root()) + _ <- ioLocal.set(Context.root()) _ <- Resource .make(IO(ctx.`with`(key, "hello").makeCurrent()))(scope => IO(scope.close()) From e522ac8117230cc909c7b24d51c984f29557d811 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Thu, 18 May 2023 17:46:48 -0400 Subject: [PATCH 04/15] Get latest cats-effect branch --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index b6ce8cbb6..f3222e416 100644 --- a/build.sbt +++ b/build.sbt @@ -26,7 +26,7 @@ ThisBuild / crossScalaVersions := Seq(Scala213, "3.2.2") ThisBuild / scalaVersion := Scala213 // the default Scala val CatsVersion = "2.9.0" -val CatsEffectVersion = "3.5.0-30-0a69caf" +val CatsEffectVersion = "3.5.0-31-2775064" val CatsMtlVersion = "1.3.1" val FS2Version = "3.6.1" val MUnitVersion = "1.0.0-M7" From d9a1079206b82ba6f8c4ae8a8072cbfb2776d49c Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Thu, 18 May 2023 17:49:17 -0400 Subject: [PATCH 05/15] Fine, have your headers --- .../src/main/scala/ContextStorageExample.scala | 16 ++++++++++++++++ .../otel4s/java/IOLocalContextStorage.scala | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/examples/src/main/scala/ContextStorageExample.scala b/examples/src/main/scala/ContextStorageExample.scala index 8967b49c8..f141ed0f0 100644 --- a/examples/src/main/scala/ContextStorageExample.scala +++ b/examples/src/main/scala/ContextStorageExample.scala @@ -1,3 +1,19 @@ +/* + * 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. + */ + import cats.effect.IO import cats.effect.IOApp import cats.effect.IOLocal diff --git a/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala b/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala index 77adcf39a..a2c46f9cf 100644 --- a/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala +++ b/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala @@ -1,3 +1,19 @@ +/* + * 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.java import cats.effect.IOLocal From c4410ab7a53c98c7fb53e42f9c346eab2f44398b Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Thu, 18 May 2023 23:33:51 -0400 Subject: [PATCH 06/15] Unused import --- examples/src/main/scala/ContextStorageExample.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/src/main/scala/ContextStorageExample.scala b/examples/src/main/scala/ContextStorageExample.scala index f141ed0f0..3e6ab0ff9 100644 --- a/examples/src/main/scala/ContextStorageExample.scala +++ b/examples/src/main/scala/ContextStorageExample.scala @@ -18,7 +18,6 @@ import cats.effect.IO import cats.effect.IOApp import cats.effect.IOLocal import cats.effect.Resource -import cats.effect.unsafe.IOLocals import io.opentelemetry.context.Context import io.opentelemetry.context.ContextKey import io.opentelemetry.context.ContextStorage From 6b542aaeecae0e7fb49af7fbe303c933d96c774d Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Fri, 19 May 2023 00:09:01 -0400 Subject: [PATCH 07/15] Get testkit out of main dependenecies --- build.sbt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index f3222e416..2f79c0f78 100644 --- a/build.sbt +++ b/build.sbt @@ -150,7 +150,7 @@ lazy val testkit = crossProject(JVMPlatform) lazy val `java-common` = project .in(file("java/common")) .enablePlugins(BuildInfoPlugin) - .dependsOn(`core-common`.jvm, `testkit-common`.jvm) + .dependsOn(`core-common`.jvm, `testkit-common`.jvm % Test) .settings( name := "otel4s-java-common", libraryDependencies ++= Seq( @@ -168,7 +168,7 @@ lazy val `java-common` = project lazy val `java-metrics` = project .in(file("java/metrics")) - .dependsOn(`java-common`, `core-metrics`.jvm, `testkit-metrics`.jvm) + .dependsOn(`java-common`, `core-metrics`.jvm, `testkit-metrics`.jvm % Test) .settings(munitDependencies) .settings( name := "otel4s-java-metrics", @@ -222,7 +222,7 @@ lazy val benchmarks = project .enablePlugins(NoPublishPlugin) .enablePlugins(JmhPlugin) .in(file("benchmarks")) - .dependsOn(core.jvm, java) + .dependsOn(core.jvm, java, `testkit-metrics`.jvm) .settings( name := "otel4s-benchmarks" ) From b6a75a010de68e1218d2b03fa513e9e7349eb2f3 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Fri, 19 May 2023 00:09:22 -0400 Subject: [PATCH 08/15] Attmept at a ContextStorageProvider SPI --- .../main/scala/ContextStorageExample.scala | 13 +++---- ...entelemetry.context.ContextStorageProvider | 1 + .../otel4s/java/IOLocalContextStorage.scala | 9 ++--- .../java/IOLocalContextStorageProvider.scala | 36 +++++++++++++++++++ 4 files changed, 47 insertions(+), 12 deletions(-) create mode 100644 java/common/src/main/resources/META-INF/services/io.opentelemetry.context.ContextStorageProvider create mode 100644 java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorageProvider.scala diff --git a/examples/src/main/scala/ContextStorageExample.scala b/examples/src/main/scala/ContextStorageExample.scala index 3e6ab0ff9..5c905dca4 100644 --- a/examples/src/main/scala/ContextStorageExample.scala +++ b/examples/src/main/scala/ContextStorageExample.scala @@ -16,12 +16,12 @@ import cats.effect.IO import cats.effect.IOApp -import cats.effect.IOLocal import cats.effect.Resource import io.opentelemetry.context.Context import io.opentelemetry.context.ContextKey import io.opentelemetry.context.ContextStorage -import org.typelevel.otel4s.java.IOLocalContextStorage +import org.typelevel.otel4s.java.IOLocalContextStorageProvider + import java.util.logging._ object ContextStorageExample extends IOApp.Simple { @@ -38,13 +38,10 @@ object ContextStorageExample extends IOApp.Simple { rootLog.setLevel(Level.FINE) rootLog.getHandlers().head.setLevel(Level.FINE) } - ioLocal <- IOLocal(null: Context) - storage = new IOLocalContextStorage(ioLocal) - _ <- IO(ContextStorage.addWrapper(_ => storage)) - ctx = Context.root() - _ <- ioLocal.set(Context.root()) + _ <- IOLocalContextStorageProvider.localContext.set(Context.root()) + _ <- IO.println(ContextStorage.get.getClass()) _ <- Resource - .make(IO(ctx.`with`(key, "hello").makeCurrent()))(scope => + .make(IO(Context.root().`with`(key, "hello").makeCurrent()))(scope => IO(scope.close()) ) .surround(printKey) diff --git a/java/common/src/main/resources/META-INF/services/io.opentelemetry.context.ContextStorageProvider b/java/common/src/main/resources/META-INF/services/io.opentelemetry.context.ContextStorageProvider new file mode 100644 index 000000000..aeea4e705 --- /dev/null +++ b/java/common/src/main/resources/META-INF/services/io.opentelemetry.context.ContextStorageProvider @@ -0,0 +1 @@ +org.typelevel.otel4s.java.IOLocalContextStorageProvider \ No newline at end of file diff --git a/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala b/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala index a2c46f9cf..0096ae32a 100644 --- a/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala +++ b/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala @@ -16,6 +16,7 @@ package org.typelevel.otel4s.java +import cats.Eval import cats.effect.IOLocal import cats.effect.unsafe.IOLocals import io.opentelemetry.context.Context @@ -23,18 +24,18 @@ import io.opentelemetry.context.ContextStorage import io.opentelemetry.context.Scope class IOLocalContextStorage( - ioLocal: IOLocal[Context] + ioLocal: Eval[IOLocal[Context]] ) extends ContextStorage { override def attach(toAttach: Context): Scope = { val previous = current() - IOLocals.set(ioLocal, toAttach) + IOLocals.set(ioLocal.value, toAttach) new Scope { - def close() = IOLocals.set(ioLocal, previous) + def close() = IOLocals.set(ioLocal.value, previous) } } override def current(): Context = - IOLocals.get(ioLocal) + IOLocals.get(ioLocal.value) } diff --git a/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorageProvider.scala b/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorageProvider.scala new file mode 100644 index 000000000..6d1ecf678 --- /dev/null +++ b/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorageProvider.scala @@ -0,0 +1,36 @@ +/* + * 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.java + +import cats.Eval +import cats.effect.IOLocal +import cats.effect.unsafe.implicits.global +import io.opentelemetry.context.Context +import io.opentelemetry.context.ContextStorage +import io.opentelemetry.context.ContextStorageProvider + +object IOLocalContextStorageProvider { + val localContext: IOLocal[Context] = + IOLocal[Context](Context.root()).unsafeRunSync() +} + +class IOLocalContextStorageProvider extends ContextStorageProvider { + def get(): ContextStorage = + new IOLocalContextStorage( + Eval.later(IOLocalContextStorageProvider.localContext) + ) +} From 978ccefc2a6a10a681a22cb57f35694bc5eaba6c Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Fri, 19 May 2023 09:20:37 -0400 Subject: [PATCH 09/15] Use syncStep to initialize the local context --- .../java/IOLocalContextStorageProvider.scala | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorageProvider.scala b/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorageProvider.scala index 6d1ecf678..062b2e116 100644 --- a/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorageProvider.scala +++ b/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorageProvider.scala @@ -18,14 +18,24 @@ package org.typelevel.otel4s.java import cats.Eval import cats.effect.IOLocal -import cats.effect.unsafe.implicits.global +import cats.effect.SyncIO +import cats.syntax.all._ import io.opentelemetry.context.Context import io.opentelemetry.context.ContextStorage import io.opentelemetry.context.ContextStorageProvider object IOLocalContextStorageProvider { val localContext: IOLocal[Context] = - IOLocal[Context](Context.root()).unsafeRunSync() + IOLocal[Context](Context.root()) + .syncStep(100) + .flatMap( + _.leftMap(_ => + new Error( + "Failed to initialize the local context of the IOLocalContextStorageProvider." + ) + ).liftTo[SyncIO] + ) + .unsafeRunSync() } class IOLocalContextStorageProvider extends ContextStorageProvider { From 99137043b59ea1febca24f2058b0344481b9fde1 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Mon, 22 May 2023 12:02:25 -0400 Subject: [PATCH 10/15] Update Cats Effect snapshot --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 2f79c0f78..560756117 100644 --- a/build.sbt +++ b/build.sbt @@ -26,7 +26,7 @@ ThisBuild / crossScalaVersions := Seq(Scala213, "3.2.2") ThisBuild / scalaVersion := Scala213 // the default Scala val CatsVersion = "2.9.0" -val CatsEffectVersion = "3.5.0-31-2775064" +val CatsEffectVersion = "3.5.0-34-2cf72a5" val CatsMtlVersion = "1.3.1" val FS2Version = "3.6.1" val MUnitVersion = "1.0.0-M7" From b6c2b0ff31727e530b3f4675376964f12b7e04a3 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Mon, 22 May 2023 12:03:20 -0400 Subject: [PATCH 11/15] Remove direct reference to IOLocalContextStorageProvider --- examples/src/main/scala/ContextStorageExample.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/src/main/scala/ContextStorageExample.scala b/examples/src/main/scala/ContextStorageExample.scala index 5c905dca4..de155209f 100644 --- a/examples/src/main/scala/ContextStorageExample.scala +++ b/examples/src/main/scala/ContextStorageExample.scala @@ -20,7 +20,6 @@ import cats.effect.Resource import io.opentelemetry.context.Context import io.opentelemetry.context.ContextKey import io.opentelemetry.context.ContextStorage -import org.typelevel.otel4s.java.IOLocalContextStorageProvider import java.util.logging._ @@ -38,7 +37,6 @@ object ContextStorageExample extends IOApp.Simple { rootLog.setLevel(Level.FINE) rootLog.getHandlers().head.setLevel(Level.FINE) } - _ <- IOLocalContextStorageProvider.localContext.set(Context.root()) _ <- IO.println(ContextStorage.get.getClass()) _ <- Resource .make(IO(Context.root().`with`(key, "hello").makeCurrent()))(scope => From 8c311369895f7e674444418b76ab23ada46d0cb0 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Mon, 22 May 2023 23:29:22 -0400 Subject: [PATCH 12/15] Unbox IOLocal Co-authored-by: Arman Bilge --- .../org/typelevel/otel4s/java/IOLocalContextStorage.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala b/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala index 0096ae32a..ea346bf7d 100644 --- a/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala +++ b/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala @@ -24,9 +24,9 @@ import io.opentelemetry.context.ContextStorage import io.opentelemetry.context.Scope class IOLocalContextStorage( - ioLocal: Eval[IOLocal[Context]] + _ioLocal: => IOLocal[Context] ) extends ContextStorage { - + private[this] lazy val ioLocal = _ioLocal override def attach(toAttach: Context): Scope = { val previous = current() IOLocals.set(ioLocal.value, toAttach) From fe7259e6dc3f2af3019912b8d786ba484b052692 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Wed, 28 Jun 2023 22:28:32 +0000 Subject: [PATCH 13/15] Fix compile, update to CE snapshot --- build.sbt | 4 ++-- .../org/typelevel/otel4s/java/IOLocalContextStorage.scala | 7 +++---- .../otel4s/java/IOLocalContextStorageProvider.scala | 5 +---- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/build.sbt b/build.sbt index c4954329c..07ef6dec3 100644 --- a/build.sbt +++ b/build.sbt @@ -26,7 +26,7 @@ ThisBuild / crossScalaVersions := Seq(Scala213, "3.3.0") ThisBuild / scalaVersion := Scala213 // the default Scala val CatsVersion = "2.9.0" -val CatsEffectVersion = "3.5.0-34-2cf72a5" +val CatsEffectVersion = "3.6-02a43a6" val CatsMtlVersion = "1.3.1" val DisciplineMUnitVersion = "2.0.0-M3" val FS2Version = "3.7.0" @@ -265,7 +265,7 @@ lazy val examples = project ), run / fork := true, javaOptions += "-Dotel.java.global-autoconfigure.enabled=true", - javaOptions += "-Dcats.effect.tracing.dumpLocals=true", + javaOptions += "-Dcats.effect.ioLocalPropagation=true", envVars ++= Map( "OTEL_PROPAGATORS" -> "b3multi", "OTEL_SERVICE_NAME" -> "Trace Example" diff --git a/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala b/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala index ea346bf7d..e18a80b38 100644 --- a/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala +++ b/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala @@ -16,7 +16,6 @@ package org.typelevel.otel4s.java -import cats.Eval import cats.effect.IOLocal import cats.effect.unsafe.IOLocals import io.opentelemetry.context.Context @@ -29,13 +28,13 @@ class IOLocalContextStorage( private[this] lazy val ioLocal = _ioLocal override def attach(toAttach: Context): Scope = { val previous = current() - IOLocals.set(ioLocal.value, toAttach) + IOLocals.set(ioLocal, toAttach) new Scope { - def close() = IOLocals.set(ioLocal.value, previous) + def close() = IOLocals.set(ioLocal, previous) } } override def current(): Context = - IOLocals.get(ioLocal.value) + IOLocals.get(ioLocal) } diff --git a/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorageProvider.scala b/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorageProvider.scala index 062b2e116..d328674d2 100644 --- a/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorageProvider.scala +++ b/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorageProvider.scala @@ -16,7 +16,6 @@ package org.typelevel.otel4s.java -import cats.Eval import cats.effect.IOLocal import cats.effect.SyncIO import cats.syntax.all._ @@ -40,7 +39,5 @@ object IOLocalContextStorageProvider { class IOLocalContextStorageProvider extends ContextStorageProvider { def get(): ContextStorage = - new IOLocalContextStorage( - Eval.later(IOLocalContextStorageProvider.localContext) - ) + new IOLocalContextStorage(IOLocalContextStorageProvider.localContext) } From e60f79316b3afa46378b83cc15e4d933439f494f Mon Sep 17 00:00:00 2001 From: Marissa Date: Thu, 31 Aug 2023 06:17:51 -0400 Subject: [PATCH 14/15] Finish implementing `IOLocalContextStorage` Finish implementing `IOLocalContextStorage` and `IOLocalContextStorageProvider` for sharing context modifications between Java and Scala instrumentation. --- .github/workflows/ci.yml | 4 +- build.sbt | 21 +- ...entelemetry.context.ContextStorageProvider | 1 - .../otel4s/java/IOLocalContextStorage.scala | 40 --- .../otel4s/java/context/Context.scala | 2 +- ...entelemetry.context.ContextStorageProvider | 1 + .../otel4s/java/IOLocalContextStorage.scala | 92 ++++++ .../java/IOLocalContextStorageProvider.scala | 9 +- .../java/IOLocalContextStorageSuite.scala | 264 ++++++++++++++++++ 9 files changed, 385 insertions(+), 49 deletions(-) delete mode 100644 java/common/src/main/resources/META-INF/services/io.opentelemetry.context.ContextStorageProvider delete mode 100644 java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala create mode 100644 java/context-storage/src/main/resources/META-INF/services/io.opentelemetry.context.ContextStorageProvider create mode 100644 java/context-storage/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala rename java/{common => context-storage}/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorageProvider.scala (81%) create mode 100644 java/context-storage/src/test/scala/org/typelevel/otel4s/java/IOLocalContextStorageSuite.scala diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14b53176b..4113c48f9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,11 +84,11 @@ jobs: - name: Make target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: mkdir -p testkit/metrics/jvm/target java/metrics/target testkit/common/jvm/target core/trace/.js/target semconv/.jvm/target core/common/.jvm/target java/trace/target unidocs/target core/metrics/.native/target core/all/.native/target core/metrics/.jvm/target core/all/.js/target java/all/target java/common/target core/metrics/.js/target core/all/.jvm/target core/trace/.native/target semconv/.js/target core/trace/.jvm/target core/common/.native/target core/common/.js/target semconv/.native/target testkit/all/jvm/target project/target + run: mkdir -p testkit/metrics/jvm/target java/metrics/target testkit/common/jvm/target core/trace/.js/target semconv/.jvm/target core/common/.jvm/target java/trace/target unidocs/target core/metrics/.native/target core/all/.native/target core/metrics/.jvm/target java/context-storage/target core/all/.js/target java/all/target java/common/target core/metrics/.js/target core/all/.jvm/target core/trace/.native/target semconv/.js/target core/trace/.jvm/target core/common/.native/target core/common/.js/target semconv/.native/target testkit/all/jvm/target project/target - name: Compress target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: tar cf targets.tar testkit/metrics/jvm/target java/metrics/target testkit/common/jvm/target core/trace/.js/target semconv/.jvm/target core/common/.jvm/target java/trace/target unidocs/target core/metrics/.native/target core/all/.native/target core/metrics/.jvm/target core/all/.js/target java/all/target java/common/target core/metrics/.js/target core/all/.jvm/target core/trace/.native/target semconv/.js/target core/trace/.jvm/target core/common/.native/target core/common/.js/target semconv/.native/target testkit/all/jvm/target project/target + run: tar cf targets.tar testkit/metrics/jvm/target java/metrics/target testkit/common/jvm/target core/trace/.js/target semconv/.jvm/target core/common/.jvm/target java/trace/target unidocs/target core/metrics/.native/target core/all/.native/target core/metrics/.jvm/target java/context-storage/target core/all/.js/target java/all/target java/common/target core/metrics/.js/target core/all/.jvm/target core/trace/.native/target semconv/.js/target core/trace/.jvm/target core/common/.native/target core/common/.js/target semconv/.native/target testkit/all/jvm/target project/target - name: Upload target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') diff --git a/build.sbt b/build.sbt index 547448ab7..a08b8887a 100644 --- a/build.sbt +++ b/build.sbt @@ -67,6 +67,7 @@ lazy val root = tlCrossRootProject `testkit-metrics`, testkit, `java-common`, + `java-context-storage`, `java-metrics`, `java-trace`, java, @@ -288,9 +289,27 @@ lazy val `java-trace` = project ) .settings(scalafixSettings) +lazy val `java-context-storage` = project + .in(file("java/context-storage")) + .dependsOn(`java-common`) + .settings(munitDependencies) + .settings( + name := "otel4s-java-context-storage", + libraryDependencies ++= Seq( + "org.typelevel" %%% "cats-effect" % CatsEffectVersion, + "org.typelevel" %%% "cats-effect-testkit" % CatsEffectVersion % Test, + ), + Test / javaOptions ++= Seq( + "-Dotel.java.global-autoconfigure.enabled=true", + "-Dcats.effect.ioLocalPropagation=true", + ), + Test / fork := true, + ) + .settings(scalafixSettings) + lazy val java = project .in(file("java/all")) - .dependsOn(core.jvm, `java-metrics`, `java-trace`) + .dependsOn(core.jvm, `java-metrics`, `java-trace`, `java-context-storage`) .settings( name := "otel4s-java", libraryDependencies ++= Seq( diff --git a/java/common/src/main/resources/META-INF/services/io.opentelemetry.context.ContextStorageProvider b/java/common/src/main/resources/META-INF/services/io.opentelemetry.context.ContextStorageProvider deleted file mode 100644 index aeea4e705..000000000 --- a/java/common/src/main/resources/META-INF/services/io.opentelemetry.context.ContextStorageProvider +++ /dev/null @@ -1 +0,0 @@ -org.typelevel.otel4s.java.IOLocalContextStorageProvider \ No newline at end of file diff --git a/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala b/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala deleted file mode 100644 index e18a80b38..000000000 --- a/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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.java - -import cats.effect.IOLocal -import cats.effect.unsafe.IOLocals -import io.opentelemetry.context.Context -import io.opentelemetry.context.ContextStorage -import io.opentelemetry.context.Scope - -class IOLocalContextStorage( - _ioLocal: => IOLocal[Context] -) extends ContextStorage { - private[this] lazy val ioLocal = _ioLocal - override def attach(toAttach: Context): Scope = { - val previous = current() - IOLocals.set(ioLocal, toAttach) - new Scope { - def close() = IOLocals.set(ioLocal, previous) - } - } - - override def current(): Context = - IOLocals.get(ioLocal) - -} diff --git a/java/common/src/main/scala/org/typelevel/otel4s/java/context/Context.scala b/java/common/src/main/scala/org/typelevel/otel4s/java/context/Context.scala index 6c224b1f2..d3996fb76 100644 --- a/java/common/src/main/scala/org/typelevel/otel4s/java/context/Context.scala +++ b/java/common/src/main/scala/org/typelevel/otel4s/java/context/Context.scala @@ -95,7 +95,7 @@ object Context { } /** The root [[`Context`]], from which all other contexts are derived. */ - val root: Context = wrap(JContext.root()) + lazy val root: Context = wrap(JContext.root()) implicit object Contextual extends context.Contextual[Context] { type Key[A] = Context.Key[A] diff --git a/java/context-storage/src/main/resources/META-INF/services/io.opentelemetry.context.ContextStorageProvider b/java/context-storage/src/main/resources/META-INF/services/io.opentelemetry.context.ContextStorageProvider new file mode 100644 index 000000000..6b56f5271 --- /dev/null +++ b/java/context-storage/src/main/resources/META-INF/services/io.opentelemetry.context.ContextStorageProvider @@ -0,0 +1 @@ +org.typelevel.otel4s.java.IOLocalContextStorageProvider diff --git a/java/context-storage/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala b/java/context-storage/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala new file mode 100644 index 000000000..ed767622e --- /dev/null +++ b/java/context-storage/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.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.java + +import cats.effect.IOLocal +import cats.effect.LiftIO +import cats.effect.MonadCancelThrow +import cats.effect.unsafe.IOLocals +import io.opentelemetry.context.{Context => JContext} +import io.opentelemetry.context.ContextStorage +import io.opentelemetry.context.Scope +import org.typelevel.otel4s.java.context.Context +import org.typelevel.otel4s.java.context.LocalContext +import org.typelevel.otel4s.java.instances._ + +/** A `ContextStorage` backed by an [[cats.effect.IOLocal `IOLocal`]] of a + * [[`Context`]] that also provides [[cats.mtl.Local `Local`]] instances that + * reflect the state of the backing `IOLocal`. Usage of `Local` and + * `ContextStorage` methods will be consistent and stay in sync as long as + * effects are threaded properly. + */ +class IOLocalContextStorage(_ioLocal: () => IOLocal[Context]) + extends ContextStorage { + private[this] implicit lazy val ioLocal: IOLocal[Context] = _ioLocal() + + @inline private[this] def unsafeCurrent: Context = + IOLocals.get(ioLocal) + + override def attach(toAttach: JContext): Scope = { + val previous = unsafeCurrent + IOLocals.set(ioLocal, Context.wrap(toAttach)) + () => IOLocals.set(ioLocal, previous) + } + + override def current(): JContext = + unsafeCurrent.underlying + + /** @return + * a [[cats.mtl.Local `Local`]] of a [[`Context`]] that reflects the state + * of the backing `IOLocal` + */ + def local[F[_]: MonadCancelThrow: LiftIO]: LocalContext[F] = implicitly +} + +object IOLocalContextStorage { + + /** Returns a [[cats.mtl.Local `Local`]] of a [[`Context`]] if an + * [[`IOLocalContextStorage`]] is configured to be used as the + * `ContextStorage` for the Java otel library. + * + * Raises an exception if an [[`IOLocalContextStorage`]] is __not__ + * configured to be used as the `ContextStorage` for the Java otel library, + * or if [[cats.effect.IOLocal `IOLocal`]] propagation is not enabled. + */ + def providedLocal[F[_]: LiftIO](implicit + F: MonadCancelThrow[F] + ): F[LocalContext[F]] = + ContextStorage.get() match { + case storage: IOLocalContextStorage => + // TODO: check `IOLocals.arePropagating` instead once our dependencies + // are updated + if (java.lang.Boolean.getBoolean("cats.effect.ioLocalPropagation")) { + F.pure(storage.local) + } else { + F.raiseError( + new IllegalStateException( + "IOLocal propagation must be enabled with: -Dcats.effect.ioLocalPropagation=true" + ) + ) + } + case _ => + F.raiseError( + new IllegalStateException( + "IOLocalContextStorage is not configured for use as the ContextStorageProvider" + ) + ) + } +} diff --git a/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorageProvider.scala b/java/context-storage/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorageProvider.scala similarity index 81% rename from java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorageProvider.scala rename to java/context-storage/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorageProvider.scala index d328674d2..0c578051b 100644 --- a/java/common/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorageProvider.scala +++ b/java/context-storage/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorageProvider.scala @@ -19,13 +19,13 @@ package org.typelevel.otel4s.java import cats.effect.IOLocal import cats.effect.SyncIO import cats.syntax.all._ -import io.opentelemetry.context.Context import io.opentelemetry.context.ContextStorage import io.opentelemetry.context.ContextStorageProvider +import org.typelevel.otel4s.java.context.Context object IOLocalContextStorageProvider { - val localContext: IOLocal[Context] = - IOLocal[Context](Context.root()) + private lazy val localContext: IOLocal[Context] = + IOLocal[Context](Context.root) .syncStep(100) .flatMap( _.leftMap(_ => @@ -37,7 +37,8 @@ object IOLocalContextStorageProvider { .unsafeRunSync() } +/** SPI implementation for [[`IOLocalContextStorage`]]. */ class IOLocalContextStorageProvider extends ContextStorageProvider { def get(): ContextStorage = - new IOLocalContextStorage(IOLocalContextStorageProvider.localContext) + new IOLocalContextStorage(() => IOLocalContextStorageProvider.localContext) } diff --git a/java/context-storage/src/test/scala/org/typelevel/otel4s/java/IOLocalContextStorageSuite.scala b/java/context-storage/src/test/scala/org/typelevel/otel4s/java/IOLocalContextStorageSuite.scala new file mode 100644 index 000000000..b7611887e --- /dev/null +++ b/java/context-storage/src/test/scala/org/typelevel/otel4s/java/IOLocalContextStorageSuite.scala @@ -0,0 +1,264 @@ +/* + * 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.java + +import cats.effect.IO +import cats.effect.SyncIO +import io.opentelemetry.context.{Context => JContext} +import io.opentelemetry.context.ContextStorage +import munit.CatsEffectSuite +import munit.Location +import org.typelevel.otel4s.context.Key +import org.typelevel.otel4s.java.context.Context +import org.typelevel.otel4s.java.context.LocalContext + +import scala.util.Using + +class IOLocalContextStorageSuite extends CatsEffectSuite { + import IOLocalContextStorageSuite._ + + private val localF: IO[LocalContext[IO]] = + IOLocalContextStorage.providedLocal + + private def sCurrent[F[_]](implicit L: LocalContext[F]): F[Context] = + L.ask[Context] + private def jCurrent: JContext = JContext.current() + + private def usingModifiedCtx[A](f: JContext => JContext)(body: => A): A = + Using.resource(f(jCurrent).makeCurrent())(_ => body) + + private def localTest( + name: String + )(body: LocalContext[IO] => IO[Any])(implicit loc: Location): Unit = + test(name) { + for { + local <- localF + _ <- body(local) + } yield () + } + + // if this fails, the rest will almost certainly fail, + // and will be meaningless regardless + localTest("correctly configured") { implicit L => + for { + sCtx <- sCurrent + jCtx <- IO(jCurrent) + } yield { + // correct ContextStorage is configured + assertEquals( + ContextStorage.get().getClass: Any, + classOf[IOLocalContextStorage]: Any + ) + + // current is root + assertEquals(JContext.root(), Context.root.underlying) + assertEquals(jCtx, sCtx.underlying) + assertEquals(sCtx, Context.root) + assertEquals(jCtx, JContext.root()) + + // root is empty + assertEquals(sCtx.get(key1), None) + assertEquals(sCtx.get(key2), None) + assertEquals(Option(jCtx.get(key1)), None) + assertEquals(Option(jCtx.get(key2)), None) + } + } + + test("works as a Java-only ContextStorage") { + usingModifiedCtx(_.`with`(key1, "1")) { + assertEquals(Option(jCurrent.get(key1)), Some("1")) + assertEquals(Option(jCurrent.get(key2)), None) + + usingModifiedCtx(_.`with`(key2, 2)) { + assertEquals(Option(jCurrent.get(key1)), Some("1")) + assertEquals(Option(jCurrent.get(key2)), Some(2)) + + usingModifiedCtx(_ => JContext.root()) { + assertEquals(Option(jCurrent.get(key1)), None) + assertEquals(Option(jCurrent.get(key2)), None) + } + } + } + } + + localTest("works as a Scala-only Local") { implicit L => + doLocally(_.updated(key1, "1")) { + for { + _ <- doLocally(_.updated(key2, 2)) { + for { + _ <- doScoped(Context.root) { + for (ctx <- sCurrent) + yield { + assertEquals(ctx.get(key1), None) + assertEquals(ctx.get(key2), None) + } + } + ctx <- sCurrent + } yield { + assertEquals(ctx.get(key1), Some("1")) + assertEquals(ctx.get(key2), Some(2)) + } + } + ctx <- sCurrent + } yield { + assertEquals(ctx.get(key1), Some("1")) + assertEquals(ctx.get(key2), None) + } + } + } + + localTest("Scala with Java nested inside it") { implicit L => + doLocally(_.updated(key1, "1")) { + for { + _ <- IO { + usingModifiedCtx(_.`with`(key2, 2)) { + val sCtx = sCurrent.unsafeRunSync() + val jCtx = jCurrent + assertEquals(jCtx, sCtx.underlying) + assertEquals(sCtx.get(key1), Some("1")) + assertEquals(sCtx.get(key2), Some(2)) + assertEquals(Option(jCtx.get(key1)), Some("1")) + assertEquals(Option(jCtx.get(key2)), Some(2)) + } + } + sCtx <- sCurrent + jCtx <- IO(jCurrent) + } yield { + assertEquals(jCtx, sCtx.underlying) + assertEquals(sCtx.get(key1), Some("1")) + assertEquals(sCtx.get(key2), None) + assertEquals(Option(jCtx.get(key1)), Some("1")) + assertEquals(Option(jCtx.get(key2)), None) + } + } + } + + localTest("Java with Scala nested inside it") { implicit L => + IO { + usingModifiedCtx(_.`with`(key1, "1")) { + val sCtx = locally { + for { + _ <- doLocally(_.updated(key2, 2)) { + for { + sCtx <- sCurrent + jCtx <- IO(jCurrent) + } yield { + assertEquals(jCtx, sCtx.underlying) + assertEquals(sCtx.get(key1), Some("1")) + assertEquals(sCtx.get(key2), Some(2)) + assertEquals(Option(jCtx.get(key1)), Some("1")) + assertEquals(Option(jCtx.get(key2)), Some(2)) + } + } + ctx <- sCurrent + } yield ctx + }.unsafeRunSync() + val jCtx = jCurrent + assertEquals(jCtx, sCtx.underlying) + assertEquals(sCtx.get(key1), Some("1")) + assertEquals(sCtx.get(key2), None) + assertEquals(Option(jCtx.get(key1)), Some("1")) + assertEquals(Option(jCtx.get(key2)), None) + } + } + } + + localTest("lots of nesting") { implicit L => + doLocally(_.updated(key1, "1")) { + for { + _ <- IO { + usingModifiedCtx(_.`with`(key2, 2)) { + usingModifiedCtx(_.`with`(key1, "3")) { + val sCtx = locally { + for { + _ <- doLocally(_.updated(key2, 4)) { + for { + sCtx <- sCurrent + jCtx <- IO(jCurrent) + } yield { + assertEquals(jCtx, sCtx.underlying) + assertEquals(sCtx.get(key1), Some("3")) + assertEquals(sCtx.get(key2), Some(4)) + assertEquals(Option(jCtx.get(key1)), Some("3")) + assertEquals(Option(jCtx.get(key2)), Some(4)) + } + } + ctx <- sCurrent + } yield ctx + }.unsafeRunSync() + val jCtx = jCurrent + assertEquals(jCtx, sCtx.underlying) + assertEquals(sCtx.get(key1), Some("3")) + assertEquals(sCtx.get(key2), Some(2)) + assertEquals(Option(jCtx.get(key1)), Some("3")) + assertEquals(Option(jCtx.get(key2)), Some(2)) + } + val sCtx = locally { + for { + _ <- doScoped(Context.root) { + for { + sCtx <- sCurrent + jCtx <- IO(jCurrent) + } yield { + assertEquals(jCtx, sCtx.underlying) + assertEquals(sCtx.get(key1), None) + assertEquals(sCtx.get(key2), None) + assertEquals(Option(jCtx.get(key1)), None) + assertEquals(Option(jCtx.get(key2)), None) + } + } + ctx <- sCurrent + } yield ctx + }.unsafeRunSync() + val jCtx = jCurrent + assertEquals(jCtx, sCtx.underlying) + assertEquals(sCtx.get(key1), Some("1")) + assertEquals(sCtx.get(key2), Some(2)) + assertEquals(Option(jCtx.get(key1)), Some("1")) + assertEquals(Option(jCtx.get(key2)), Some(2)) + } + } + sCtx <- sCurrent + jCtx <- IO(jCurrent) + } yield { + assertEquals(jCtx, sCtx.underlying) + assertEquals(sCtx.get(key1), Some("1")) + assertEquals(sCtx.get(key2), None) + assertEquals(Option(jCtx.get(key1)), Some("1")) + assertEquals(Option(jCtx.get(key2)), None) + } + } + } +} + +object IOLocalContextStorageSuite { + private val keyProvider = Key.Provider[SyncIO, Context.Key] + val key1: Context.Key[String] = + keyProvider.uniqueKey[String]("key1").unsafeRunSync() + val key2: Context.Key[Int] = + keyProvider.uniqueKey[Int]("key2").unsafeRunSync() + + // `Local`'s methods have their argument lists in the an annoying order + def doLocally[F[_], A](f: Context => Context)(fa: F[A])(implicit + L: LocalContext[F] + ): F[A] = + L.local(fa)(f) + def doScoped[F[_], A](e: Context)(fa: F[A])(implicit + L: LocalContext[F] + ): F[A] = + L.scope(fa)(e) +} From 39ba75452b89226b6e07f655a84b3d81ee258ff0 Mon Sep 17 00:00:00 2001 From: Marissa Date: Wed, 4 Oct 2023 15:00:23 -0400 Subject: [PATCH 15/15] fixup! Finish implementing `IOLocalContextStorage` fix docs --- .../otel4s/java/IOLocalContextStorage.scala | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/java/context-storage/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala b/java/context-storage/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala index ed767622e..3e0edc1c6 100644 --- a/java/context-storage/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala +++ b/java/context-storage/src/main/scala/org/typelevel/otel4s/java/IOLocalContextStorage.scala @@ -28,10 +28,10 @@ import org.typelevel.otel4s.java.context.LocalContext import org.typelevel.otel4s.java.instances._ /** A `ContextStorage` backed by an [[cats.effect.IOLocal `IOLocal`]] of a - * [[`Context`]] that also provides [[cats.mtl.Local `Local`]] instances that - * reflect the state of the backing `IOLocal`. Usage of `Local` and - * `ContextStorage` methods will be consistent and stay in sync as long as - * effects are threaded properly. + * [[org.typelevel.otel4s.java.context.Context `Context`]] that also provides + * [[cats.mtl.Local `Local`]] instances that reflect the state of the backing + * `IOLocal`. Usage of `Local` and `ContextStorage` methods will be consistent + * and stay in sync as long as effects are threaded properly. */ class IOLocalContextStorage(_ioLocal: () => IOLocal[Context]) extends ContextStorage { @@ -50,15 +50,17 @@ class IOLocalContextStorage(_ioLocal: () => IOLocal[Context]) unsafeCurrent.underlying /** @return - * a [[cats.mtl.Local `Local`]] of a [[`Context`]] that reflects the state - * of the backing `IOLocal` + * a [[cats.mtl.Local `Local`]] of a + * [[org.typelevel.otel4s.java.context.Context `Context`]] that reflects + * the state of the backing `IOLocal` */ def local[F[_]: MonadCancelThrow: LiftIO]: LocalContext[F] = implicitly } object IOLocalContextStorage { - /** Returns a [[cats.mtl.Local `Local`]] of a [[`Context`]] if an + /** Returns a [[cats.mtl.Local `Local`]] of a + * [[org.typelevel.otel4s.java.context.Context `Context`]] if an * [[`IOLocalContextStorage`]] is configured to be used as the * `ContextStorage` for the Java otel library. *