Skip to main content
Version: 2.0.x


OpenCensus is a stats collection and distributed tracing framework. Well known implementations are Jaeger and Zipkin.


First, add the following dependency to your build.sbt:

"dev.zio" %% "zio-opencensus" % "<version>"


To use ZIO Telemetry, you will need a Tracing service in your environment. You also need to provide a tracer implementation:

import zio.telemetry.opencensus.Tracing
import zio.telemetry.opencensus.implicits._
import zio._
import io.opencensus.trace.Status

val tracerLayer = ZLayer.succeed(io.opencensus.trace.Tracing.getTracer)

val errorMapper = ErrorMapper[Throwable] { case _ => Status.UNKNOWN }

val app =
ZIO.serviceWithZIO[Tracing] { tracing =>
import tracing.aspects._

(for {
_ <- tracing.putAttributes(Map("foo" -> "bar"))
message <- Console.readline
} yield message) @@ root("/app")
}.provide(, tracerLayer)

After importing import tracing.aspects._, additional ZIOAspect combinators on ZIOs are available to support starting child spans and adding attributes.

ZIO.serviceWithZIO[Tracing] { tracing =>
import tracing.aspects._

// start a new root span and set some attributes
val zio1 = ZIO.unit @@ root("root span", attributes = ("foo", "bar))
// start a child of the current span
val zio2 = ZIO.unit @@ span("child span", attributes = Map.empty)

To propagate contexts across process boundaries, extraction and injection can be used. The current span context is injected into a carrier, which is passed through some side channel to the next process. There it is injected back and a child span of it is started.

Due to the use of the (mutable) OpenCensus carrier APIs, injection and extraction are not referentially transparent.

ZIO.serviceWithZIO[Tracing] { tracing =>
import tracing.aspects._

val textFormat = Tracing.getPropagationComponent().getB3Format()
val carrier: mutable.Map[String, String] = mutable.Map().empty

val getter: TextFormat.Getter[mutable.Map[String, String]] = new TextFormat.Getter[mutable.Map[String, String]] {
override def keys(carrier: mutable.Map[String, String]): lang.Iterable[String] =

override def get(carrier: mutable.Map[String, String], key: String): String =

val setter: TextFormat.Setter[mutable.Map[String, String]] = new TextFormat.Setter[mutable.Map[String, String]] {
override def put(carrier: mutable.Map[String, String], key: String, value: String): Unit =
carrier.update(key, value)

val zio1 = tracing.inject(textFormat, carrier, setter) @@
root("root span", attributes = Map.empty)

val zio2 = ZIO.unit @@
span("child span", attributes = Map(("foo", "bar"))) @@
fromRootSpan(textFormat, carrier, getter, "foo", attributes = Map.empty)