Skip to main content
In OpenTelemetry, Baggage is a mechanism for propagating key-value pairs across service boundaries. It allows applications to attach contextual metadata (e.g., request IDs, user IDs, or debug flags) to distributed traces, ensuring that relevant data flows alongside requests without modifying their payloads.

Use Cases

Baggage is primarily used for:
  • Enriching logs and traces with contextual information
  • Debugging and monitoring distributed applications
  • Passing request-scoped metadata without modifying the business logic

What is BaggageManager?

BaggageManager is a functional abstraction that provides an interface to manage Baggage. It extends Local[F, Baggage] and can:
1

Read the current Baggage

Use the current method to access the current baggage.
2

Modify Baggage in a scoped manner

Use local or scope methods for scoped modifications.
3

Read specific entries

Use get or getValue to retrieve specific baggage entries.

Getting the BaggageManager

You can obtain a BaggageManager instance from the autoconfigured OtelJava:
import cats.effect.IO
import org.typelevel.otel4s.baggage.BaggageManager
import org.typelevel.otel4s.oteljava.OtelJava

OtelJava.autoConfigured[IO]().use { otel4s =>
  val baggageManager: BaggageManager[IO] = otel4s.baggageManager
  IO.println("BaggageManager: " + baggageManager)
}

Using BaggageManager

Baggage can be modified exclusively in a scoped-manner.

Reading the Current Baggage

To read the current baggage context:
def printBaggage(implicit bm: BaggageManager[IO]): IO[Unit] =
  BaggageManager[IO].current.flatMap(b => IO.println(s"Baggage: $b"))

Setting and Modifying Baggage

You can modify baggage in a scoped manner using local or scope:
import org.typelevel.otel4s.baggage.Baggage

def withUserId[A](fa: IO[A])(implicit bm: BaggageManager[IO]): IO[A] =
  bm.local(b => b.updated("user-id", "12345"))(fa)

def withScopedBaggage[A](fa: IO[A])(implicit bm: BaggageManager[IO]): IO[A] =
  bm.scope(Baggage.empty.updated("request-id", "req-abc"))(fa)

Using local

The local method modifies the current baggage by applying a transformation function:
import cats.effect.IO
import org.typelevel.otel4s.baggage.BaggageManager

def addUserContext[A](userId: String)(effect: IO[A])(implicit bm: BaggageManager[IO]): IO[A] =
  bm.local(baggage => baggage.updated("user-id", userId))(effect)

Using scope

The scope method replaces the entire baggage context for the duration of an effect:
import org.typelevel.otel4s.baggage.Baggage

def withCustomBaggage[A](effect: IO[A])(implicit bm: BaggageManager[IO]): IO[A] = {
  val customBaggage = Baggage.empty
    .updated("request-id", "req-123")
    .updated("debug-mode", "true")
  
  bm.scope(customBaggage)(effect)
}

Retrieving a Specific Baggage Entry

You can retrieve individual baggage values using getValue:
def fetchBaggageEntry(implicit bm: BaggageManager[IO]): IO[Unit] =
  bm.getValue("user-id").flatMap {
    case Some(userId) => IO.println(s"User ID: $userId")
    case None         => IO.println("User ID not found in baggage")
  }

Complete Example

Here’s a complete example showing how to use baggage in a service:
import cats.effect.{IO, IOApp}
import org.typelevel.otel4s.baggage.{Baggage, BaggageManager}
import org.typelevel.otel4s.oteljava.OtelJava

object BaggageExample extends IOApp.Simple {

  def processRequest(userId: String)(implicit bm: BaggageManager[IO]): IO[Unit] =
    bm.local(_.updated("user-id", userId)) {
      for {
        _ <- IO.println("Processing request...")
        _ <- logUserContext
        _ <- performOperation
      } yield ()
    }

  def logUserContext(implicit bm: BaggageManager[IO]): IO[Unit] =
    bm.getValue("user-id").flatMap {
      case Some(userId) => IO.println(s"Current user: $userId")
      case None         => IO.println("No user context")
    }

  def performOperation(implicit bm: BaggageManager[IO]): IO[Unit] =
    IO.println("Performing operation with baggage context")

  def run: IO[Unit] =
    OtelJava.autoConfigured[IO]().use { otel4s =>
      implicit val bm: BaggageManager[IO] = otel4s.baggageManager
      processRequest("user-12345")
    }
}

Best Practices

1

Keep baggage small

Baggage is propagated in every request, so keep the data minimal to avoid performance overhead.
2

Use meaningful keys

Use clear, consistent naming conventions for baggage keys across your services.
3

Scope modifications appropriately

Always use scoped modifications (local or scope) to ensure baggage changes don’t leak beyond their intended context.
4

Don't store sensitive data

Baggage can be logged and propagated across service boundaries, so avoid storing sensitive information.

Build docs developers (and LLMs) love