From 3e6a850339d31e5d755805474fcc785332b09706 Mon Sep 17 00:00:00 2001 From: Marissa Date: Mon, 7 Aug 2023 12:21:42 -0400 Subject: [PATCH] Redesign context implementation Redesign context implementation to use a `Context` type class that supports arbitrary types for the context rather than specifically `Vault`. In particular, it supports different context implementations for different backends, and parameterises several types by a `C` parameter that refers to the type of the context. --- build.sbt | 5 +- .../scala/org/typelevel/otel4s/Otel4s.scala | 4 +- .../typelevel/otel4s/ContextPropagators.scala | 10 +- .../typelevel/otel4s/KindTransformer.scala | 4 +- .../typelevel/otel4s/TextMapPropagator.scala | 23 ++-- .../typelevel/otel4s/context/Contextual.scala | 82 +++++++++++++ .../org/typelevel/otel4s/context/Key.scala | 48 ++++++++ .../org/typelevel/otel4s/context/syntax.scala | 19 +++ .../otel4s/context/ContextSuite.scala | 52 +++++++++ .../otel4s/context/vault/VaultContext.scala | 82 +++++++++++++ .../org/typelevel/otel4s/metrics/Meter.scala | 2 +- .../metrics/ObservableInstrumentBuilder.scala | 2 +- .../otel4s/metrics/ObservableSuite.scala | 2 +- .../typelevel/otel4s/trace/SpanBuilder.scala | 4 +- .../typelevel/otel4s/trace/SpanContext.scala | 11 -- .../org/typelevel/otel4s/trace/SpanOps.scala | 9 +- .../org/typelevel/otel4s/trace/Tracer.scala | 2 +- examples/src/main/scala/KleisliExample.scala | 20 ++-- .../org/typelevel/otel4s/java/OtelJava.scala | 44 +++---- .../otel4s/java/ContextPropagatorsImpl.scala | 17 +-- .../otel4s/java/TextMapPropagatorImpl.scala | 23 ++-- .../otel4s/java/context/Context.scala | 109 ++++++++++++++++++ .../otel4s/java}/context/package.scala | 7 +- .../otel4s/java/ContextConversions.scala | 50 -------- .../typelevel/otel4s/java/trace/Scope.scala | 45 -------- .../otel4s/java/trace/SpanBuilderImpl.scala | 48 +++----- .../otel4s/java/trace/SpanRunner.scala | 21 ++-- .../otel4s/java/trace/TraceScope.scala | 88 -------------- .../otel4s/java/trace/TracerBuilderImpl.scala | 11 +- .../otel4s/java/trace/TracerImpl.scala | 56 ++++----- .../java/trace/TracerProviderImpl.scala | 23 ++-- .../typelevel/otel4s/java/trace/Traces.scala | 17 ++- .../otel4s/java/trace/TracerSuite.scala | 12 +- 33 files changed, 561 insertions(+), 391 deletions(-) create mode 100644 core/common/src/main/scala/org/typelevel/otel4s/context/Contextual.scala create mode 100644 core/common/src/main/scala/org/typelevel/otel4s/context/Key.scala create mode 100644 core/common/src/main/scala/org/typelevel/otel4s/context/syntax.scala create mode 100644 core/common/src/test/scala/org/typelevel/otel4s/context/ContextSuite.scala create mode 100644 core/common/src/test/scala/org/typelevel/otel4s/context/vault/VaultContext.scala create mode 100644 java/common/src/main/scala/org/typelevel/otel4s/java/context/Context.scala rename {core/common/src/main/scala/org/typelevel/otel4s => java/common/src/main/scala/org/typelevel/otel4s/java}/context/package.scala (82%) delete mode 100644 java/trace/src/main/scala/org/typelevel/otel4s/java/ContextConversions.scala delete mode 100644 java/trace/src/main/scala/org/typelevel/otel4s/java/trace/Scope.scala delete mode 100644 java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TraceScope.scala diff --git a/build.sbt b/build.sbt index d7629c216..099dd8c35 100644 --- a/build.sbt +++ b/build.sbt @@ -84,8 +84,9 @@ lazy val `core-common` = crossProject(JVMPlatform, JSPlatform, NativePlatform) name := "otel4s-core-common", libraryDependencies ++= Seq( "org.typelevel" %%% "cats-core" % CatsVersion, + "org.typelevel" %%% "cats-effect-kernel" % CatsEffectVersion, "org.typelevel" %%% "cats-mtl" % CatsMtlVersion, - "org.typelevel" %%% "vault" % VaultVersion, + "org.typelevel" %%% "vault" % VaultVersion % Test, "org.typelevel" %%% "cats-laws" % CatsVersion % Test, "org.typelevel" %%% "discipline-munit" % DisciplineMUnitVersion % Test, "org.scalameta" %%% "munit" % MUnitVersion % Test, @@ -225,7 +226,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.typelevel" %%% "discipline-munit" % MUnitDisciplineVersion % Test, 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 e91ea0d62..bc19940b6 100644 --- a/core/all/src/main/scala/org/typelevel/otel4s/Otel4s.scala +++ b/core/all/src/main/scala/org/typelevel/otel4s/Otel4s.scala @@ -20,7 +20,9 @@ import org.typelevel.otel4s.metrics.MeterProvider import org.typelevel.otel4s.trace.TracerProvider trait Otel4s[F[_]] { - def propagators: ContextPropagators[F] + type Ctx + + def propagators: ContextPropagators[F, Ctx] /** A registry for creating named meters. */ diff --git a/core/common/src/main/scala/org/typelevel/otel4s/ContextPropagators.scala b/core/common/src/main/scala/org/typelevel/otel4s/ContextPropagators.scala index d77e003b4..f1978ddce 100644 --- a/core/common/src/main/scala/org/typelevel/otel4s/ContextPropagators.scala +++ b/core/common/src/main/scala/org/typelevel/otel4s/ContextPropagators.scala @@ -18,8 +18,8 @@ package org.typelevel.otel4s import cats.Applicative -trait ContextPropagators[F[_]] { - def textMapPropagator: TextMapPropagator[F] +trait ContextPropagators[F[_], Ctx] { + def textMapPropagator: TextMapPropagator[F, Ctx] } object ContextPropagators { @@ -31,9 +31,9 @@ object ContextPropagators { * @tparam F * the higher-kinded type of a polymorphic effect */ - def noop[F[_]: Applicative]: ContextPropagators[F] = - new ContextPropagators[F] { - def textMapPropagator: TextMapPropagator[F] = + def noop[F[_]: Applicative, Ctx]: ContextPropagators[F, Ctx] = + new ContextPropagators[F, Ctx] { + def textMapPropagator: TextMapPropagator[F, Ctx] = TextMapPropagator.noop } } diff --git a/core/common/src/main/scala/org/typelevel/otel4s/KindTransformer.scala b/core/common/src/main/scala/org/typelevel/otel4s/KindTransformer.scala index a48b0cfc6..e6c553e4b 100644 --- a/core/common/src/main/scala/org/typelevel/otel4s/KindTransformer.scala +++ b/core/common/src/main/scala/org/typelevel/otel4s/KindTransformer.scala @@ -23,8 +23,8 @@ import cats.data.IorT import cats.data.Kleisli import cats.data.OptionT import cats.data.StateT -import cats.effect.MonadCancelThrow -import cats.effect.Resource +import cats.effect.kernel.MonadCancelThrow +import cats.effect.kernel.Resource import cats.~> /** A utility for transforming the higher-kinded type `F` to another diff --git a/core/common/src/main/scala/org/typelevel/otel4s/TextMapPropagator.scala b/core/common/src/main/scala/org/typelevel/otel4s/TextMapPropagator.scala index 5d0b9c480..d09ad7c63 100644 --- a/core/common/src/main/scala/org/typelevel/otel4s/TextMapPropagator.scala +++ b/core/common/src/main/scala/org/typelevel/otel4s/TextMapPropagator.scala @@ -17,7 +17,6 @@ package org.typelevel.otel4s import cats.Applicative -import org.typelevel.vault.Vault /** The process of propagating data across process boundaries involves injecting * and extracting values in the form of text into carriers that travel in-band. @@ -34,7 +33,7 @@ import org.typelevel.vault.Vault * @tparam F * the higher-kinded type of a polymorphic effect */ -trait TextMapPropagator[F[_]] { +trait TextMapPropagator[F[_], Ctx] { /** Extracts key-value pairs from the given `carrier` and adds them to the * given context. @@ -51,7 +50,7 @@ trait TextMapPropagator[F[_]] { * @return * the new context with stored key-value pairs */ - def extract[A: TextMapGetter](ctx: Vault, carrier: A): Vault + def extract[A: TextMapGetter](ctx: Ctx, carrier: A): Ctx /** Injects data from the context into the given mutable `carrier` for * downstream consumers, for example as HTTP headers. @@ -65,7 +64,7 @@ trait TextMapPropagator[F[_]] { * @tparam A * the type of the carrier, which is mutable */ - def inject[A: TextMapSetter](ctx: Vault, carrier: A): F[Unit] + def inject[A: TextMapSetter](ctx: Ctx, carrier: A): F[Unit] /** Injects data from the context into a copy of the given immutable `carrier` * for downstream consumers, for example as HTTP headers. @@ -85,12 +84,14 @@ trait TextMapPropagator[F[_]] { * @return * a copy of the carrier, with new fields injected */ - def injected[A: TextMapUpdater](ctx: Vault, carrier: A): A + def injected[A: TextMapUpdater](ctx: Ctx, carrier: A): A } object TextMapPropagator { - def apply[F[_]](implicit ev: TextMapPropagator[F]): TextMapPropagator[F] = ev + def apply[F[_], Ctx](implicit + ev: TextMapPropagator[F, Ctx] + ): TextMapPropagator[F, Ctx] = ev /** Creates a no-op implementation of the [[TextMapPropagator]]. * @@ -99,15 +100,15 @@ object TextMapPropagator { * @tparam F * the higher-kinded type of a polymorphic effect */ - def noop[F[_]: Applicative]: TextMapPropagator[F] = - new TextMapPropagator[F] { - def extract[A: TextMapGetter](ctx: Vault, carrier: A): Vault = + def noop[F[_]: Applicative, Ctx]: TextMapPropagator[F, Ctx] = + new TextMapPropagator[F, Ctx] { + def extract[A: TextMapGetter](ctx: Ctx, carrier: A): Ctx = ctx - def inject[A: TextMapSetter](ctx: Vault, carrier: A): F[Unit] = + def inject[A: TextMapSetter](ctx: Ctx, carrier: A): F[Unit] = Applicative[F].unit - def injected[A: TextMapUpdater](ctx: Vault, carrier: A): A = + def injected[A: TextMapUpdater](ctx: Ctx, carrier: A): A = carrier } } diff --git a/core/common/src/main/scala/org/typelevel/otel4s/context/Contextual.scala b/core/common/src/main/scala/org/typelevel/otel4s/context/Contextual.scala new file mode 100644 index 000000000..01687c3fb --- /dev/null +++ b/core/common/src/main/scala/org/typelevel/otel4s/context/Contextual.scala @@ -0,0 +1,82 @@ +/* + * 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 +package context + +trait Contextual[C] { outer => + + /** The type of [[context.Key `Key`]] used by contexts of type `C`. */ + type Key[A] <: context.Key[A] + + /** Retrieves the value associated with the given key from the context, if + * such a value exists. + */ + def get[A](ctx: C)(key: Key[A]): Option[A] + + /** Retrieves the value associated with the given key from the context, if + * such a value exists; otherwise, returns the provided default value. + */ + def getOrElse[A](ctx: C)(key: Key[A], default: => A): A = + get(ctx)(key).getOrElse(default) + + /** Creates a copy of this context with the given value associated with the + * given key. + */ + def updated[A](ctx: C)(key: Key[A], value: A): C + + /** The root context, from which all other contexts are derived. */ + def root: C + + class ContextSyntax(ctx: C) { + + /** Retrieves the value associated with the given key from the context, if + * such a value exists. + */ + def get[A](key: Key[A]): Option[A] = + outer.get(ctx)(key) + + /** Retrieves the value associated with the given key from the context, if + * such a value exists; otherwise, returns the provided default value. + */ + def getOrElse[A](key: Key[A], default: => A): A = + outer.getOrElse(ctx)(key, default) + + /** Creates a copy of this context with the given value associated with the + * given key. + */ + def updated[A](key: Key[A], value: A): C = + outer.updated(ctx)(key, value) + } +} + +object Contextual { + + /** A type alias for a [[`Contextual`]] explicitly parameterized by its + * [[Contextual.Key `Key`]] type. + */ + type Keyed[C, K[X] <: Key[X]] = Contextual[C] { type Key[A] = K[A] } + + /** Summons a [[`Contextual`]] that is available implicitly. */ + def apply[C](implicit c: Contextual[C]): Contextual[C] = c + + trait Syntax { + implicit def toContextSyntax[C](ctx: C)(implicit + c: Contextual[C] + ): c.ContextSyntax = + new c.ContextSyntax(ctx) + } +} diff --git a/core/common/src/main/scala/org/typelevel/otel4s/context/Key.scala b/core/common/src/main/scala/org/typelevel/otel4s/context/Key.scala new file mode 100644 index 000000000..513eb2007 --- /dev/null +++ b/core/common/src/main/scala/org/typelevel/otel4s/context/Key.scala @@ -0,0 +1,48 @@ +/* + * 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.context + +/** A key for a context. */ +trait Key[A] { + + /** The debug name of the key. */ + val name: String + + override def toString: String = s"Key($name)" +} + +object Key { + + /** Something that provides context keys. + * + * @tparam K + * the type of keys + */ + trait Provider[F[_], K[X] <: Key[X]] { + + /** Creates a unique key with the given (debug) name. */ + def uniqueKey[A](name: String): F[K[A]] + } + + object Provider { + + /** Summons a [[`Provider`]] that is available implicitly. */ + def apply[F[_], K[X] <: Key[X]](implicit + p: Provider[F, K] + ): Provider[F, K] = p + } +} diff --git a/core/common/src/main/scala/org/typelevel/otel4s/context/syntax.scala b/core/common/src/main/scala/org/typelevel/otel4s/context/syntax.scala new file mode 100644 index 000000000..af55c80fc --- /dev/null +++ b/core/common/src/main/scala/org/typelevel/otel4s/context/syntax.scala @@ -0,0 +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.context + +object syntax extends Contextual.Syntax diff --git a/core/common/src/test/scala/org/typelevel/otel4s/context/ContextSuite.scala b/core/common/src/test/scala/org/typelevel/otel4s/context/ContextSuite.scala new file mode 100644 index 000000000..183cd850d --- /dev/null +++ b/core/common/src/test/scala/org/typelevel/otel4s/context/ContextSuite.scala @@ -0,0 +1,52 @@ +/* + * 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.context + +import cats.effect.SyncIO +import munit.FunSuite +import org.typelevel.otel4s.context.syntax._ +import org.typelevel.otel4s.context.vault.VaultContext + +class ContextSuite extends FunSuite { + test("implicit syntax") { + def check[C, K[X] <: Key[X]](implicit + c: Contextual.Keyed[C, K], + kp: Key.Provider[SyncIO, K] + ): Unit = { + val key1 = kp.uniqueKey[String]("key1").unsafeRunSync() + val key2 = kp.uniqueKey[Int]("key2").unsafeRunSync() + + var ctx = c.root + assertEquals(ctx.get(key1), None) + assertEquals(ctx.get(key2), None) + + ctx = ctx.updated(key1, "1") + assertEquals(ctx.get(key1), Some("1")) + assertEquals(ctx.get(key2), None) + + ctx = ctx.updated(key1, "2") + assertEquals(ctx.get(key1), Some("2")) + assertEquals(ctx.get(key2), None) + + ctx = ctx.updated(key2, 1) + assertEquals(ctx.get(key1), Some("2")) + assertEquals(ctx.get(key2), Some(1)) + } + + check[VaultContext, VaultContext.Key] + } +} diff --git a/core/common/src/test/scala/org/typelevel/otel4s/context/vault/VaultContext.scala b/core/common/src/test/scala/org/typelevel/otel4s/context/vault/VaultContext.scala new file mode 100644 index 000000000..5546b894f --- /dev/null +++ b/core/common/src/test/scala/org/typelevel/otel4s/context/vault/VaultContext.scala @@ -0,0 +1,82 @@ +/* + * 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 +package context +package vault + +import cats.Functor +import cats.effect.Unique +import cats.syntax.functor._ +import org.typelevel.vault.{Key => VaultKey} +import org.typelevel.vault.Vault + +/** A context implementation backed by a [[org.typelevel.vault.Vault `Vault`]]. + */ +final class VaultContext private (vault: Vault) { + + /** Retrieves the value associated with the given key from the context, if + * such a value exists. + */ + def get[A](key: VaultContext.Key[A]): Option[A] = + vault.lookup(key.underlying) + + /** Retrieves the value associated with the given key from the context, if + * such a value exists; otherwise, returns the provided default value. + */ + def getOrElse[A](key: VaultContext.Key[A], default: => A): A = + get(key).getOrElse(default) + + /** Creates a copy of this context with the given value associated with the + * given key. + */ + def updated[A](key: VaultContext.Key[A], value: A): VaultContext = + new VaultContext(vault.insert(key.underlying, value)) +} + +object VaultContext { + + /** A key for use with a [[`VaultContext`]]. */ + final class Key[A] private ( + val name: String, + private[VaultContext] val underlying: VaultKey[A] + ) extends context.Key[A] + + object Key { + + /** Creates a unique key with the given (debug) name. */ + def unique[F[_]: Functor: Unique, A](name: String): F[Key[A]] = + VaultKey.newKey[F, A].map(new Key[A](name, _)) + + implicit def provider[F[_]: Functor: Unique]: context.Key.Provider[F, Key] = + new context.Key.Provider[F, Key] { + def uniqueKey[A](name: String): F[Key[A]] = unique(name) + } + } + + /** The root [[`VaultContext`]], from which all other contexts are derived. */ + val root: VaultContext = new VaultContext(Vault.empty) + + implicit object Contextual extends context.Contextual[VaultContext] { + type Key[A] = VaultContext.Key[A] + + def get[A](ctx: VaultContext)(key: Key[A]): Option[A] = + ctx.get(key) + def updated[A](ctx: VaultContext)(key: Key[A], value: A): VaultContext = + ctx.updated(key, value) + def root: VaultContext = VaultContext.root + } +} diff --git a/core/metrics/src/main/scala/org/typelevel/otel4s/metrics/Meter.scala b/core/metrics/src/main/scala/org/typelevel/otel4s/metrics/Meter.scala index 1f557dd95..f15fe83e5 100644 --- a/core/metrics/src/main/scala/org/typelevel/otel4s/metrics/Meter.scala +++ b/core/metrics/src/main/scala/org/typelevel/otel4s/metrics/Meter.scala @@ -17,7 +17,7 @@ package org.typelevel.otel4s.metrics import cats.Applicative -import cats.effect.Resource +import cats.effect.kernel.Resource @annotation.implicitNotFound(""" Could not find the `Meter` for ${F}. `Meter` can be one of the following: diff --git a/core/metrics/src/main/scala/org/typelevel/otel4s/metrics/ObservableInstrumentBuilder.scala b/core/metrics/src/main/scala/org/typelevel/otel4s/metrics/ObservableInstrumentBuilder.scala index cbc034e3e..6b1321c70 100644 --- a/core/metrics/src/main/scala/org/typelevel/otel4s/metrics/ObservableInstrumentBuilder.scala +++ b/core/metrics/src/main/scala/org/typelevel/otel4s/metrics/ObservableInstrumentBuilder.scala @@ -16,7 +16,7 @@ package org.typelevel.otel4s.metrics -import cats.effect.Resource +import cats.effect.kernel.Resource trait ObservableInstrumentBuilder[F[_], A, Instrument] { type Self <: ObservableInstrumentBuilder[F, A, Instrument] diff --git a/core/metrics/src/test/scala/org/typelevel/otel4s/metrics/ObservableSuite.scala b/core/metrics/src/test/scala/org/typelevel/otel4s/metrics/ObservableSuite.scala index 4329f891f..ea1369a74 100644 --- a/core/metrics/src/test/scala/org/typelevel/otel4s/metrics/ObservableSuite.scala +++ b/core/metrics/src/test/scala/org/typelevel/otel4s/metrics/ObservableSuite.scala @@ -19,7 +19,7 @@ package metrics import cats.effect.IO import cats.effect.Ref -import cats.effect.Resource +import cats.effect.kernel.Resource import cats.syntax.all._ import munit.CatsEffectSuite diff --git a/core/trace/src/main/scala/org/typelevel/otel4s/trace/SpanBuilder.scala b/core/trace/src/main/scala/org/typelevel/otel4s/trace/SpanBuilder.scala index 4e559137d..e5697b397 100644 --- a/core/trace/src/main/scala/org/typelevel/otel4s/trace/SpanBuilder.scala +++ b/core/trace/src/main/scala/org/typelevel/otel4s/trace/SpanBuilder.scala @@ -19,8 +19,8 @@ package trace import cats.Applicative import cats.arrow.FunctionK -import cats.effect.MonadCancelThrow -import cats.effect.Resource +import cats.effect.kernel.MonadCancelThrow +import cats.effect.kernel.Resource import scala.concurrent.duration.FiniteDuration diff --git a/core/trace/src/main/scala/org/typelevel/otel4s/trace/SpanContext.scala b/core/trace/src/main/scala/org/typelevel/otel4s/trace/SpanContext.scala index def3b8e14..8fd4aa78d 100644 --- a/core/trace/src/main/scala/org/typelevel/otel4s/trace/SpanContext.scala +++ b/core/trace/src/main/scala/org/typelevel/otel4s/trace/SpanContext.scala @@ -16,9 +16,6 @@ package org.typelevel.otel4s.trace -import cats.effect.SyncIO -import org.typelevel.vault.Key -import org.typelevel.vault.Vault import scodec.bits.ByteVector /** A span context contains the state that must propagate to child spans and @@ -68,9 +65,6 @@ trait SpanContext { * parent. */ def isRemote: Boolean - - def storeInContext(context: Vault): Vault = - context.insert(SpanContext.key, this) } object SpanContext { @@ -115,9 +109,4 @@ object SpanContext { val isValid: Boolean = false val isRemote: Boolean = false } - - private val key = Key.newKey[SyncIO, SpanContext].unsafeRunSync() - - def fromContext(context: Vault): Option[SpanContext] = - context.lookup(key) } diff --git a/core/trace/src/main/scala/org/typelevel/otel4s/trace/SpanOps.scala b/core/trace/src/main/scala/org/typelevel/otel4s/trace/SpanOps.scala index 1d14a453b..2bb97701b 100644 --- a/core/trace/src/main/scala/org/typelevel/otel4s/trace/SpanOps.scala +++ b/core/trace/src/main/scala/org/typelevel/otel4s/trace/SpanOps.scala @@ -17,8 +17,8 @@ package org.typelevel.otel4s package trace -import cats.effect.MonadCancelThrow -import cats.effect.Resource +import cats.effect.kernel.MonadCancelThrow +import cats.effect.kernel.Resource import cats.syntax.functor._ import cats.~> @@ -56,8 +56,9 @@ trait SpanOps[F[_]] { */ def startUnmanaged: F[Span[F]] - /** Creates a [[Span]] and a [[cats.effect.Resource Resource]] for using it. - * Unlike [[startUnmanaged]], the lifecycle of the span is fully managed. + /** Creates a [[Span]] and a [[cats.effect.kernel.Resource Resource]] for + * using it. Unlike [[startUnmanaged]], the lifecycle of the span is fully + * managed. * * The finalization strategy is determined by [[SpanFinalizer.Strategy]]. By * default, the abnormal termination (error, cancelation) is recorded. diff --git a/core/trace/src/main/scala/org/typelevel/otel4s/trace/Tracer.scala b/core/trace/src/main/scala/org/typelevel/otel4s/trace/Tracer.scala index f4ef99f0c..cf42bec8c 100644 --- a/core/trace/src/main/scala/org/typelevel/otel4s/trace/Tracer.scala +++ b/core/trace/src/main/scala/org/typelevel/otel4s/trace/Tracer.scala @@ -18,7 +18,7 @@ package org.typelevel.otel4s package trace import cats.Applicative -import cats.effect.MonadCancelThrow +import cats.effect.kernel.MonadCancelThrow import cats.~> import org.typelevel.otel4s.meta.InstrumentMeta diff --git a/examples/src/main/scala/KleisliExample.scala b/examples/src/main/scala/KleisliExample.scala index 0a9734ed0..d57b58176 100644 --- a/examples/src/main/scala/KleisliExample.scala +++ b/examples/src/main/scala/KleisliExample.scala @@ -19,29 +19,27 @@ import cats.effect.Async import cats.effect.IO import cats.effect.IOApp import cats.effect.Resource -import cats.mtl.Local import io.opentelemetry.api.GlobalOpenTelemetry import org.typelevel.otel4s.java.OtelJava +import org.typelevel.otel4s.java.context.Context +import org.typelevel.otel4s.java.context.LocalContext import org.typelevel.otel4s.trace.Tracer -import org.typelevel.vault.Vault object KleisliExample extends IOApp.Simple { - def work[F[_]: Async: Tracer] = + def work[F[_]: Async: Tracer]: F[Unit] = Tracer[F].span("work").surround(Async[F].delay(println("I'm working"))) - def tracerResource[F[_]](implicit - F: Async[F], - L: Local[F, Vault] - ): Resource[F, Tracer[F]] = + private def tracerResource[F[_]: Async: LocalContext] + : Resource[F, Tracer[F]] = Resource .eval(Async[F].delay(GlobalOpenTelemetry.get)) .map(OtelJava.local[F]) .evalMap(_.tracerProvider.get("kleisli-example")) def run: IO[Unit] = - tracerResource[Kleisli[IO, Vault, *]] - .use { implicit tracer: Tracer[Kleisli[IO, Vault, *]] => - work[Kleisli[IO, Vault, *]] + tracerResource[Kleisli[IO, Context, *]] + .use { implicit tracer: Tracer[Kleisli[IO, Context, *]] => + work[Kleisli[IO, Context, *]] } - .run(Vault.empty) + .run(Context.root) } diff --git a/java/all/src/main/scala/org/typelevel/otel4s/java/OtelJava.scala b/java/all/src/main/scala/org/typelevel/otel4s/java/OtelJava.scala index 832eade89..45218687a 100644 --- a/java/all/src/main/scala/org/typelevel/otel4s/java/OtelJava.scala +++ b/java/all/src/main/scala/org/typelevel/otel4s/java/OtelJava.scala @@ -21,7 +21,6 @@ import cats.effect.IOLocal import cats.effect.LiftIO import cats.effect.Resource import cats.effect.Sync -import cats.mtl.Local import cats.syntax.all._ import io.opentelemetry.api.{OpenTelemetry => JOpenTelemetry} import io.opentelemetry.api.GlobalOpenTelemetry @@ -29,12 +28,21 @@ import io.opentelemetry.sdk.{OpenTelemetrySdk => JOpenTelemetrySdk} import org.typelevel.otel4s.ContextPropagators import org.typelevel.otel4s.Otel4s import org.typelevel.otel4s.java.Conversions.asyncFromCompletableResultCode +import org.typelevel.otel4s.java.context.Context +import org.typelevel.otel4s.java.context.LocalContext import org.typelevel.otel4s.java.instances._ import org.typelevel.otel4s.java.metrics.Metrics import org.typelevel.otel4s.java.trace.Traces import org.typelevel.otel4s.metrics.MeterProvider import org.typelevel.otel4s.trace.TracerProvider -import org.typelevel.vault.Vault + +sealed class OtelJava[F[_]] private ( + val propagators: ContextPropagators[F, Context], + val meterProvider: MeterProvider[F], + val tracerProvider: TracerProvider[F], +) extends Otel4s[F] { + type Ctx = Context +} object OtelJava { @@ -48,30 +56,26 @@ object OtelJava { * @return * An effect of an [[org.typelevel.otel4s.Otel4s]] resource. */ - def forAsync[F[_]: LiftIO: Async](jOtel: JOpenTelemetry): F[Otel4s[F]] = - IOLocal(Vault.empty) - .map { implicit ioLocal: IOLocal[Vault] => + def forAsync[F[_]: LiftIO: Async](jOtel: JOpenTelemetry): F[OtelJava[F]] = + IOLocal(Context.root) + .map { implicit ioLocal: IOLocal[Context] => local[F](jOtel) } .to[F] - def local[F[_]]( + def local[F[_]: Async: LocalContext]( jOtel: JOpenTelemetry - )(implicit F: Async[F], L: Local[F, Vault]): Otel4s[F] = { - val contextPropagators = new ContextPropagatorsImpl[F]( - jOtel.getPropagators, - ContextConversions.toJContext, - ContextConversions.fromJContext - ) + ): OtelJava[F] = { + val contextPropagators = new ContextPropagatorsImpl[F](jOtel.getPropagators) val metrics = Metrics.forAsync(jOtel) val traces = Traces.local(jOtel, contextPropagators) - new Otel4s[F] { - def propagators: ContextPropagators[F] = contextPropagators - def meterProvider: MeterProvider[F] = metrics.meterProvider - def tracerProvider: TracerProvider[F] = traces.tracerProvider - - override def toString: String = jOtel.toString() + new OtelJava[F]( + contextPropagators, + metrics.meterProvider, + traces.tracerProvider, + ) { + override def toString: String = jOtel.toString } } @@ -85,7 +89,7 @@ object OtelJava { */ def resource[F[_]: LiftIO: Async]( acquire: F[JOpenTelemetrySdk] - ): Resource[F, Otel4s[F]] = + ): Resource[F, OtelJava[F]] = Resource .make(acquire)(sdk => asyncFromCompletableResultCode(Sync[F].delay(sdk.shutdown())) @@ -95,6 +99,6 @@ object OtelJava { /** Creates an [[org.typelevel.otel4s.Otel4s]] from the global Java * OpenTelemetry instance. */ - def global[F[_]: LiftIO: Async]: F[Otel4s[F]] = + def global[F[_]: LiftIO: Async]: F[OtelJava[F]] = Sync[F].delay(GlobalOpenTelemetry.get).flatMap(forAsync[F]) } diff --git a/java/common/src/main/scala/org/typelevel/otel4s/java/ContextPropagatorsImpl.scala b/java/common/src/main/scala/org/typelevel/otel4s/java/ContextPropagatorsImpl.scala index 62d72c05f..196ae90da 100644 --- a/java/common/src/main/scala/org/typelevel/otel4s/java/ContextPropagatorsImpl.scala +++ b/java/common/src/main/scala/org/typelevel/otel4s/java/ContextPropagatorsImpl.scala @@ -18,21 +18,14 @@ package org.typelevel.otel4s package java import cats.effect.kernel.Sync -import io.opentelemetry.context.{Context => JContext} import io.opentelemetry.context.propagation.{ ContextPropagators => JContextPropagators } -import org.typelevel.vault.Vault +import org.typelevel.otel4s.java.context.Context private[java] class ContextPropagatorsImpl[F[_]: Sync]( - propagators: JContextPropagators, - toJContext: Vault => JContext, - fromJContext: JContext => Vault -) extends ContextPropagators[F] { - val textMapPropagator: TextMapPropagator[F] = - new TextMapPropagatorImpl( - propagators.getTextMapPropagator, - toJContext, - fromJContext - ) + propagators: JContextPropagators +) extends ContextPropagators[F, Context] { + val textMapPropagator: TextMapPropagator[F, Context] = + new TextMapPropagatorImpl(propagators.getTextMapPropagator) } diff --git a/java/common/src/main/scala/org/typelevel/otel4s/java/TextMapPropagatorImpl.scala b/java/common/src/main/scala/org/typelevel/otel4s/java/TextMapPropagatorImpl.scala index 270f0490a..6f54c8996 100644 --- a/java/common/src/main/scala/org/typelevel/otel4s/java/TextMapPropagatorImpl.scala +++ b/java/common/src/main/scala/org/typelevel/otel4s/java/TextMapPropagatorImpl.scala @@ -18,34 +18,29 @@ package org.typelevel.otel4s package java import cats.effect.Sync -import io.opentelemetry.context.{Context => JContext} import io.opentelemetry.context.propagation.{ TextMapPropagator => JTextMapPropagator } import org.typelevel.otel4s.java.Conversions._ -import org.typelevel.vault.Vault +import org.typelevel.otel4s.java.context.Context private[java] class TextMapPropagatorImpl[F[_]: Sync]( - jPropagator: JTextMapPropagator, - toJContext: Vault => JContext, - fromJContext: JContext => Vault -) extends TextMapPropagator[F] { - def extract[A: TextMapGetter](ctx: Vault, carrier: A): Vault = - fromJContext( - jPropagator.extract(toJContext(ctx), carrier, fromTextMapGetter) - ) + jPropagator: JTextMapPropagator +) extends TextMapPropagator[F, Context] { + def extract[A: TextMapGetter](ctx: Context, carrier: A): Context = + ctx.map(jPropagator.extract(_, carrier, fromTextMapGetter)) - def inject[A: TextMapSetter](ctx: Vault, carrier: A): F[Unit] = + def inject[A: TextMapSetter](ctx: Context, carrier: A): F[Unit] = Sync[F].delay( - jPropagator.inject(toJContext(ctx), carrier, fromTextMapSetter) + jPropagator.inject(ctx.underlying, carrier, fromTextMapSetter) ) - def injected[A](ctx: Vault, carrier: A)(implicit + def injected[A](ctx: Context, carrier: A)(implicit injector: TextMapUpdater[A] ): A = { var injectedCarrier = carrier jPropagator.inject[Null]( - toJContext(ctx), + ctx.underlying, null, // explicitly allowed per opentelemetry-java, so our setter can be a lambda! (_, key, value) => { injectedCarrier = injector.updated(injectedCarrier, key, value) 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 new file mode 100644 index 000000000..6c224b1f2 --- /dev/null +++ b/java/common/src/main/scala/org/typelevel/otel4s/java/context/Context.scala @@ -0,0 +1,109 @@ +/* + * 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 +package java.context + +import cats.effect.Sync +import io.opentelemetry.api.trace.{Span => JSpan} +import io.opentelemetry.context.{Context => JContext} +import io.opentelemetry.context.ContextKey + +/** A context wrapping a context from the Java open telemetry library (referred + * to in further documentation as a "Java context"). + */ +sealed trait Context { + + /** The underlying Java context. */ + def underlying: JContext + + /** Retrieves the value associated with the given key from the context, if + * such a value exists. + */ + def get[A](key: Context.Key[A]): Option[A] + + /** Retrieves the value associated with the given key from the context, if + * such a value exists; otherwise, returns the provided default value. + */ + final def getOrElse[A](key: Context.Key[A], default: => A): A = + get(key).getOrElse(default) + + /** Creates a copy of this context with the given value associated with the + * given key. + */ + def updated[A](key: Context.Key[A], value: A): Context + + /** Maps the underlying Java context. */ + private[java] def map(f: JContext => JContext): Context +} + +object Context { + private[java] object Noop extends Context { + val underlying: JContext = + JSpan.getInvalid.storeInContext(root.underlying) + def get[A](key: Context.Key[A]): Option[A] = None + def updated[A](key: Context.Key[A], value: A): Context = this + private[java] def map(f: JContext => JContext): Context = this + } + + private[java] final case class Wrapped private[Context] (underlying: JContext) + extends Context { + def get[A](key: Context.Key[A]): Option[A] = + Option(underlying.get(key)) + def updated[A](key: Context.Key[A], value: A): Context = + Wrapped(underlying.`with`(key, value)) + private[java] def map(f: JContext => JContext): Context = + wrap(f(underlying)) + } + + /** A key for use with a [[`Context`]] */ + final class Key[A] private (val name: String) + extends context.Key[A] + with ContextKey[A] + + object Key { + + /** Creates a unique key with the given (debug) name. */ + def unique[F[_]: Sync, A](name: String): F[Key[A]] = + Sync[F].delay(new Key(name)) + + implicit def provider[F[_]: Sync]: context.Key.Provider[F, Key] = + new context.Key.Provider[F, Key] { + def uniqueKey[A](name: String): F[Key[A]] = unique(name) + } + } + + /** Wraps a Java context as a [[`Context`]]. */ + def wrap(context: JContext): Context = { + val isNoop = + Option(JSpan.fromContextOrNull(context)) + .exists(!_.getSpanContext.isValid) + if (isNoop) Noop else Wrapped(context) + } + + /** The root [[`Context`]], from which all other contexts are derived. */ + val root: Context = wrap(JContext.root()) + + implicit object Contextual extends context.Contextual[Context] { + type Key[A] = Context.Key[A] + + def get[A](ctx: Context)(key: Key[A]): Option[A] = + ctx.get(key) + def updated[A](ctx: Context)(key: Key[A], value: A): Context = + ctx.updated(key, value) + def root: Context = Context.root + } +} diff --git a/core/common/src/main/scala/org/typelevel/otel4s/context/package.scala b/java/common/src/main/scala/org/typelevel/otel4s/java/context/package.scala similarity index 82% rename from core/common/src/main/scala/org/typelevel/otel4s/context/package.scala rename to java/common/src/main/scala/org/typelevel/otel4s/java/context/package.scala index 9f96534df..cb96db4b0 100644 --- a/core/common/src/main/scala/org/typelevel/otel4s/context/package.scala +++ b/java/common/src/main/scala/org/typelevel/otel4s/java/context/package.scala @@ -14,13 +14,12 @@ * limitations under the License. */ -package org.typelevel.otel4s +package org.typelevel.otel4s.java import cats.mtl.Ask import cats.mtl.Local -import org.typelevel.vault.Vault package object context { - type LocalVault[F[_]] = Local[F, Vault] - type AskVault[F[_]] = Ask[F, Vault] + type AskContext[F[_]] = Ask[F, Context] + type LocalContext[F[_]] = Local[F, Context] } diff --git a/java/trace/src/main/scala/org/typelevel/otel4s/java/ContextConversions.scala b/java/trace/src/main/scala/org/typelevel/otel4s/java/ContextConversions.scala deleted file mode 100644 index 852397170..000000000 --- a/java/trace/src/main/scala/org/typelevel/otel4s/java/ContextConversions.scala +++ /dev/null @@ -1,50 +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 io.opentelemetry.api.trace.{Span => JSpan} -import io.opentelemetry.context.{Context => JContext} -import org.typelevel.otel4s.java.trace.Scope -import org.typelevel.otel4s.java.trace.WrappedSpanContext -import org.typelevel.vault.Vault - -private[otel4s] object ContextConversions { - - def toJContext(context: Vault): JContext = - Scope.fromContext(context) match { - case Scope.Root(ctx) => - ctx - case Scope.Span(ctx, jSpan) => - jSpan.storeInContext(ctx) - case Scope.Noop => - JSpan.getInvalid.storeInContext(JContext.root()) - } - - def fromJContext(jContext: JContext): Vault = - JSpan.fromContextOrNull(jContext) match { - case null => - Scope.Root(jContext).storeInContext(Vault.empty) - case jSpan => - if (jSpan.getSpanContext.isValid) { - var context = Vault.empty - context = Scope.Span(jContext, jSpan).storeInContext(context) - new WrappedSpanContext(jSpan.getSpanContext).storeInContext(context) - } else { - Scope.Noop.storeInContext(Vault.empty) - } - } -} diff --git a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/Scope.scala b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/Scope.scala deleted file mode 100644 index 5351c0cfa..000000000 --- a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/Scope.scala +++ /dev/null @@ -1,45 +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.trace - -import cats.effect.SyncIO -import io.opentelemetry.api.trace.{Span => JSpan} -import io.opentelemetry.context.{Context => JContext} -import org.typelevel.vault.Key -import org.typelevel.vault.Vault - -private[java] sealed trait Scope { - def storeInContext(context: Vault): Vault = - context.insert(Scope.scopeKey, this) -} - -private[java] object Scope { - val root: Scope = Root(JContext.root) - - final case class Root(ctx: JContext) extends Scope - final case class Span( - ctx: JContext, - span: JSpan - ) extends Scope - case object Noop extends Scope - - private val scopeKey = - Key.newKey[SyncIO, Scope].unsafeRunSync() - - def fromContext(context: Vault): Scope = - context.lookup(scopeKey).getOrElse(root) -} diff --git a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/SpanBuilderImpl.scala b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/SpanBuilderImpl.scala index 5aaa5b816..7caf00840 100644 --- a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/SpanBuilderImpl.scala +++ b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/SpanBuilderImpl.scala @@ -27,6 +27,8 @@ import io.opentelemetry.api.trace.{SpanKind => JSpanKind} import io.opentelemetry.api.trace.{Tracer => JTracer} import io.opentelemetry.context.{Context => JContext} import org.typelevel.otel4s.Attribute +import org.typelevel.otel4s.java.context.Context +import org.typelevel.otel4s.java.context.LocalContext import org.typelevel.otel4s.trace.Span import org.typelevel.otel4s.trace.SpanBuilder import org.typelevel.otel4s.trace.SpanContext @@ -39,7 +41,6 @@ import scala.concurrent.duration.FiniteDuration private[java] final case class SpanBuilderImpl[F[_]: Sync]( jTracer: JTracer, name: String, - scope: TraceScope[F], runner: SpanRunner[F], parent: SpanBuilderImpl.Parent = SpanBuilderImpl.Parent.Propagate, finalizationStrategy: SpanFinalizer.Strategy = @@ -48,7 +49,8 @@ private[java] final case class SpanBuilderImpl[F[_]: Sync]( links: Seq[(SpanContext, Seq[Attribute[_]])] = Nil, attributes: Seq[Attribute[_]] = Nil, startTimestamp: Option[FiniteDuration] = None -) extends SpanBuilder[F] { +)(implicit L: LocalContext[F]) + extends SpanBuilder[F] { import SpanBuilderImpl._ def withSpanKind(spanKind: SpanKind): SpanBuilder[F] = @@ -124,32 +126,19 @@ private[java] final case class SpanBuilderImpl[F[_]: Sync]( } private def parentContext: F[Option[JContext]] = - parent match { - case Parent.Root => - scope.current.flatMap { - case Scope.Root(ctx) => - Sync[F].pure(Some(ctx)) - - case Scope.Span(_, _) => - scope.root.map(s => Some(s.ctx)) - - case Scope.Noop => - Sync[F].pure(None) - } - - case Parent.Propagate => - scope.current.map { - case Scope.Root(ctx) => Some(ctx) - case Scope.Span(ctx, _) => Some(ctx) - case Scope.Noop => None - } - - case Parent.Explicit(parent) => - def parentSpan = JSpan.wrap(WrappedSpanContext.unwrap(parent)) - scope.current.map { - case Scope.Root(ctx) => Some(ctx.`with`(parentSpan)) - case Scope.Span(ctx, _) => Some(ctx.`with`(parentSpan)) - case Scope.Noop => None + L.reader { + case Context.Noop => None + case Context.Wrapped(underlying) => + Some { + parent match { + case Parent.Root => + Context.root.underlying + case Parent.Propagate => underlying + case Parent.Explicit(parent) => + JSpan + .wrap(WrappedSpanContext.unwrap(parent)) + .storeInContext(underlying) + } } } } @@ -157,7 +146,7 @@ private[java] final case class SpanBuilderImpl[F[_]: Sync]( private[java] object SpanBuilderImpl { sealed trait Parent - object Parent { + private object Parent { case object Propagate extends Parent case object Root extends Parent final case class Explicit(parent: SpanContext) extends Parent @@ -171,5 +160,4 @@ private[java] object SpanBuilderImpl { case SpanKind.Producer => JSpanKind.PRODUCER case SpanKind.Consumer => JSpanKind.CONSUMER } - } diff --git a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/SpanRunner.scala b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/SpanRunner.scala index d009e3d29..e6643c8ed 100644 --- a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/SpanRunner.scala +++ b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/SpanRunner.scala @@ -26,6 +26,7 @@ import cats.syntax.functor._ import cats.~> import io.opentelemetry.api.trace.{SpanBuilder => JSpanBuilder} import io.opentelemetry.context.{Context => JContext} +import org.typelevel.otel4s.java.context.LocalContext import org.typelevel.otel4s.trace.Span import org.typelevel.otel4s.trace.SpanFinalizer import org.typelevel.otel4s.trace.SpanOps @@ -43,7 +44,7 @@ private[java] object SpanRunner { finalizationStrategy: SpanFinalizer.Strategy ) - def span[F[_]: Sync](scope: TraceScope[F]): SpanRunner[F] = + def fromLocal[F[_]: Sync: LocalContext]: SpanRunner[F] = new SpanRunner[F] { def start(ctx: Option[RunnerContext]): Resource[F, SpanOps.Res[F]] = { ctx match { @@ -51,8 +52,7 @@ private[java] object SpanRunner { startManaged( builder = builder, hasStartTimestamp = hasStartTs, - finalizationStrategy = finalization, - scope = scope + finalizationStrategy = finalization ).map { case (back, nt) => SpanOps.Res(Span.fromBackend(back), nt) } case None => @@ -91,9 +91,8 @@ private[java] object SpanRunner { private def startManaged[F[_]: Sync]( builder: JSpanBuilder, hasStartTimestamp: Boolean, - finalizationStrategy: SpanFinalizer.Strategy, - scope: TraceScope[F] - ): Resource[F, (SpanBackendImpl[F], F ~> F)] = { + finalizationStrategy: SpanFinalizer.Strategy + )(implicit L: LocalContext[F]): Resource[F, (SpanBackendImpl[F], F ~> F)] = { def acquire: F[SpanBackendImpl[F]] = startSpan(builder, hasStartTimestamp) @@ -108,8 +107,14 @@ private[java] object SpanRunner { for { backend <- Resource.makeCase(acquire) { case (b, ec) => release(b, ec) } - nt <- Resource.eval(scope.makeScope(backend.jSpan)) + nt <- Resource.eval { + L.reader { ctx => + new (F ~> F) { + def apply[A](fa: F[A]): F[A] = + L.scope(fa)(ctx.map(backend.jSpan.storeInContext)) + } + } + } } yield (backend, nt) } - } diff --git a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TraceScope.scala b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TraceScope.scala deleted file mode 100644 index 30a8b178e..000000000 --- a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TraceScope.scala +++ /dev/null @@ -1,88 +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.trace - -import cats.mtl.Local -import cats.~> -import io.opentelemetry.api.trace.{Span => JSpan} -import io.opentelemetry.context.{Context => JContext} -import org.typelevel.vault.Vault - -private[java] trait TraceScope[F[_]] { - def root: F[Scope.Root] - def current: F[Scope] - def makeScope(span: JSpan): F[F ~> F] - def rootScope: F[F ~> F] - def noopScope: F ~> F -} - -private[java] object TraceScope { - - def fromLocal[F[_]](implicit L: Local[F, Vault]): TraceScope[F] = { - val scopeRoot = Scope.Root(JContext.root()) - - new TraceScope[F] { - val root: F[Scope.Root] = - L.applicative.pure(scopeRoot) - - def current: F[Scope] = - L.applicative.map(L.ask[Vault])(Scope.fromContext) - - def makeScope(span: JSpan): F[F ~> F] = - L.applicative.map(current)(scope => createScope(nextScope(scope, span))) - - def rootScope: F[F ~> F] = - L.applicative.map(current) { - case Scope.Root(_) => - createScope(scopeRoot) - - case Scope.Span(_, _) => - createScope(scopeRoot) - - case Scope.Noop => - createScope(Scope.Noop) - } - - def noopScope: F ~> F = - createScope(Scope.Noop) - - private def createScope(scope: Scope): F ~> F = - new (F ~> F) { - def apply[A](fa: F[A]): F[A] = - L.local(fa)(scope.storeInContext) - } - - private def nextScope(scope: Scope, span: JSpan): Scope = - scope match { - case Scope.Root(ctx) => - Scope.Span( - ctx.`with`(span), - span - ) - - case Scope.Span(ctx, _) => - Scope.Span( - ctx.`with`(span), - span - ) - - case Scope.Noop => - Scope.Noop - } - } - } -} diff --git a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerBuilderImpl.scala b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerBuilderImpl.scala index 810a613b0..bfd2b4f56 100644 --- a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerBuilderImpl.scala +++ b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerBuilderImpl.scala @@ -19,13 +19,13 @@ package org.typelevel.otel4s.java.trace import cats.effect.Sync import io.opentelemetry.api.trace.{TracerProvider => JTracerProvider} import org.typelevel.otel4s.ContextPropagators -import org.typelevel.otel4s.context.LocalVault +import org.typelevel.otel4s.java.context.Context +import org.typelevel.otel4s.java.context.LocalContext import org.typelevel.otel4s.trace._ -private[java] final case class TracerBuilderImpl[F[_]: Sync: LocalVault]( +private[java] final case class TracerBuilderImpl[F[_]: Sync: LocalContext]( jTracerProvider: JTracerProvider, - propagators: ContextPropagators[F], - scope: TraceScope[F], + propagators: ContextPropagators[F, Context], name: String, version: Option[String] = None, schemaUrl: Option[String] = None @@ -41,7 +41,6 @@ private[java] final case class TracerBuilderImpl[F[_]: Sync: LocalVault]( val b = jTracerProvider.tracerBuilder(name) version.foreach(b.setInstrumentationVersion) schemaUrl.foreach(b.setSchemaUrl) - new TracerImpl(b.build(), scope, propagators) + new TracerImpl(b.build(), propagators) } - } diff --git a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerImpl.scala b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerImpl.scala index b58ad44ac..f737c1e8d 100644 --- a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerImpl.scala +++ b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerImpl.scala @@ -17,66 +17,58 @@ package org.typelevel.otel4s.java.trace import cats.effect.Sync -import cats.mtl.Local -import cats.syntax.flatMap._ -import cats.syntax.functor._ import io.opentelemetry.api.trace.{Span => JSpan} import io.opentelemetry.api.trace.{Tracer => JTracer} import org.typelevel.otel4s.ContextPropagators import org.typelevel.otel4s.TextMapGetter import org.typelevel.otel4s.TextMapUpdater -import org.typelevel.otel4s.context.LocalVault +import org.typelevel.otel4s.java.context.Context +import org.typelevel.otel4s.java.context.LocalContext import org.typelevel.otel4s.trace.SpanBuilder import org.typelevel.otel4s.trace.SpanContext import org.typelevel.otel4s.trace.Tracer -import org.typelevel.vault.Vault -private[java] class TracerImpl[F[_]: Sync: LocalVault]( +private[java] class TracerImpl[F[_]: Sync]( jTracer: JTracer, - scope: TraceScope[F], - propagators: ContextPropagators[F] -) extends Tracer[F] { + propagators: ContextPropagators[F, Context] +)(implicit L: LocalContext[F]) + extends Tracer[F] { - private val runner: SpanRunner[F] = SpanRunner.span(scope) + private val runner: SpanRunner[F] = SpanRunner.fromLocal val meta: Tracer.Meta[F] = Tracer.Meta.enabled def currentSpanContext: F[Option[SpanContext]] = - scope.current.map { - case Scope.Span(_, jSpan) if jSpan.getSpanContext.isValid => - Some(new WrappedSpanContext(jSpan.getSpanContext)) - - case _ => - None + L.reader { + case Context.Noop => None + case Context.Wrapped(underlying) => + Option(JSpan.fromContextOrNull(underlying)) + .map(jSpan => new WrappedSpanContext(jSpan.getSpanContext)) } def spanBuilder(name: String): SpanBuilder[F] = - new SpanBuilderImpl[F](jTracer, name, scope, runner) + new SpanBuilderImpl[F](jTracer, name, runner) def childScope[A](parent: SpanContext)(fa: F[A]): F[A] = - scope - .makeScope(JSpan.wrap(WrappedSpanContext.unwrap(parent))) - .flatMap(_(fa)) + L.local(fa) { + _.map(JSpan.wrap(WrappedSpanContext.unwrap(parent)).storeInContext) + } def rootScope[A](fa: F[A]): F[A] = - scope.rootScope.flatMap(_(fa)) + L.local(fa) { + case Context.Noop => Context.Noop + case Context.Wrapped(_) => Context.root + } def noopScope[A](fa: F[A]): F[A] = - scope.noopScope(fa) + L.scope(fa)(Context.Noop) def joinOrRoot[A, C: TextMapGetter](carrier: C)(fa: F[A]): F[A] = { - val context = propagators.textMapPropagator.extract(Vault.empty, carrier) - - // use Local[F, Vault].scope(..)(context) to bring extracted headers - SpanContext.fromContext(context) match { - case Some(parent) => - Local[F, Vault].scope(childScope(parent)(fa))(context) - case None => - Local[F, Vault].scope(fa)(context) - } + val context = propagators.textMapPropagator.extract(Context.root, carrier) + L.scope(fa)(context) } def propagate[C: TextMapUpdater](carrier: C): F[C] = - Local[F, Vault].reader(propagators.textMapPropagator.injected(_, carrier)) + L.reader(propagators.textMapPropagator.injected(_, carrier)) } diff --git a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerProviderImpl.scala b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerProviderImpl.scala index fa2219c06..25d4d7f7e 100644 --- a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerProviderImpl.scala +++ b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerProviderImpl.scala @@ -17,31 +17,26 @@ package org.typelevel.otel4s.java.trace import cats.effect.Sync -import cats.mtl.Local import io.opentelemetry.api.trace.{TracerProvider => JTracerProvider} import org.typelevel.otel4s.ContextPropagators -import org.typelevel.otel4s.context.LocalVault +import org.typelevel.otel4s.java.context.Context +import org.typelevel.otel4s.java.context.LocalContext import org.typelevel.otel4s.trace.TracerBuilder import org.typelevel.otel4s.trace.TracerProvider -import org.typelevel.vault.Vault -private[java] class TracerProviderImpl[F[_]: Sync: LocalVault]( +private[java] class TracerProviderImpl[F[_]: Sync: LocalContext] private ( jTracerProvider: JTracerProvider, - propagators: ContextPropagators[F], - scope: TraceScope[F] + propagators: ContextPropagators[F, Context] ) extends TracerProvider[F] { def tracer(name: String): TracerBuilder[F] = - TracerBuilderImpl(jTracerProvider, propagators, scope, name) + TracerBuilderImpl(jTracerProvider, propagators, name) } private[java] object TracerProviderImpl { - def local[F[_]]( + def local[F[_]: Sync: LocalContext]( jTracerProvider: JTracerProvider, - propagators: ContextPropagators[F] - )(implicit F: Sync[F], L: Local[F, Vault]): TracerProvider[F] = { - val traceScope = TraceScope.fromLocal[F] - new TracerProviderImpl(jTracerProvider, propagators, traceScope) - } - + propagators: ContextPropagators[F, Context] + ): TracerProvider[F] = + new TracerProviderImpl(jTracerProvider, propagators) } diff --git a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/Traces.scala b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/Traces.scala index 629f7a5ea..0c0155561 100644 --- a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/Traces.scala +++ b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/Traces.scala @@ -20,11 +20,11 @@ package java.trace import cats.effect.IOLocal import cats.effect.LiftIO import cats.effect.Sync -import cats.mtl.Local import io.opentelemetry.api.{OpenTelemetry => JOpenTelemetry} +import org.typelevel.otel4s.java.context.Context +import org.typelevel.otel4s.java.context.LocalContext import org.typelevel.otel4s.java.instances._ import org.typelevel.otel4s.trace.TracerProvider -import org.typelevel.vault.Vault trait Traces[F[_]] { def tracerProvider: TracerProvider[F] @@ -32,10 +32,10 @@ trait Traces[F[_]] { object Traces { - def local[F[_]]( + def local[F[_]: Sync: LocalContext]( jOtel: JOpenTelemetry, - propagators: ContextPropagators[F] - )(implicit F: Sync[F], L: Local[F, Vault]): Traces[F] = { + propagators: ContextPropagators[F, Context] + ): Traces[F] = { val provider = TracerProviderImpl.local(jOtel.getTracerProvider, propagators) new Traces[F] { @@ -45,12 +45,11 @@ object Traces { def ioLocal[F[_]: LiftIO: Sync]( jOtel: JOpenTelemetry, - propagators: ContextPropagators[F] + propagators: ContextPropagators[F, Context] ): F[Traces[F]] = - IOLocal(Vault.empty) - .map { implicit ioLocal: IOLocal[Vault] => + IOLocal(Context.root) + .map { implicit ioLocal: IOLocal[Context] => local(jOtel, propagators) } .to[F] - } diff --git a/java/trace/src/test/scala/org/typelevel/otel4s/java/trace/TracerSuite.scala b/java/trace/src/test/scala/org/typelevel/otel4s/java/trace/TracerSuite.scala index 1f2db2916..b08122320 100644 --- a/java/trace/src/test/scala/org/typelevel/otel4s/java/trace/TracerSuite.scala +++ b/java/trace/src/test/scala/org/typelevel/otel4s/java/trace/TracerSuite.scala @@ -51,13 +51,12 @@ import io.opentelemetry.sdk.trace.`export`.SimpleSpanProcessor import io.opentelemetry.sdk.trace.internal.data.ExceptionEventData import munit.CatsEffectSuite import org.typelevel.otel4s.Attribute -import org.typelevel.otel4s.java.ContextConversions import org.typelevel.otel4s.java.ContextPropagatorsImpl +import org.typelevel.otel4s.java.context.Context import org.typelevel.otel4s.java.instances._ import org.typelevel.otel4s.trace.Span import org.typelevel.otel4s.trace.Tracer import org.typelevel.otel4s.trace.TracerProvider -import org.typelevel.vault.Vault import java.time.Instant import scala.concurrent.duration._ @@ -160,6 +159,9 @@ class TracerSuite extends CatsEffectSuite { } yield { assertEquals(spans.map(_.getTraceId), List(span.context.traceIdHex)) assertEquals(spans.map(_.getSpanId), List(span.context.spanIdHex)) + val key = JAttributeKey.stringKey("string-attribute") + val attr = spans.map(data => Option(data.getAttributes.get(key))) + assertEquals(attr.flatten, List("value")) } } @@ -1117,16 +1119,14 @@ class TracerSuite extends CatsEffectSuite { val tracerProvider: SdkTracerProvider = customize(builder).build() - IOLocal(Vault.empty).map { implicit ioLocal: IOLocal[Vault] => + IOLocal(Context.root).map { implicit ioLocal: IOLocal[Context] => val textMapPropagators = W3CTraceContextPropagator.getInstance() +: additionalPropagators val propagators = new ContextPropagatorsImpl[IO]( JContextPropagators.create( JTextMapPropagator.composite(textMapPropagators.asJava) - ), - ContextConversions.toJContext, - ContextConversions.fromJContext + ) ) val provider = TracerProviderImpl.local[IO](tracerProvider, propagators)