The context API in otel4s provides mechanisms for managing contextual information across async boundaries, including trace context, baggage, and custom values.
Contextual
The Contextual[C] trait defines operations for working with context values.
Type Signature
sealed trait Contextual[C]
Type Members
type Key[A] <: context.Key[A]
The type of Key used by contexts of type C.
Core Methods
get
def get[A](ctx: C)(key: Key[A]): Option[A]
Retrieves the value associated with the given key from the context.
The context to retrieve from
Returns: Option[A] - Some(value) if found, None otherwise
getOrElse
def getOrElse[A](ctx: C)(key: Key[A], default: => A): A
Retrieves the value associated with the given key, or returns the default value if not found.
The context to retrieve from
The default value to return if the key is not found
Returns: A - The value associated with the key, or the default
updated
def updated[A](ctx: C)(key: Key[A], value: A): C
Creates a copy of the context with the given value associated with the given key.
The key to associate the value with
Returns: C - A new context with the updated value
root
The root context, from which all other contexts are derived.
Syntax Extensions
The ContextSyntax class provides convenient syntax for working with contexts:
import org.typelevel.otel4s.context.syntax._
val ctx: C = ???
val key: Key[String] = ???
// Get a value
val maybeValue: Option[String] = ctx.get(key)
// Get with default
val value: String = ctx.getOrElse(key, "default")
// Update context
val newCtx: C = ctx.updated(key, "new value")
Summoning Contextual
Contextual.apply[C](implicit c: Contextual[C]): Contextual[C]
Summons an implicit Contextual instance.
Example:
val contextual = Contextual[MyContext]
Key
A Key[A] represents a typed key for storing and retrieving values in a context.
Type Signature
sealed trait Key[A] {
val name: String
}
Properties
The debug name of the key
Key.Provider
Provides a way to create unique context keys.
sealed trait Provider[F[_], K[X] <: Key[X]] {
def uniqueKey[A](name: String): F[K[A]]
}
uniqueKey
def uniqueKey[A](name: String): F[K[A]]
Creates a unique key with the given debug name.
The debug name for the key
Returns: F[K[A]] - A new unique key
Note: Different key instances are always treated as non-equal, even if they have the same name.
LocalProvider
The LocalProvider[F, Ctx] trait provides a way to create cats.mtl.Local instances for managing context in effect types.
Type Signature
sealed trait LocalProvider[F[_], Ctx] {
def local: F[Local[F, Ctx]]
}
Creating LocalProvider
fromIOLocal
LocalProvider.fromIOLocal[F, Ctx](ioLocal: IOLocal[Ctx]): LocalProvider[F, Ctx]
Creates a LocalProvider that derives a Local instance from an IOLocal.
The IOLocal to use for context storage
Example:
import cats.effect.IOLocal
import cats.effect.IO
IOLocal(Context.root).map { implicit ioLocal =>
val provider = LocalProvider[IO, Context]
}
fromLocal
LocalProvider.fromLocal[F, Ctx](l: Local[F, Ctx]): LocalProvider[F, Ctx]
Creates a LocalProvider that returns the given Local instance.
The Local instance to use
Example:
implicit val local: Local[F, Context] = ???
val provider = LocalProvider[F, Context]
fromLiftIO
LocalProvider.fromLiftIO[F, Ctx]: LocalProvider[F, Ctx]
Creates a LocalProvider that creates a new IOLocal under the hood.
Note: Every invocation of local creates a new IOLocal. If you want to share an IOLocal with other components, use fromIOLocal.
Example:
implicit val liftIO: LiftIO[F] = ???
implicit val contextual: Contextual[Context] = ???
val provider = LocalProvider.fromLiftIO[F, Context]
Implicit Resolution
The LocalProvider companion object provides several implicit instances:
- From IOLocal: If an implicit
IOLocal[Ctx] is available
- From Local: If an implicit
Local[F, Ctx] is available
- From LiftIO: If
LiftIO[F] and Contextual[Ctx] are available
Example:
import cats.effect.{IO, IOLocal}
// Automatically derives LocalProvider from IOLocal
IOLocal(Context.root).flatMap { implicit ioLocal =>
// LocalProvider[IO, Context] is available implicitly
val provider = LocalProvider[IO, Context]
???
}
Context Propagation
Context propagation is handled through the TextMapGetter, TextMapUpdater, and TextMapPropagator traits.
TextMapGetter
Extracts context from a carrier (e.g., HTTP headers).
trait TextMapGetter[A] {
def get(carrier: A, key: String): Option[String]
def keys(carrier: A): Iterable[String]
}
TextMapUpdater
Injects context into a carrier.
trait TextMapUpdater[A] {
def updated(carrier: A, key: String, value: String): A
}
Usage with Tracer
The context propagation is typically used with Tracer:
// Extract context from headers and create child span
val headers: Map[String, String] = ???
tracer.joinOrRoot(headers) {
tracer.span("operation").use { span =>
// This span is a child of the span from the headers
???
}
}
// Inject context into headers
val carrier: Map[String, String] = Map.empty
tracer.propagate(carrier).flatMap { headersWithContext =>
// headersWithContext contains trace propagation headers
???
}
Complete Example
import cats.effect.{IO, IOLocal}
import cats.mtl.Local
import org.typelevel.otel4s.context._
case class MyContext(values: Map[String, String])
object MyContext {
implicit val contextual: Contextual[MyContext] = new Contextual[MyContext] {
type Key[A] = String
def get[A](ctx: MyContext)(key: String): Option[A] =
ctx.values.get(key).asInstanceOf[Option[A]]
def updated[A](ctx: MyContext)(key: String, value: A): MyContext =
MyContext(ctx.values.updated(key, value.toString))
def root: MyContext = MyContext(Map.empty)
}
}
for {
ioLocal <- IOLocal(MyContext.root)
provider = LocalProvider.fromIOLocal[IO, MyContext](ioLocal)
local <- provider.local
// Use the local context
_ <- local.scope(MyContext(Map("key" -> "value"))) {
local.ask[MyContext].flatMap { ctx =>
IO.println(s"Context: $ctx")
}
}
} yield ()
See Also