Skip to content

Commit

Permalink
Add an example of otel instrumentation usage (#660)
Browse files Browse the repository at this point in the history
* Add an example how to use otel instrumentation

* Add doc (not finished)

* Make instrumentation work

* Attempt (failed) using Tracing with turned on instrumention

* Use GlobalOpenTelemtry tracer

* Fix internal health span

* Improve docs
  • Loading branch information
grouzen authored May 20, 2023
1 parent f179976 commit 9cd34a2
Show file tree
Hide file tree
Showing 16 changed files with 284 additions and 37 deletions.
14 changes: 13 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ addCommandAlias("fmt", "all scalafmtSbt scalafmt test:scalafmt")
addCommandAlias("check", "ciCheck;docsCheck")
addCommandAlias("ciCheck", "all scalafmtSbtCheck scalafmtCheck test:scalafmtCheck")
addCommandAlias("docsCheck", "docs/checkReadme;docs/checkGithubWorkflow")
addCommandAlias("compileExamples", "opentracingExample/compile;opentelemetryExample/compile")
addCommandAlias(
"compileExamples",
"opentracingExample/compile;opentelemetryExample/compile;opentelemetryInstrumentationExample/compile"
)

lazy val root =
project
Expand Down Expand Up @@ -81,6 +84,15 @@ lazy val opentelemetryExample =
.settings(libraryDependencies := Dependencies.opentelemetryExample)
.dependsOn(opentelemetry)

lazy val opentelemetryInstrumentationExample =
project
.in(file("opentelemetry-instrumentation-example"))
.settings(stdSettings("opentelemetry-instrumentation-example"))
.settings(publish / skip := true)
.settings(onlyWithScala2)
.settings(libraryDependencies := Dependencies.opentelemetryInstrumentationExample)
.dependsOn(opentelemetry)

lazy val docs =
project
.in(file("zio-telemetry-docs"))
Expand Down
2 changes: 1 addition & 1 deletion docs/opentelemetry-example.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ id: opentelemetry-example
title: "OpenTelemetry Example"
---

You can find the source code [here](https://github.com/zio/zio-telemetry/tree/series/2.x/opentracing-example).
You can find the source code [here](https://github.com/zio/zio-telemetry/tree/series/2.x/opentelemetry-example).

For an explanation in more detail, check the [OpenTracing Example](opentracing-example.md).

Expand Down
43 changes: 43 additions & 0 deletions docs/opentelemetry-instrumentation-example.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
id: opentelemetry-instrumentation-example
title: "OpenTelemetry Automatic Instrumentation Example"
---

You can find the source code [here](https://github.com/zio/zio-telemetry/tree/series/2.x/opentelemetry-instrumentation-example).

Firstly, download OpenTelemetry JVM agent JAR:
```bash
OTEL_AGENT_PATH=$(cs fetch --classpath "io.opentelemetry.javaagent:opentelemetry-javaagent:latest.release")
```

Then start Jaeger by running the following command:
```bash
docker run --rm -it \
-e COLLECTOR_OTLP_ENABLED=true \
-p 14250:14250 \
-p 16686:16686 \
-p 4317:4317 \
jaegertracing/all-in-one:1.42
```

Then start the server application
```bash
sbt -J-javaagent:$OTEL_AGENT_PATH \
-J-Dotel.service.name=example-server \
-J-Dotel.traces.sampler=always_on \
-J-Dotel.traces.exporter=otlp \
-J-Dotel.metrics.exporter=none \
"opentelemetryInstrumentationExample/runMain zio.telemetry.opentelemetry.instrumentation.example.ServerApp"
```

and the client application which will send one request to the server application
```bash
sbt -J-javaagent:$OTEL_AGENT_PATH \
-J-Dotel.service.name=example-client \
-J-Dotel.traces.sampler=always_on \
-J-Dotel.traces.exporter=otlp \
-J-Dotel.metrics.exporter=none \
"opentelemetryInstrumentationExample/runMain zio.telemetry.opentelemetry.instrumentation.example.ClientApp"
```

Head over to [http://localhost:16686/](http://localhost:16686/) to see the result.
23 changes: 16 additions & 7 deletions docs/opentelemetry.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,22 @@ import zio._
ZIO.serviceWithZIO[Baggage] { baggage =>
val carrier = OutgoingContextCarrier.default()

for {
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 ()

for {
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)
```

Expand All @@ -99,9 +102,15 @@ ZIO.serviceWithZIO[Tracing] { tracing =>
val propagator = TraceContextPropagator.default
val kernel = mutable.Map().empty

tracing.inject(propagator, OutgoingContextCarrier.default(kernel)) @@ root("span of upstream service") *>
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
Expand All @@ -115,7 +124,7 @@ Since [version 1.25.0](https://github.com/open-telemetry/opentelemetry-java-inst
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`.
using `ContextStorage` backed by OpenTelemetry's `Context` and `Tracer` provided by globally registered `TracerProvider`.

```scala
import zio.telemetry.opentelemetry.tracing.Tracing
Expand All @@ -132,7 +141,7 @@ val app =
ZIO.logInfo("Hello") @@ root("root span", SpanKind.INTERNAL, errorMapper)
}.provide(
Tracing.live,
ContextStorage.openTelemetryContext, // <<<
JaegerTracer.live
ContextStorage.openTelemetryContext, // <<< ContextStorage
ZLayer.fromZIO(ZIO.attempt(GlobalOpenTelemetry.getTracer("hello"))) // <<< Tracer
)
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
server {
host = "0.0.0.0"
port = 9000
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package zio.telemetry.opentelemetry.instrumentation.example

import sttp.client3.httpclient.zio.HttpClientZioBackend
import zio.config.magnolia.descriptor
import zio.config.typesafe.TypesafeConfig
import zio.telemetry.opentelemetry.instrumentation.example.config.AppConfig
import zio._
import zio.config.ReadError
import zio.telemetry.opentelemetry.instrumentation.example.http.HttpClient

object ClientApp extends ZIOAppDefault {

private val configLayer: Layer[ReadError[String], AppConfig] =
TypesafeConfig.fromResourcePath(descriptor[AppConfig])

private val httpBackendLayer: TaskLayer[Backend] =
ZLayer.scoped {
ZIO.acquireRelease(HttpClientZioBackend())(_.close().ignore)
}

override def run: Task[ExitCode] =
ZIO
.serviceWithZIO[HttpClient](_.health.exitCode)
.provide(
configLayer,
httpBackendLayer,
HttpClient.live
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package zio.telemetry.opentelemetry.instrumentation.example

import io.opentelemetry.api.GlobalOpenTelemetry
import io.opentelemetry.api.trace.Tracer
import zio._
import zio.config.ReadError
import zio.config.typesafe.TypesafeConfig
import zio.config.magnolia._
import zio.telemetry.opentelemetry.context.ContextStorage
import zio.telemetry.opentelemetry.instrumentation.example.config.AppConfig
import zio.telemetry.opentelemetry.instrumentation.example.http.{ HttpServer, HttpServerApp }
import zio.telemetry.opentelemetry.tracing.Tracing

object ServerApp extends ZIOAppDefault {

private val configLayer: Layer[ReadError[String], AppConfig] =
TypesafeConfig.fromResourcePath(descriptor[AppConfig])

private val globalTracerLayer: TaskLayer[Tracer] =
ZLayer.fromZIO(
ZIO.attempt(GlobalOpenTelemetry.getTracer("zio.telemetry.opentelemetry.instrumentation.example.ServerApp"))
)

override def run: Task[ExitCode] =
ZIO
.serviceWithZIO[HttpServer](_.start.exitCode)
.provide(
configLayer,
HttpServer.live,
HttpServerApp.live,
Tracing.live,
globalTracerLayer,
ContextStorage.openTelemetryContext
)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package zio.telemetry.opentelemetry.instrumentation.example.config

case class AppConfig(server: ServerConfig)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package zio.telemetry.opentelemetry.instrumentation.example.config

case class ServerConfig(host: String, port: Int)
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package zio.telemetry.opentelemetry.instrumentation.example.http

import sttp.client3._
import sttp.model.Uri
import zio._
import zio.telemetry.opentelemetry.instrumentation.example.Backend
import zio.telemetry.opentelemetry.instrumentation.example.config.AppConfig

case class HttpClient(backend: Backend, config: AppConfig) {

private val backendUrl =
Uri
.safeApply(config.server.host, config.server.port)
.map(_.withPath("status"))
.left
.map(new IllegalArgumentException(_))

def health: Task[String] =
for {
url <- ZIO.fromEither(backendUrl)
response <- backend.send(basicRequest.get(url.withPath("health")).response(asStringAlways))
result = response.body
} yield result

}

object HttpClient {

val live: RLayer[AppConfig with Backend, HttpClient] =
ZLayer.fromFunction(HttpClient.apply _)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package zio.telemetry.opentelemetry.instrumentation.example.http

import zio._
import zhttp.service.Server
import zio.Console.printLine
import zio.telemetry.opentelemetry.instrumentation.example.config.AppConfig

case class HttpServer(config: AppConfig, httpServerApp: HttpServerApp) {

def start: ZIO[Any, Throwable, Nothing] =
for {
_ <- Server.start(config.server.port, httpServerApp.routes)
_ <- printLine(s"HttpServer started on port ${config.server.port}")
never <- ZIO.never
} yield never

}

object HttpServer {

val live: URLayer[AppConfig with HttpServerApp, HttpServer] =
ZLayer.fromFunction(HttpServer.apply _)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package zio.telemetry.opentelemetry.instrumentation.example.http

import zhttp.http._
import zio._
import zio.telemetry.opentelemetry.tracing.Tracing

case class HttpServerApp(tracing: Tracing) {

import tracing.aspects._

val routes: HttpApp[Any, Throwable] =
Http.collectZIO { case _ @Method.GET -> _ / "health" =>
health @@ span("health-endpoint")
}

def health: UIO[Response] =
for {
_ <- tracing.addEvent("executing health logic")
_ <- tracing.setAttribute("zio", "telemetry")
response <- ZIO.succeed(Response.ok)
} yield response

}

object HttpServerApp {

val live: URLayer[Tracing, HttpServerApp] =
ZLayer.fromFunction(HttpServerApp.apply _)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package zio.telemetry.opentelemetry.instrumentation

import sttp.capabilities.WebSockets
import sttp.capabilities.zio.ZioStreams
import sttp.client3.SttpBackend
import zio.Task

package object example {

type Backend = SttpBackend[Task, ZioStreams with WebSockets]

}
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,11 @@ object ContextStorage {
val openTelemetryContext: ULayer[ContextStorage] =
ZLayer.succeed {
new ContextStorage {
override def get(implicit trace: Trace): UIO[Context] = ZIO.succeed(Context.current())
override def get(implicit trace: Trace): UIO[Context] =
ZIO.succeed(Context.current())

override def set(context: Context)(implicit trace: Trace): UIO[Unit] = ZIO.succeed(context.makeCurrent()).unit
override def set(context: Context)(implicit trace: Trace): UIO[Unit] =
ZIO.succeed(context.makeCurrent()).unit

override def getAndSet(context: Context)(implicit trace: Trace): UIO[Context] =
ZIO.succeed {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,16 +223,19 @@ object TracingTest extends ZIOSpecDefault {

val carrier: mutable.Map[String, String] = mutable.Map().empty

val roundtrip =
(for {
_ <-
tracing.inject(TraceContextPropagator.default, OutgoingContextCarrier.default(carrier)) @@
span("foo")
_ <-
ZIO.unit @@
extractSpan(TraceContextPropagator.default, IncomingContextCarrier.default(carrier), "baz") @@
span("bar")
} yield ()) @@ span("ROOT")

for {
_ <-
(for {
_ <-
tracing.inject(TraceContextPropagator.default, OutgoingContextCarrier.default(carrier)) @@ span("foo")
_ <-
ZIO.unit @@
extractSpan(TraceContextPropagator.default, IncomingContextCarrier.default(carrier), "baz") @@
span("bar")
} yield ()) @@ span("ROOT")
_ <- roundtrip
spans <- getFinishedSpans
root = spans.find(_.getName == "ROOT")
foo = spans.find(_.getName == "foo")
Expand Down
Loading

0 comments on commit 9cd34a2

Please sign in to comment.