ZIO telemetry is a purely-functional, type-safe OpenTracing client.
OpenTracing is a standard and API for distributed tracing, i.e. collecting timings, and logs across process boundaries. Well known implementations are Jaeger and Zipkin.
To use ZIO telemetry, you will need a Clock
and a OpenTelemetry
service in your
environment:
import io.opentracing.mock.MockTracer
import io.opentracing.propagation._
import zio._
import zio.clock.Clock
import zio.telemetry.opentracing._
val tracer = new MockTracer
val managedEnvironment =
for {
clock_ <- ZIO.environment[Clock].toManaged_
ot <- managed(tracer)
} yield new Clock with Telemetry {
override val clock: Clock.Service[Any] = clock_.clock
override def telemetry: Telemetry.Service = ot.telemetry
}
After importing import zio.telemetry.opentracing._
, additional combinators
on ZIO
s are available to support starting child spans, tagging, logging and
managing baggage.
// start a new root span and set some baggage item
val zio = UIO.unit
.setBaggage("foo", "bar")
.root("root span")
// start a child of the current span, set a tag and log a message
val zio = UIO.unit
.tag("http.status_code", 200)
.log("doing some serious work here!")
.span("child span")
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. For the example we use the standardized TextMap
carrier. For details about extraction and injection, please refer to
OpenTracing Documentation.
Due to the use of the (mutable) OpenTracing carrier APIs, injection and extraction are not referentially transparent.
val buffer = new TextMapAdapter(mutable.Map.empty.asJava)
for {
_ <- zio.inject(Format.Builtin.TEXT_MAP, buffer)
_ <- zio.spanFrom(Format.Builtin.TEXT_MAP, buffer, "child of remote span")
} yield buffer
Firstly, start Jaeger by running following command:
docker run -d --name jaeger \
-e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
-p 5775:5775/udp \
-p 6831:6831/udp \
-p 6832:6832/udp \
-p 5778:5778 \
-p 16686:16686 \
-p 14268:14268 \
-p 9411:9411 \
jaegertracing/all-in-one:1.6
To check if it's running properly visit Jaeger UI. More info can be found here.
Our application contains two services:
Represents entry point to example distributed system. It exposes /statuses
endpoint which returns list of system's services statuses.
In order to start service run:
sbt "example/runMain zio.telemetry.example.ProxyServer"
If it's start properly it should be available on https://0.0.0.0:8080/statuses
.
Represents "internal" service of the system. It exposes /status
endpoint which returns service status.
In order to start service run:
sbt "example/runMain zio.telemetry.example.BackendServer"
If it's start properly it should be available on https://0.0.0.0:9000/status
.
Configuration is given in application.conf.
After both services are properly started, running following command
curl -X GET https://0.0.0.0:8080/statuses
should return following response:
{
"data": [
{
"name": "backend",
"version": "1.0.0",
"status": "up"
},
{
"name": "proxy",
"version": "1.0.0",
"status": "up"
}
]
}
Simultaneously, it will create trace that will be stored in Jaeger backend.