Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an example of otel instrumentation usage #660

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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