OpenTelemetry
OpenTelemetry is a collection of tools, APIs, and SDKs. You can use it to instrument, generate, collect, and export telemetry data for analysis in order to understand your software's performance and behavior. Well known implementations are Jaeger and Zipkin.
Installation
First, add the following dependency to your build.sbt:
"dev.zio" %% "zio-opentelemetry" % "<version>"
Usage
Tracing
To use ZIO Telemetry, you will need a Tracing
service in your environment. You also need to provide a Tracer
(for this example we use JaegerTracer.live
from opentelemetry-example
module) and ContextStorage
implementation.
import zio.telemetry.opentelemetry.tracing.Tracing
import zio.telemetry.opentelemetry.context.ContextStorage
import zio.telemetry.opentelemetry.example.JaegerTracer
import io.opentelemetry.api.trace.{ SpanKind, StatusCode }
import zio._
val statusMapper = StatusMapper.failureThrowable(_ => StatusCode.UNSET)
val app =
ZIO.serviceWithZIO[Tracing] { tracing =>
// import available aspects to create spans conveniently
import tracing.aspects._
val zio = for {
// set an attribute to the current span
_ <- tracing.setAttribute("zio", "telemetry")
// add an event to the current span
_ <- tracing.addEvent("before readline")
// some logic
message <- Console.readline
// add another event to the current span
_ <- tracing.addEvent("after readline")
} yield message
// create a root span out of `zio`
zio @@ root("root span", SpanKind.INTERNAL, statusMapper)
}.provide(Tracing.live, ContextStorage.fiberRef, JaegerTracer.live)
Baggage
To use Baggage API, you also will need a Baggage
service in your environment. You also need to provide
ContextStorage
implementation.
import zio.telemetry.opentelemetry.baggage.Baggage
import zio.telemetry.opentelemetry.baggage.propagation.BaggagePropagator
import zio.telemetry.opentelemetry.context.ContextStorage
import zio._
val app =
ZIO.serviceWithZIO[Baggage] { baggage =>
val carrier = OutgoingContextCarrier.default()
val upstream = for {
// add new key/value into the baggage of current tracing context
_ <- baggage.set("zio", "telemetry")
// import current baggage data into carrier so it can be used by downstream consumer
_ <- baggage.inject(BaggagePropagator.default, carrier)
} yield ()
val downstream = for {
// extract current baggage data from the carrier
_ <- baggage.extract(BaggagePropagator.default, IncomingContextCarrier.default(carrier.kernel))
// get value from the extracted baggage
data <- baggage.get("zio")
} yield data
upstream *> downstream
}.provide(Baggage.live, ContextStorage.fiberRef)
Context Propagation
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 extracted back and a child span of it is started.
Due to the use of the (mutable) OpenTelemetry carrier APIs, injection and extraction are not referentially transparent.
ZIO.serviceWithZIO[Tracing] { tracing =>
import tracing.aspects._
val propagator = TraceContextPropagator.default
val kernel = mutable.Map().empty
val upstream =
tracing.inject(propagator, OutgoingContextCarrier.default(kernel)) @@ root("span of upstream service")
val downstream =
extractSpan(propagator, IncomingContextCarrier.default(kernel), "span of downstream service")
upstream *> downstream
}.provide(Tracing.live, ContextStorage.fiberRef, JaegerTracer.live)
Usage with OpenTelemetry automatic instrumentation
OpenTelemetry provides a JVM agent for automatic instrumentation which supports many popular Java libraries.
Since version 1.25.0 OpenTelemetry JVM agent supports ZIO.
To enable interoperability between automatic instrumentation and zio-opentelemetry
, Tracing
has to be created
using ContextStorage
backed by OpenTelemetry's Context
and Tracer
provided by globally registered TracerProvider
.
import zio.telemetry.opentelemetry.tracing.Tracing
import zio.telemetry.opentelemetry.context.ContextStorage
import zio.telemetry.opentelemetry.example.JaegerTracer
import io.opentelemetry.api.trace.{SpanKind, StatusCode}
import zio._
val statusMapper = StatusMapper.failureNoException(_ => StatusCode.UNSET)
val app =
ZIO.serviceWithZIO[Tracing] { tracing =>
import tracing.aspects._
ZIO.logInfo("Hello") @@ root("root span", SpanKind.INTERNAL, statusMapper)
}.provide(
Tracing.live,
ContextStorage.openTelemetryContext, // <<< ContextStorage
ZLayer.fromZIO(ZIO.attempt(GlobalOpenTelemetry.getTracer("hello"))) // <<< Tracer
)