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 bbe5df02c..8b57cfaa1 100644 --- a/core/common/src/main/scala/org/typelevel/otel4s/ContextPropagators.scala +++ b/core/common/src/main/scala/org/typelevel/otel4s/ContextPropagators.scala @@ -18,14 +18,14 @@ 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 { - 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/TextMapPropagator.scala b/core/common/src/main/scala/org/typelevel/otel4s/TextMapPropagator.scala index cd9ef1268..9caa92a5a 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,22 +84,24 @@ 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 - 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/Context.scala b/core/common/src/main/scala/org/typelevel/otel4s/context/Context.scala new file mode 100644 index 000000000..a9259c9eb --- /dev/null +++ b/core/common/src/main/scala/org/typelevel/otel4s/context/Context.scala @@ -0,0 +1,51 @@ +/* + * 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 Context[C] { outer => + type Key[A] <: context.Key[A] + + def get[A](ctx: C)(key: Key[A]): Option[A] + + def getOrElse[A](ctx: C)(key: Key[A], default: => A): A = + get(ctx)(key).getOrElse(default) + + def updated[A](ctx: C)(key: Key[A], value: A): C + + def root: C + + class Ops(ctx: C) { + def get[A](key: Key[A]): Option[A] = + outer.get(ctx)(key) + def getOrElse[A](key: Key[A], default: => A): A = + outer.getOrElse(ctx)(key, default) + def updated[A](key: Key[A], value: A): C = + outer.updated(ctx)(key, value) + } +} + +object Context { + type Keyed[C, K[_]] = Context[C] { type Key[A] = K[A] } + + def apply[C](implicit c: Context[C]): Context[C] = c + + object Implicits { + implicit def mkOps[C](ctx: C)(implicit c: Context[C]): c.Ops = + new c.Ops(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..73cb8c3de --- /dev/null +++ b/core/common/src/main/scala/org/typelevel/otel4s/context/Key.scala @@ -0,0 +1,32 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.typelevel.otel4s.context + +trait Key[A] { + val name: String + + override def toString: String = s"Key($name)" +} + +object Key { + trait Provider[F[_], K[_]] { + def uniqueKey[A](name: String): F[K[A]] + } + object Provider { + def apply[F[_], K[_]](implicit p: Provider[F, K]): Provider[F, K] = p + } +} diff --git a/core/common/src/main/scala/org/typelevel/otel4s/context/vault/VaultContext.scala b/core/common/src/main/scala/org/typelevel/otel4s/context/vault/VaultContext.scala new file mode 100644 index 000000000..a5ef04dc4 --- /dev/null +++ b/core/common/src/main/scala/org/typelevel/otel4s/context/vault/VaultContext.scala @@ -0,0 +1,62 @@ +/* + * 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 + +final case class VaultContext(vault: Vault) { + def get[A](key: VaultContext.Key[A]): Option[A] = + vault.lookup(key.underlying) + def getOrElse[A](key: VaultContext.Key[A], default: => A): A = + get(key).getOrElse(default) + def updated[A](key: VaultContext.Key[A], value: A): VaultContext = + VaultContext(vault.insert(key.underlying, value)) +} + +object VaultContext extends (Vault => VaultContext) { + final class Key[A] private ( + val name: String, + private[VaultContext] val underlying: VaultKey[A] + ) extends context.Key[A] + object Key { + 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) + } + } + + val root: VaultContext = apply(Vault.empty) + + implicit object Ctx extends Context[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/trace/src/main/scala/org/typelevel/otel4s/trace/SpanContext.scala b/core/trace/src/main/scala/org/typelevel/otel4s/trace/SpanContext.scala index a2cedd577..79f78ea76 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 trait SpanContext { @@ -56,9 +53,6 @@ trait SpanContext { * parent. */ def isRemote: Boolean - - def storeInContext(context: Vault): Vault = - context.insert(SpanContext.key, this) } object SpanContext { @@ -85,9 +79,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/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..c2862191f --- /dev/null +++ b/java/common/src/main/scala/org/typelevel/otel4s/java/context/Context.scala @@ -0,0 +1,84 @@ +/* + * 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 + +sealed trait Context { + def underlying: JContext + def get[A](key: Context.Key[A]): Option[A] + final def getOrElse[A](key: Context.Key[A], default: => A): A = + get(key).getOrElse(default) + def updated[A](key: Context.Key[A], value: A): 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)) + } + + final class Key[A] private (val name: String) + extends context.Key[A] + with ContextKey[A] + object Key { + 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) + } + } + + def wrap(context: JContext): Context = { + val isNoop = + Option(JSpan.fromContextOrNull(context)) + .exists(!_.getSpanContext.isValid) + if (isNoop) Noop else Wrapped(context) + } + + val root: Context = wrap(JContext.root()) + + implicit object Ctx extends context.Context[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 79% 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..3f85e6b0a 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,10 @@ * 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 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 f7ac4dc0f..000000000 --- a/java/trace/src/main/scala/org/typelevel/otel4s/java/ContextConversions.scala +++ /dev/null @@ -1,56 +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 = { - var jContext = JContext.root() - jContext = Scope.fromContext(context) match { - case Scope.Root(_) => - jContext - case Scope.Span(_, jSpan) => - jSpan.storeInContext(jContext) - case Scope.Noop => - JSpan.getInvalid.storeInContext(jContext) - } - jContext - } - - def fromJContext(jContext: JContext): Vault = { - var context = Vault.empty - JSpan.fromContextOrNull(jContext) match { - case null => - context = Scope.root.storeInContext(context) - case jSpan => - if (jSpan.getSpanContext.isValid) { - context = Scope.Span(jContext, jSpan).storeInContext(context) - context = - new WrappedSpanContext(jSpan.getSpanContext).storeInContext(context) - } else { - context = Scope.Noop.storeInContext(context) - } - } - context - } -} 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 6f9b02f28..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.AskVault +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: AskVault]( +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: AskVault]( 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 eeff9202c..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,65 +17,58 @@ package org.typelevel.otel4s.java.trace import cats.effect.Sync -import cats.mtl.Ask -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.AskVault +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: AskVault]( +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) - - SpanContext.fromContext(context) match { - case Some(parent) => - childScope(parent)(fa) - case None => - rootScope(fa) - } + val context = propagators.textMapPropagator.extract(Context.root, carrier) + L.scope(fa)(context) } def propagate[C: TextMapUpdater](carrier: C): F[C] = - Ask[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 088005b4d..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.AskVault +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: AskVault]( +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 772c30cdc..bd4ff66f2 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 @@ -42,13 +42,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._ @@ -871,11 +870,9 @@ 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 propagators = new ContextPropagatorsImpl[IO]( - JContextPropagators.create(W3CTraceContextPropagator.getInstance()), - ContextConversions.toJContext, - ContextConversions.fromJContext + JContextPropagators.create(W3CTraceContextPropagator.getInstance()) ) val provider = TracerProviderImpl.local[IO](tracerProvider, propagators)