Skip to content

Commit

Permalink
Redesign context implementation
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
NthPortal committed Aug 11, 2023
1 parent 7686cfc commit b4b53c5
Show file tree
Hide file tree
Showing 23 changed files with 378 additions and 383 deletions.
4 changes: 3 additions & 1 deletion core/all/src/main/scala/org/typelevel/otel4s/Otel4s.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
32 changes: 32 additions & 0 deletions core/common/src/main/scala/org/typelevel/otel4s/context/Key.scala
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -56,9 +53,6 @@ trait SpanContext {
* parent.
*/
def isRemote: Boolean

def storeInContext(context: Vault): Vault =
context.insert(SpanContext.key, this)
}

object SpanContext {
Expand All @@ -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)
}
20 changes: 9 additions & 11 deletions examples/src/main/scala/KleisliExample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Loading

0 comments on commit b4b53c5

Please sign in to comment.