Skip to content

Commit

Permalink
Add logging support for opentelemetry (#752)
Browse files Browse the repository at this point in the history
* Initial commit

* Map LogLevel to Severity

* Add test case for filterLogLevel

* Add docs for Logging

* Add multiple loggers test case

* Make opentelemetry-example work with Fluentbit backend

* Fix otel logger in example

* Rename OpenTelemetryContextStorage

* Remove seq's data and logs

* fmt

* Rename private method end to endCurrentSpan

* Finish with opentelemetry-example (jaeger and seq)

* Update opentelemetry.md docs

* Add example for logback appender in instrumentation example

* Fix examples compilation
  • Loading branch information
grouzen authored Nov 17, 2023
1 parent c4240da commit b83dea2
Show file tree
Hide file tree
Showing 36 changed files with 731 additions and 235 deletions.
22 changes: 20 additions & 2 deletions docs/opentelemetry-example.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,33 @@ You can find the source code [here](https://github.com/zio/zio-telemetry/tree/se

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

Firstly, start Jaeger by running the following command:
We're going to show an example of how to collect traces and logs. For this, we will use
[Jaeger](https://www.jaegertracing.io/) and [Seq](https://datalust.co/seq).

Start Jaeger by running the following command:
```bash
docker run --rm -it \
-d \
-e COLLECTOR_OTLP_ENABLED=true \
-p 4317:4317 \
-p 16686:16686 \
jaegertracing/all-in-one:1.47
```

To run Seq, you also need to specify an admin password (user is `admin`):
```bash
PH=$(echo 'admin123' | docker run --rm -i datalust/seq config hash)

docker run \
-d \
--restart unless-stopped \
-e ACCEPT_EULA=Y \
-e SEQ_FIRSTRUN_ADMINPASSWORDHASH="$PH" \
-p 80:80 \
-p 5341:5341 \
datalust/seq
```

Then start the proxy application
```bash
sbt "opentelemetryExample/runMain zio.telemetry.opentelemetry.example.ProxyApp"
Expand All @@ -29,4 +47,4 @@ Now perform the following request:
```bash
curl -X GET http://localhost:8080/statuses
```
and head over to [http://localhost:16686/](http://localhost:16686/) to see the result.
and head over to [Jaeger UI](http://localhost:16686/) and [Seq UI](http://localhost:80/) to see the result.
35 changes: 29 additions & 6 deletions docs/opentelemetry-instrumentation-example.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,49 @@ 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")
```
Firstly, we need to start the observability backends ([Jaeger](https://www.jaegertracing.io/) and [Seq](https://datalust.co/seq))

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

To run Seq, you also need to specify an admin password (user is `admin`):
```bash
PH=$(echo 'admin123' | docker run --rm -i datalust/seq config hash)

docker run \
-d \
--restart unless-stopped \
-e ACCEPT_EULA=Y \
-e SEQ_FIRSTRUN_ADMINPASSWORDHASH="$PH" \
-p 80:80 \
-p 5341:5341 \
datalust/seq
```

After this, we can kick off our application to generate some metrics.

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

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.logs.exporter=otlp \
-J-Dotel.exporter.otlp.logs.protocol="http/protobuf" \
-J-Dotel.exporter.otlp.logs.endpoint="http://localhost:5341/ingest/otlp/v1/logs" \
-J-Dotel.metrics.exporter=none \
"opentelemetryInstrumentationExample/runMain zio.telemetry.opentelemetry.instrumentation.example.ServerApp"
```
Expand All @@ -40,4 +62,5 @@ sbt -J-javaagent:$OTEL_AGENT_PATH \
"opentelemetryInstrumentationExample/runMain zio.telemetry.opentelemetry.instrumentation.example.ClientApp"
```

Head over to [http://localhost:16686/](http://localhost:16686/) to see the result.
Head over to [Jaeger UI](http://localhost:16686/) and [Seq UI](http://localhost:80/) to see the result.

73 changes: 52 additions & 21 deletions docs/opentelemetry.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,12 @@ import zio.telemetry.opentelemetry.example.JaegerTracer
import io.opentelemetry.api.trace.{ SpanKind, StatusCode }
import zio._

val statusMapper = StatusMapper.failureThrowable(_ => StatusCode.UNSET)
val instrumentationScopeName = "com.example.MyApp"
val resourceName = "example-app"

val app =
ZIO.serviceWithZIO[Tracing] { tracing =>
// import available aspects to create spans conveniently
import tracing.aspects._
val statusMapper = StatusMapper.failureThrowable(_ => StatusCode.UNSET)

val app = ZIO.serviceWithZIO[Tracing] { tracing =>
val zio = for {
// set an attribute to the current span
_ <- tracing.setAttribute("zio", "telemetry")
Expand All @@ -46,9 +45,9 @@ val app =
} yield message

// create a root span out of `zio`
zio @@ root("root span", SpanKind.INTERNAL, statusMapper)
zio @@ tracing.aspects.root("root span", SpanKind.INTERNAL, statusMapper)

}.provide(Tracing.live, ContextStorage.fiberRef, JaegerTracer.live)
}.provide(Tracing.live, ContextStorage.fiberRef, JaegerTracer.live(resourceName, instrumentationScopeName))
```

### Baggage
Expand All @@ -62,8 +61,7 @@ import zio.telemetry.opentelemetry.baggage.propagation.BaggagePropagator
import zio.telemetry.opentelemetry.context.ContextStorage
import zio._

val app =
ZIO.serviceWithZIO[Baggage] { baggage =>
val app = ZIO.serviceWithZIO[Baggage] { baggage =>
val carrier = OutgoingContextCarrier.default()

val upstream = for {
Expand All @@ -82,7 +80,42 @@ import zio._

upstream *> downstream

}.provide(Baggage.live, ContextStorage.fiberRef)
}.provide(Baggage.live, ContextStorage.fiberRef)
```

### Logging

To send Log signals, you need to provide an instance of `LoggerProvider`
(for this example we use `SeqLoggerProvider.live` from `opentelemetry-example` module) and add a `ZLogger` implementation that is able
to emit correlated log records to an OpenTelemetry Collector by providing `Logging.live` layer.

```scala
import zio.telemetry.opentelemetry.logging.Logging
import zio.telemetry.opentelemetry.context.ContextStorage
import zio.telemetry.opentelemetry.tracing.Tracing
import zio.telemetry.opentelemetry.example.JaegerTracer
import zio._

val instrumentationScopeName = "com.example.MyApp"
val resourceName = "example-app"

val app = ZIO.serviceWithZIO[Tracing] { tracing =>
ZIO.logDebug("not correlated message with 'my-app1' instrumentation scope")
.provideLayer(Logging.live("my-app1", LogLevel.Debug))

tracing.root("root span")(
ZIO.logInfo("correlated message with 'my-app2' instrumentation scope")
).provideLayer(Logging.live("my-app2"))

ZIO.logAnnotate("zio", "logging")(
ZIO.logInfo("propagate ZIO log annotations to OTEL log attributes")
).provideLayer(Logging.live("my-app3"))
}.provide(
Tracing.live,
JaegerTracer.live(resourceName, instrumentationScopeName),
SeqLoggerProvider.live(resourceName),
ContextStorage.fiberRef
)
```

### Context Propagation
Expand All @@ -97,20 +130,18 @@ are not referentially transparent.

```scala
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")
tracing.inject(propagator, OutgoingContextCarrier.default(kernel)) @@ tracing.aspects.root("span of upstream service")

val downstream =
extractSpan(propagator, IncomingContextCarrier.default(kernel), "span of downstream service")
tracing.extractSpan(propagator, IncomingContextCarrier.default(kernel), "span of downstream service")

upstream *> downstream

}.provide(Tracing.live, ContextStorage.fiberRef, JaegerTracer.live)
}.provide(Tracing.live, ContextStorage.fiberRef, JaegerTracer.live(resourceName, instrumentationScopeName))
```

### Usage with OpenTelemetry automatic instrumentation
Expand All @@ -133,15 +164,15 @@ import zio.telemetry.opentelemetry.example.JaegerTracer
import io.opentelemetry.api.trace.{SpanKind, StatusCode}
import zio._

val instrumentationScopeName = "com.example.MyApp"

val statusMapper = StatusMapper.failureNoException(_ => StatusCode.UNSET)

val app =
ZIO.serviceWithZIO[Tracing] { tracing =>
import tracing.aspects._
ZIO.logInfo("Hello") @@ root("root span", SpanKind.INTERNAL, statusMapper)
val app = ZIO.serviceWithZIO[Tracing] { tracing =>
ZIO.logInfo("Hello") @@ tracing.aspects.root("root span", SpanKind.INTERNAL, statusMapper)
}.provide(
Tracing.live,
ContextStorage.openTelemetryContext, // <<< ContextStorage
ZLayer.fromZIO(ZIO.attempt(GlobalOpenTelemetry.getTracer("hello"))) // <<< Tracer
ContextStorage.openTelemetryContext,
ZLayer.fromZIO(ZIO.attempt(GlobalOpenTelemetry.getTracer(instrumentationScopeName)))
)
```
3 changes: 0 additions & 3 deletions opentelemetry-example/src/main/resources/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,4 @@ backend {
port = 9000
}

tracer {
host = "http://localhost:4317"
}

9 changes: 9 additions & 0 deletions opentelemetry-example/src/main/resources/fluentbit.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[INPUT]
name opentelemetry
listen 0.0.0.0
port 4318

[OUTPUT]
name stdout
match *

Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@ import zio.telemetry.opentelemetry.example.http.{BackendHttpApp, BackendHttpServ
import zio._
import zio.telemetry.opentelemetry.baggage.Baggage
import zio.telemetry.opentelemetry.context.ContextStorage
import zio.telemetry.opentelemetry.example.otel.{JaegerTracer, SeqLoggerProvider}
import zio.telemetry.opentelemetry.logging.Logging
import zio.telemetry.opentelemetry.tracing.Tracing

object BackendApp extends ZIOAppDefault {

private val configLayer = TypesafeConfig.fromResourcePath(descriptor[AppConfig])

override def run: Task[ExitCode] =
private val instrumentationScopeName = "zio.telemetry.opentelemetry.example.BackendApp"
private val resourceName = "opentelemetry-example-backend"

override def run: ZIO[Scope, Any, ExitCode] =
ZIO
.serviceWithZIO[BackendHttpServer](_.start.exitCode)
.provide(
Expand All @@ -23,7 +28,9 @@ object BackendApp extends ZIOAppDefault {
Tracing.live,
Baggage.live(),
ContextStorage.fiberRef,
JaegerTracer.live
JaegerTracer.live(resourceName, instrumentationScopeName),
SeqLoggerProvider.live(resourceName),
Logging.live(instrumentationScopeName)
)

}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,37 @@ package zio.telemetry.opentelemetry.example
import zio._
import zio.config.magnolia._
import zio.config.typesafe.TypesafeConfig
import zio.http.ZClient
import zio.http.Client
import zio.telemetry.opentelemetry.baggage.Baggage
import zio.telemetry.opentelemetry.context.ContextStorage
import zio.telemetry.opentelemetry.example.config.AppConfig
import zio.telemetry.opentelemetry.example.http.{Client, ProxyHttpApp, ProxyHttpServer}
import zio.telemetry.opentelemetry.example.http.{BackendClient, ProxyHttpApp, ProxyHttpServer}
import zio.telemetry.opentelemetry.example.otel.{JaegerTracer, SeqLoggerProvider}
import zio.telemetry.opentelemetry.logging.Logging
import zio.telemetry.opentelemetry.tracing.Tracing

object ProxyApp extends ZIOAppDefault {

private val configLayer = TypesafeConfig.fromResourcePath(descriptor[AppConfig])

private val instrumentationScopeName = "zio.telemetry.opentelemetry.example.ProxyApp"
private val resourceName = "opentelemetry-example-proxy"

override def run: Task[ExitCode] =
ZIO
.serviceWithZIO[ProxyHttpServer](_.start.exitCode)
.provide(
configLayer,
ZClient.default,
Client.live,
Client.default,
BackendClient.live,
ProxyHttpServer.live,
ProxyHttpApp.live,
Tracing.live,
Baggage.live(),
ContextStorage.fiberRef,
JaegerTracer.live
JaegerTracer.live(resourceName, instrumentationScopeName),
SeqLoggerProvider.live(resourceName),
Logging.live(instrumentationScopeName)
)

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

final case class AppConfig(proxy: ProxyConfig, backend: BackendConfig, tracer: TracerConfig)
final case class AppConfig(proxy: ProxyConfig, backend: BackendConfig)

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import zio.telemetry.opentelemetry.example.config.AppConfig

import java.nio.charset.StandardCharsets

case class Client(backend: zio.http.Client, config: AppConfig) {
case class BackendClient(backend: zio.http.Client, config: AppConfig) {

private val backendUrl =
URL
Expand All @@ -30,9 +30,9 @@ case class Client(backend: zio.http.Client, config: AppConfig) {

}

object Client {
object BackendClient {

val live: RLayer[AppConfig with zio.http.Client, Client] =
ZLayer.fromFunction(Client.apply _)
val live: RLayer[AppConfig with zio.http.Client, BackendClient] =
ZLayer.fromFunction(BackendClient.apply _)

}
Loading

0 comments on commit b83dea2

Please sign in to comment.