diff --git a/docs/opentelemetry-example.md b/docs/opentelemetry-example.md index ec406a43..9ca85340 100644 --- a/docs/opentelemetry-example.md +++ b/docs/opentelemetry-example.md @@ -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" @@ -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. \ No newline at end of file +and head over to [Jaeger UI](http://localhost:16686/) and [Seq UI](http://localhost:80/) to see the result. \ No newline at end of file diff --git a/docs/opentelemetry-instrumentation-example.md b/docs/opentelemetry-instrumentation-example.md index e7560e0d..8ca7fb02 100644 --- a/docs/opentelemetry-instrumentation-example.md +++ b/docs/opentelemetry-instrumentation-example.md @@ -5,14 +5,12 @@ 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 \ @@ -20,12 +18,36 @@ docker run --rm -it \ 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" ``` @@ -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. + diff --git a/docs/opentelemetry.md b/docs/opentelemetry.md index b14cf19b..d3619e01 100644 --- a/docs/opentelemetry.md +++ b/docs/opentelemetry.md @@ -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") @@ -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 @@ -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 { @@ -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 @@ -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 @@ -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))) ) ``` diff --git a/opentelemetry-example/src/main/resources/application.conf b/opentelemetry-example/src/main/resources/application.conf index c10be92a..d1c19c12 100644 --- a/opentelemetry-example/src/main/resources/application.conf +++ b/opentelemetry-example/src/main/resources/application.conf @@ -8,7 +8,4 @@ backend { port = 9000 } -tracer { - host = "http://localhost:4317" -} diff --git a/opentelemetry-example/src/main/resources/fluentbit.conf b/opentelemetry-example/src/main/resources/fluentbit.conf new file mode 100644 index 00000000..e6571eed --- /dev/null +++ b/opentelemetry-example/src/main/resources/fluentbit.conf @@ -0,0 +1,9 @@ +[INPUT] + name opentelemetry + listen 0.0.0.0 + port 4318 + +[OUTPUT] + name stdout + match * + diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/BackendApp.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/BackendApp.scala index b4a9870f..eb151829 100644 --- a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/BackendApp.scala +++ b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/BackendApp.scala @@ -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( @@ -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) ) } diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/JaegerTracer.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/JaegerTracer.scala deleted file mode 100644 index 1f2b84d3..00000000 --- a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/JaegerTracer.scala +++ /dev/null @@ -1,40 +0,0 @@ -package zio.telemetry.opentelemetry.example - -import io.opentelemetry.api.common.Attributes -import io.opentelemetry.api.trace.Tracer -import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter -import io.opentelemetry.sdk.OpenTelemetrySdk -import io.opentelemetry.sdk.resources.Resource -import io.opentelemetry.sdk.trace.SdkTracerProvider -import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor -import io.opentelemetry.semconv.resource.attributes.ResourceAttributes -import zio._ -import zio.telemetry.opentelemetry.example.config.AppConfig - -object JaegerTracer { - - def live: RLayer[AppConfig, Tracer] = - ZLayer { - for { - config <- ZIO.service[AppConfig] - tracer <- makeTracer(config.tracer.host) - } yield tracer - } - - def makeTracer(host: String): Task[Tracer] = - for { - spanExporter <- ZIO.attempt(OtlpGrpcSpanExporter.builder().setEndpoint(host).build()) - spanProcessor <- ZIO.succeed(SimpleSpanProcessor.create(spanExporter)) - tracerProvider <- - ZIO.attempt( - SdkTracerProvider - .builder() - .setResource(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, "opentelemetry-example"))) - .addSpanProcessor(spanProcessor) - .build() - ) - openTelemetry <- ZIO.succeed(OpenTelemetrySdk.builder().setTracerProvider(tracerProvider).build()) - tracer <- ZIO.succeed(openTelemetry.getTracer("zio.telemetry.opentelemetry.example.JaegerTracer")) - } yield tracer - -} diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/ProxyApp.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/ProxyApp.scala index fd2194a6..4407167f 100644 --- a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/ProxyApp.scala +++ b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/ProxyApp.scala @@ -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) ) } diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/config/AppConfig.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/config/AppConfig.scala index b38332b6..370c4686 100644 --- a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/config/AppConfig.scala +++ b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/config/AppConfig.scala @@ -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) diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/config/TracerConfig.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/config/TracerConfig.scala deleted file mode 100644 index ef195c31..00000000 --- a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/config/TracerConfig.scala +++ /dev/null @@ -1,3 +0,0 @@ -package zio.telemetry.opentelemetry.example.config - -final case class TracerConfig(host: String) diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/Client.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/BackendClient.scala similarity index 82% rename from opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/Client.scala rename to opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/BackendClient.scala index 32eb9ad6..f1f2eabe 100644 --- a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/Client.scala +++ b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/BackendClient.scala @@ -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 @@ -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 _) } diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/BackendHttpApp.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/BackendHttpApp.scala index 8fbfb208..361f561b 100644 --- a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/BackendHttpApp.scala +++ b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/BackendHttpApp.scala @@ -1,7 +1,7 @@ package zio.telemetry.opentelemetry.example.http import io.opentelemetry.api.trace.SpanKind -import zhttp.http.{->, /, Headers, Http, HttpApp, Method, Response} +import zio.http._ import zio._ import zio.json.EncoderOps import zio.telemetry.opentelemetry.baggage.Baggage @@ -20,14 +20,14 @@ case class BackendHttpApp(tracing: Tracing, baggage: Baggage) { override val kernel: Headers = initial override def getAllKeys(carrier: Headers): Iterable[String] = - carrier.headers.headersAsList.map(_._1) + carrier.headers.map(_.headerName) override def getByKey(carrier: Headers, key: String): Option[String] = - carrier.headers.headerValue(key) + carrier.headers.get(key) } - val routes: HttpApp[Any, Throwable] = + val routes: HttpApp[Any, Nothing] = Http.collectZIO { case request @ Method.GET -> _ / "status" => val carrier = headersCarrier(request.headers) @@ -42,6 +42,7 @@ case class BackendHttpApp(tracing: Tracing, baggage: Baggage) { _ <- tracing.addEvent("event from backend before response") response <- ZIO.succeed(Response.json(ServiceStatus.up("backend").toJson)) _ <- tracing.addEvent("event from backend after response") + _ <- ZIO.logInfo("status processing finished on backend") } yield response } diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/BackendHttpServer.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/BackendHttpServer.scala index 7d4339ce..f2cb9ee9 100644 --- a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/BackendHttpServer.scala +++ b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/BackendHttpServer.scala @@ -1,15 +1,14 @@ package zio.telemetry.opentelemetry.example.http -import zhttp.service.Server -import zio.Console.printLine +import zio.http._ import zio._ import zio.telemetry.opentelemetry.example.config.AppConfig case class BackendHttpServer(config: AppConfig, httpApp: BackendHttpApp) { def start: ZIO[Any, Throwable, Nothing] = - printLine(s"Starting BackendHttpServer on port ${config.backend.port}") *> - Server.start(config.backend.port, httpApp.routes) + ZIO.logInfo(s"Starting BackendHttpServer on port ${config.backend.port}") *> + Server.serve(httpApp.routes).provide(Server.defaultWithPort(config.backend.port)) } diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/ProxyHttpApp.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/ProxyHttpApp.scala index b4e2153e..1f8038d7 100644 --- a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/ProxyHttpApp.scala +++ b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/ProxyHttpApp.scala @@ -1,7 +1,7 @@ package zio.telemetry.opentelemetry.example.http import io.opentelemetry.api.trace.{SpanKind, StatusCode} -import zhttp.http._ +import zio.http._ import zio._ import zio.json.EncoderOps import zio.telemetry.opentelemetry.baggage.Baggage @@ -10,18 +10,18 @@ import zio.telemetry.opentelemetry.context.OutgoingContextCarrier import zio.telemetry.opentelemetry.tracing.propagation.TraceContextPropagator import zio.telemetry.opentelemetry.tracing.{StatusMapper, Tracing} -case class ProxyHttpApp(client: Client, tracing: Tracing, baggage: Baggage) { +case class ProxyHttpApp(client: BackendClient, tracing: Tracing, baggage: Baggage) { import tracing.aspects._ private val statusMapper: StatusMapper[Throwable, Any] = StatusMapper.failureThrowable(_ => StatusCode.UNSET) - val routes: HttpApp[Any, Throwable] = + val routes: HttpApp[Any, Nothing] = Http.collectZIO { case Method.GET -> _ / "statuses" => statuses @@ root("/statuses", SpanKind.SERVER, statusMapper = statusMapper) } - def statuses: Task[Response] = { + def statuses: UIO[Response] = { val carrier = OutgoingContextCarrier.default() for { @@ -30,7 +30,8 @@ case class ProxyHttpApp(client: Client, tracing: Tracing, baggage: Baggage) { _ <- baggage.set("proxy-baggage", "value from proxy") _ <- tracing.inject(TraceContextPropagator.default, carrier) _ <- baggage.inject(BaggagePropagator.default, carrier) - statuses <- client.status(carrier.kernel.toMap) + statuses <- client.status(carrier.kernel.toMap).catchAll(_ => ZIO.succeed(Statuses(List.empty))) + _ <- ZIO.logInfo("statuses processing finished on proxy") } yield Response.json(statuses.toJson) } @@ -38,7 +39,7 @@ case class ProxyHttpApp(client: Client, tracing: Tracing, baggage: Baggage) { object ProxyHttpApp { - val live: URLayer[Client with Tracing with Baggage, ProxyHttpApp] = + val live: URLayer[BackendClient with Tracing with Baggage, ProxyHttpApp] = ZLayer.fromFunction(ProxyHttpApp.apply _) } diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/ProxyHttpServer.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/ProxyHttpServer.scala index d5b1d287..41136acb 100644 --- a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/ProxyHttpServer.scala +++ b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/http/ProxyHttpServer.scala @@ -1,15 +1,14 @@ package zio.telemetry.opentelemetry.example.http -import zhttp.service.Server -import zio.Console.printLine +import zio.http._ import zio._ import zio.telemetry.opentelemetry.example.config.AppConfig case class ProxyHttpServer(config: AppConfig, httpApp: ProxyHttpApp) { def start: ZIO[Any, Throwable, Nothing] = - printLine(s"Starting ProxyHttpServer on port ${config.proxy.port}") *> - Server.start(config.proxy.port, httpApp.routes) + ZIO.logInfo(s"Starting ProxyHttpServer on port ${config.proxy.port}") *> + Server.serve(httpApp.routes).provide(Server.defaultWithPort(config.proxy.port)) } diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/otel/FluentbitLoggerProvider.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/otel/FluentbitLoggerProvider.scala new file mode 100644 index 00000000..b29104d8 --- /dev/null +++ b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/otel/FluentbitLoggerProvider.scala @@ -0,0 +1,33 @@ +package zio.telemetry.opentelemetry.example.otel + +import io.opentelemetry.api.common.Attributes +import io.opentelemetry.api.logs.LoggerProvider +import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter +import io.opentelemetry.sdk.logs.SdkLoggerProvider +import io.opentelemetry.sdk.logs.`export`.SimpleLogRecordProcessor +import io.opentelemetry.sdk.resources.Resource +import io.opentelemetry.semconv.ResourceAttributes +import zio._ + +/** + * https://fluentbit.io/ + */ +object FluentbitLoggerProvider { + + def live(resourceName: String): TaskLayer[LoggerProvider] = + ZLayer( + for { + logRecordExporter <- ZIO.succeed(OtlpHttpLogRecordExporter.builder().build()) + logRecordProcessor <- ZIO.succeed(SimpleLogRecordProcessor.create(logRecordExporter)) + loggerProvider <- + ZIO.attempt( + SdkLoggerProvider + .builder() + .setResource(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, resourceName))) + .addLogRecordProcessor(logRecordProcessor) + .build() + ) + } yield loggerProvider + ) + +} diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/otel/FluentbitTracer.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/otel/FluentbitTracer.scala new file mode 100644 index 00000000..2f65ca2e --- /dev/null +++ b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/otel/FluentbitTracer.scala @@ -0,0 +1,36 @@ +package zio.telemetry.opentelemetry.example.otel + +import io.opentelemetry.api.common.Attributes +import io.opentelemetry.api.trace.Tracer +import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter +import io.opentelemetry.sdk.OpenTelemetrySdk +import io.opentelemetry.sdk.resources.Resource +import io.opentelemetry.sdk.trace.SdkTracerProvider +import io.opentelemetry.sdk.trace.`export`.SimpleSpanProcessor +import io.opentelemetry.semconv.ResourceAttributes +import zio._ + +/** + * https://fluentbit.io/ + */ +object FluentbitTracer { + + def live(resourceName: String, instrumentationScopeName: String): TaskLayer[Tracer] = + ZLayer( + for { + spanExporter <- ZIO.attempt(OtlpHttpSpanExporter.builder().build()) + spanProcessor <- ZIO.succeed(SimpleSpanProcessor.create(spanExporter)) + tracerProvider <- + ZIO.attempt( + SdkTracerProvider + .builder() + .setResource(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, resourceName))) + .addSpanProcessor(spanProcessor) + .build() + ) + openTelemetry <- ZIO.succeed(OpenTelemetrySdk.builder().setTracerProvider(tracerProvider).build()) + tracer <- ZIO.succeed(openTelemetry.getTracer(instrumentationScopeName)) + } yield tracer + ) + +} diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/otel/JaegerTracer.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/otel/JaegerTracer.scala new file mode 100644 index 00000000..00c111a2 --- /dev/null +++ b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/otel/JaegerTracer.scala @@ -0,0 +1,36 @@ +package zio.telemetry.opentelemetry.example.otel + +import io.opentelemetry.api.common.Attributes +import io.opentelemetry.api.trace.Tracer +import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter +import io.opentelemetry.sdk.OpenTelemetrySdk +import io.opentelemetry.sdk.resources.Resource +import io.opentelemetry.sdk.trace.SdkTracerProvider +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor +import io.opentelemetry.semconv.ResourceAttributes +import zio._ + +/** + * https://www.jaegertracing.io/ + */ +object JaegerTracer { + + def live(resourceName: String, instrumentationScopeName: String): TaskLayer[Tracer] = + ZLayer( + for { + spanExporter <- ZIO.attempt(OtlpGrpcSpanExporter.builder().build()) + spanProcessor <- ZIO.succeed(SimpleSpanProcessor.create(spanExporter)) + tracerProvider <- + ZIO.attempt( + SdkTracerProvider + .builder() + .setResource(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, resourceName))) + .addSpanProcessor(spanProcessor) + .build() + ) + openTelemetry <- ZIO.succeed(OpenTelemetrySdk.builder().setTracerProvider(tracerProvider).build()) + tracer <- ZIO.succeed(openTelemetry.getTracer(instrumentationScopeName)) + } yield tracer + ) + +} diff --git a/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/otel/SeqLoggerProvider.scala b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/otel/SeqLoggerProvider.scala new file mode 100644 index 00000000..e9de8ec8 --- /dev/null +++ b/opentelemetry-example/src/main/scala/zio/telemetry/opentelemetry/example/otel/SeqLoggerProvider.scala @@ -0,0 +1,39 @@ +package zio.telemetry.opentelemetry.example.otel + +import io.opentelemetry.api.common.Attributes +import io.opentelemetry.api.logs.LoggerProvider +import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter +import io.opentelemetry.sdk.logs.SdkLoggerProvider +import io.opentelemetry.sdk.logs.`export`.SimpleLogRecordProcessor +import io.opentelemetry.sdk.resources.Resource +import io.opentelemetry.semconv.ResourceAttributes +import zio._ + +/** + * https://datalust.co/seq + */ +object SeqLoggerProvider { + + def live(resourceName: String): TaskLayer[LoggerProvider] = + ZLayer( + for { + logRecordExporter <- + ZIO.succeed( + OtlpHttpLogRecordExporter + .builder() + .setEndpoint("http://localhost:5341/ingest/otlp/v1/logs") + .build() + ) + logRecordProcessor <- ZIO.succeed(SimpleLogRecordProcessor.create(logRecordExporter)) + loggerProvider <- + ZIO.attempt( + SdkLoggerProvider + .builder() + .setResource(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, resourceName))) + .addLogRecordProcessor(logRecordProcessor) + .build() + ) + } yield loggerProvider + ) + +} diff --git a/opentelemetry-instrumentation-example/src/main/resources/logback.xml b/opentelemetry-instrumentation-example/src/main/resources/logback.xml new file mode 100644 index 00000000..f63874cf --- /dev/null +++ b/opentelemetry-instrumentation-example/src/main/resources/logback.xml @@ -0,0 +1,20 @@ + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + diff --git a/opentelemetry-instrumentation-example/src/main/scala/zio/telemetry/opentelemetry/instrumentation/example/ClientApp.scala b/opentelemetry-instrumentation-example/src/main/scala/zio/telemetry/opentelemetry/instrumentation/example/ClientApp.scala index 17a2b7cd..d6bfcd5f 100644 --- a/opentelemetry-instrumentation-example/src/main/scala/zio/telemetry/opentelemetry/instrumentation/example/ClientApp.scala +++ b/opentelemetry-instrumentation-example/src/main/scala/zio/telemetry/opentelemetry/instrumentation/example/ClientApp.scala @@ -1,5 +1,6 @@ package zio.telemetry.opentelemetry.instrumentation.example +import zio.http._ import zio._ import zio.config.ReadError import zio.config.magnolia.descriptor @@ -17,7 +18,7 @@ object ClientApp extends ZIOAppDefault { .serviceWithZIO[HttpClient](_.health.exitCode) .provide( configLayer, - zio.http.Client.default, + Client.default, HttpClient.live ) } diff --git a/opentelemetry-instrumentation-example/src/main/scala/zio/telemetry/opentelemetry/instrumentation/example/ServerApp.scala b/opentelemetry-instrumentation-example/src/main/scala/zio/telemetry/opentelemetry/instrumentation/example/ServerApp.scala index ecba4eab..abdd50fb 100644 --- a/opentelemetry-instrumentation-example/src/main/scala/zio/telemetry/opentelemetry/instrumentation/example/ServerApp.scala +++ b/opentelemetry-instrumentation-example/src/main/scala/zio/telemetry/opentelemetry/instrumentation/example/ServerApp.scala @@ -1,7 +1,9 @@ package zio.telemetry.opentelemetry.instrumentation.example -import io.opentelemetry.api.GlobalOpenTelemetry +import io.opentelemetry.api.{GlobalOpenTelemetry, OpenTelemetry} import io.opentelemetry.api.trace.Tracer +import io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender +import zio.logging.backend.SLF4J import zio._ import zio.config.ReadError import zio.config.typesafe.TypesafeConfig @@ -13,24 +15,36 @@ import zio.telemetry.opentelemetry.tracing.Tracing object ServerApp extends ZIOAppDefault { + override val bootstrap: ZLayer[ZIOAppArgs, Any, Any] = + Runtime.removeDefaultLoggers >>> SLF4J.slf4j + + private val instrumentationScopeName = "zio.telemetry.opentelemetry.instrumentation.example.ServerApp" + 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")) + ZIO.attempt(GlobalOpenTelemetry.getTracer(instrumentationScopeName)) ) + private val globalOpenTelemetry: TaskLayer[OpenTelemetry] = + ZLayer(ZIO.attempt(GlobalOpenTelemetry.get())) + override def run: Task[ExitCode] = - ZIO - .serviceWithZIO[HttpServer](_.start.exitCode) - .provide( - configLayer, - HttpServer.live, - HttpServerApp.live, - Tracing.live, - globalTracerLayer, - ContextStorage.openTelemetryContext - ) + (for { + server <- ZIO.service[HttpServer] + openTelemetry <- ZIO.service[OpenTelemetry] + _ <- ZIO.attempt(OpenTelemetryAppender.install(openTelemetry)) + exitCode <- server.start.exitCode + } yield exitCode).provide( + configLayer, + HttpServer.live, + HttpServerApp.live, + Tracing.live, + ContextStorage.native, + globalTracerLayer, + globalOpenTelemetry + ) } diff --git a/opentelemetry-instrumentation-example/src/main/scala/zio/telemetry/opentelemetry/instrumentation/example/http/HttpServer.scala b/opentelemetry-instrumentation-example/src/main/scala/zio/telemetry/opentelemetry/instrumentation/example/http/HttpServer.scala index f0a67cfd..828b53b9 100644 --- a/opentelemetry-instrumentation-example/src/main/scala/zio/telemetry/opentelemetry/instrumentation/example/http/HttpServer.scala +++ b/opentelemetry-instrumentation-example/src/main/scala/zio/telemetry/opentelemetry/instrumentation/example/http/HttpServer.scala @@ -1,15 +1,14 @@ package zio.telemetry.opentelemetry.instrumentation.example.http import zio._ -import zhttp.service.Server -import zio.Console.printLine +import zio.http._ import zio.telemetry.opentelemetry.instrumentation.example.config.AppConfig case class HttpServer(config: AppConfig, httpServerApp: HttpServerApp) { def start: ZIO[Any, Throwable, Nothing] = - printLine(s"Starting HttpServer on port ${config.server.port}") *> - Server.start(config.server.port, httpServerApp.routes) + ZIO.logInfo(s"Starting HttpServer on port ${config.server.port}") *> + Server.serve(httpServerApp.routes).provide(Server.defaultWithPort(config.server.port)) } diff --git a/opentelemetry-instrumentation-example/src/main/scala/zio/telemetry/opentelemetry/instrumentation/example/http/HttpServerApp.scala b/opentelemetry-instrumentation-example/src/main/scala/zio/telemetry/opentelemetry/instrumentation/example/http/HttpServerApp.scala index 52af5f45..0ac414e4 100644 --- a/opentelemetry-instrumentation-example/src/main/scala/zio/telemetry/opentelemetry/instrumentation/example/http/HttpServerApp.scala +++ b/opentelemetry-instrumentation-example/src/main/scala/zio/telemetry/opentelemetry/instrumentation/example/http/HttpServerApp.scala @@ -1,6 +1,6 @@ package zio.telemetry.opentelemetry.instrumentation.example.http -import zhttp.http._ +import zio.http._ import zio._ import zio.telemetry.opentelemetry.tracing.Tracing @@ -8,7 +8,7 @@ case class HttpServerApp(tracing: Tracing) { import tracing.aspects._ - val routes: HttpApp[Any, Throwable] = + val routes: HttpApp[Any, Nothing] = Http.collectZIO { case _ @Method.GET -> _ / "health" => health @@ span("health-endpoint") } @@ -17,6 +17,7 @@ case class HttpServerApp(tracing: Tracing) { for { _ <- tracing.addEvent("executing health logic") _ <- tracing.setAttribute("zio", "telemetry") + _ <- ZIO.logInfo("health processing finished on the server") response <- ZIO.succeed(Response.ok) } yield response diff --git a/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/context/ContextStorage.scala b/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/context/ContextStorage.scala index 9cbb13ae..cd185e34 100644 --- a/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/context/ContextStorage.scala +++ b/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/context/ContextStorage.scala @@ -3,7 +3,7 @@ package zio.telemetry.opentelemetry.context import io.opentelemetry.context.Context import zio._ -trait ContextStorage { +sealed trait ContextStorage { def get(implicit trace: Trace): UIO[Context] @@ -20,6 +20,59 @@ trait ContextStorage { object ContextStorage { + final class FiberRefContextStorage(private[zio] val ref: FiberRef[Context]) extends ContextStorage { + + override def get(implicit trace: Trace): UIO[Context] = + ref.get + + override def set(context: Context)(implicit trace: Trace): UIO[Unit] = + ref.set(context) + + override def getAndSet(context: Context)(implicit trace: Trace): UIO[Context] = + ref.getAndSet(context) + + override def updateAndGet(f: Context => Context)(implicit trace: Trace): UIO[Context] = + ref.updateAndGet(f) + + override def locally[R, E, A](context: Context)(zio: ZIO[R, E, A])(implicit + trace: Trace + ): ZIO[R, E, A] = + ref.locally(context)(zio) + + override def locallyScoped(context: Context)(implicit trace: Trace): ZIO[Scope, Nothing, Unit] = + ref.locallyScoped(context) + } + + final class NativeContextStorage extends ContextStorage { + + 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 getAndSet(context: Context)(implicit trace: Trace): UIO[Context] = + ZIO.succeed { + val old = Context.current() + val _ = context.makeCurrent() + old + }.uninterruptible + + override def updateAndGet(f: Context => Context)(implicit trace: Trace): UIO[Context] = + ZIO.succeed { + val updated = f(Context.current()) + val _ = updated.makeCurrent() + updated + }.uninterruptible + + override def locally[R, E, A](context: Context)(zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = + ZIO.acquireReleaseWith(get <* set(context))(set)(_ => zio) + + override def locallyScoped(context: Context)(implicit trace: Trace): ZIO[Scope, Nothing, Unit] = + ZIO.acquireRelease(get <* set(context))(set).unit + + } + /** * The main one. Uses [[FiberRef]] as a [[ContextStorage]]. */ @@ -28,29 +81,7 @@ object ContextStorage { FiberRef .make[Context](Context.root()) .flatMap { ref => - ZIO.succeed { - new ContextStorage { - override def get(implicit trace: Trace): UIO[Context] = - ref.get - - override def set(context: Context)(implicit trace: Trace): UIO[Unit] = - ref.set(context) - - override def getAndSet(context: Context)(implicit trace: Trace): UIO[Context] = - ref.getAndSet(context) - - override def updateAndGet(f: Context => Context)(implicit trace: Trace): UIO[Context] = - ref.updateAndGet(f) - - override def locally[R, E, A](context: Context)(zio: ZIO[R, E, A])(implicit - trace: Trace - ): ZIO[R, E, A] = - ref.locally(context)(zio) - - override def locallyScoped(context: Context)(implicit trace: Trace): ZIO[Scope, Nothing, Unit] = - ref.locallyScoped(context) - } - } + ZIO.succeed(new FiberRefContextStorage(ref)) } ) @@ -58,36 +89,7 @@ object ContextStorage { * Uses OpenTelemetry's context storage which is backed by a [[java.lang.ThreadLocal]]. This makes sense only if * [[https://github.com/open-telemetry/opentelemetry-java-instrumentation OTEL instrumentation agent]] is used. */ - val openTelemetryContext: ULayer[ContextStorage] = - ZLayer.succeed { - new ContextStorage { - 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 getAndSet(context: Context)(implicit trace: Trace): UIO[Context] = - ZIO.succeed { - val old = Context.current() - val _ = context.makeCurrent() - old - }.uninterruptible - - override def updateAndGet(f: Context => Context)(implicit trace: Trace): UIO[Context] = - ZIO.succeed { - val updated = f(Context.current()) - val _ = updated.makeCurrent() - updated - }.uninterruptible - - override def locally[R, E, A](context: Context)(zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] = - ZIO.acquireReleaseWith(get <* set(context))(set)(_ => zio) - - override def locallyScoped(context: Context)(implicit trace: Trace): ZIO[Scope, Nothing, Unit] = - ZIO.acquireRelease(get <* set(context))(set).unit - - } - } + val native: ULayer[ContextStorage] = + ZLayer.succeed(new NativeContextStorage) } diff --git a/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/logging/Logging.scala b/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/logging/Logging.scala new file mode 100644 index 00000000..fd5e4c08 --- /dev/null +++ b/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/logging/Logging.scala @@ -0,0 +1,75 @@ +package zio.telemetry.opentelemetry.logging + +import io.opentelemetry.api.common.AttributeKey +import io.opentelemetry.api.logs.{Logger, LoggerProvider, Severity} +import io.opentelemetry.context.Context +import zio._ +import zio.telemetry.opentelemetry.context.ContextStorage + +object Logging { + + def live( + instrumentationScopeName: String, + logLevel: LogLevel = LogLevel.Info + ): ZLayer[ContextStorage with LoggerProvider, Nothing, Unit] = + ZLayer.scoped( + for { + loggerProvider <- ZIO.service[LoggerProvider] + contextStorage <- ZIO.service[ContextStorage] + logger <- ZIO.succeed( + zioLogger(instrumentationScopeName)(contextStorage, loggerProvider) + .filterLogLevel(_ >= logLevel) + ) + _ <- ZIO.withLoggerScoped(logger) + } yield () + ) + + private def zioLogger(instrumentationScopeName: String)( + contextStorage: ContextStorage, + loggerProvider: LoggerProvider + ): ZLogger[String, Unit] = + new ZLogger[String, Unit] { + + val logger: Logger = loggerProvider.get(instrumentationScopeName) + + override def apply( + trace: Trace, + fiberId: FiberId, + logLevel: LogLevel, + message: () => String, + cause: Cause[Any], + context: FiberRefs, + spans: List[LogSpan], + annotations: Map[String, String] + ): Unit = { + val builder = logger.logRecordBuilder() + + builder.setBody(message()) + builder.setSeverityText(logLevel.label) + builder.setSeverity(severityMapping(logLevel)) + annotations.foreach { case (k, v) => builder.setAttribute(AttributeKey.stringKey(k), v) } + + contextStorage match { + case cs: ContextStorage.FiberRefContextStorage => + context.get(cs.ref).foreach(builder.setContext) + case _: ContextStorage.NativeContextStorage => + builder.setContext(Context.current()) + } + + builder.emit() + } + + private def severityMapping(level: LogLevel): Severity = + level match { + case LogLevel.Trace => Severity.TRACE + case LogLevel.Debug => Severity.DEBUG + case LogLevel.Info => Severity.INFO + case LogLevel.Warning => Severity.WARN + case LogLevel.Error => Severity.ERROR + case LogLevel.Fatal => Severity.FATAL + case _ => Severity.UNDEFINED_SEVERITY_NUMBER + } + + } + +} diff --git a/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/tracing/Tracing.scala b/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/tracing/Tracing.scala index 415b4a1b..6becf748 100644 --- a/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/tracing/Tracing.scala +++ b/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/tracing/Tracing.scala @@ -560,7 +560,7 @@ object Tracing { updatedCtx <- createChildUnsafe(ctx, spanName, spanKind, attributes, links) oldCtx <- ctxStorage.getAndSet(updatedCtx) span <- getCurrentSpanUnsafe - finalize = end *> ctxStorage.set(oldCtx) + finalize = endCurrentSpan *> ctxStorage.set(oldCtx) } yield (span, finalize) override def root[R, E, E1 <: E, A, A1 <: A]( @@ -630,7 +630,7 @@ object Tracing { updatedCtx <- createChildUnsafe(ctx, spanName, spanKind, attributes, links) _ <- ctxStorage.set(updatedCtx) span <- getCurrentSpanUnsafe - finalize = end *> ctxStorage.set(ctx) + finalize = endCurrentSpan *> ctxStorage.set(ctx) } yield (span, finalize) override def scopedEffect[A](effect: => A)(implicit trace: Trace): Task[A] = @@ -873,7 +873,7 @@ object Tracing { private def endSpan(span: Span)(implicit trace: Trace): UIO[Unit] = currentNanos.flatMap(nanos => ZIO.succeed(span.end(nanos, TimeUnit.NANOSECONDS))) - private def end(implicit trace: Trace): UIO[Any] = + private def endCurrentSpan(implicit trace: Trace): UIO[Any] = getCurrentSpanUnsafe.flatMap(endSpan) /** diff --git a/opentelemetry/src/test/scala/zio/telemetry/opentelemetry/logging/LoggingTest.scala b/opentelemetry/src/test/scala/zio/telemetry/opentelemetry/logging/LoggingTest.scala new file mode 100644 index 00000000..90cc87e2 --- /dev/null +++ b/opentelemetry/src/test/scala/zio/telemetry/opentelemetry/logging/LoggingTest.scala @@ -0,0 +1,176 @@ +package zio.telemetry.opentelemetry.logging + +import io.opentelemetry.api.logs.{LoggerProvider, Severity} +import io.opentelemetry.sdk.logs.SdkLoggerProvider +import io.opentelemetry.sdk.logs.data.LogRecordData +import io.opentelemetry.sdk.logs.`export`.SimpleLogRecordProcessor +import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter +import zio._ +import zio.telemetry.opentelemetry.context.ContextStorage +import zio.telemetry.opentelemetry.tracing.{Tracing, TracingTest} +import zio.test.Assertion._ +import zio.test._ + +import scala.jdk.CollectionConverters._ + +object LoggingTest extends ZIOSpecDefault { + + val inMemoryLogLoggerProvider: ZIO[Any, Nothing, (InMemoryLogRecordExporter, SdkLoggerProvider)] = + for { + logRecordExporter <- ZIO.succeed(InMemoryLogRecordExporter.create()) + logRecordProcessor <- ZIO.succeed(SimpleLogRecordProcessor.create(logRecordExporter)) + loggerProvider <- ZIO.succeed(SdkLoggerProvider.builder().addLogRecordProcessor(logRecordProcessor).build()) + } yield (logRecordExporter, loggerProvider) + + val inMemoryLoggerProviderLayer: ULayer[InMemoryLogRecordExporter with LoggerProvider] = + ZLayer.fromZIOEnvironment(inMemoryLogLoggerProvider.map { case (inMemoryLogRecordExporter, loggerProvider) => + ZEnvironment(inMemoryLogRecordExporter).add(loggerProvider) + }) + + def loggingMockLayer( + instrumentationScopeName: String, + logLevel: LogLevel = LogLevel.Info + ): URLayer[ContextStorage, InMemoryLogRecordExporter with LoggerProvider] = + Runtime.removeDefaultLoggers >>> + inMemoryLoggerProviderLayer >>> + (Logging.live(instrumentationScopeName, logLevel) ++ inMemoryLoggerProviderLayer) + + def getFinishedLogRecords: ZIO[InMemoryLogRecordExporter, Nothing, List[LogRecordData]] = + ZIO.service[InMemoryLogRecordExporter].map(_.getFinishedLogRecordItems.asScala.toList) + + override def spec: Spec[TestEnvironment with Scope, Any] = + suite("zio opentelemetry")( + suite("Logging")( + test("without tracing context") { + for { + _ <- ZIO.logAnnotate("zio", "logging")(ZIO.logInfo("test")) + logRecords <- getFinishedLogRecords + } yield { + val r = logRecords.head + val body = r.getBody.asString() + val severityNumber = r.getSeverity.getSeverityNumber + val severityText = r.getSeverityText + val instrumentationScopeName = r.getInstrumentationScopeInfo.getName + val attributes = r.getAttributes.asMap().asScala.toMap.map { case (k, v) => k.getKey -> v.toString } + val traceId = r.getSpanContext.getTraceId + val spanId = r.getSpanContext.getSpanId + + assert(logRecords.length)(equalTo(1)) && + assert(body)(equalTo("test")) && + assert(severityNumber)(equalTo(Severity.INFO.getSeverityNumber)) && + assert(severityText)(equalTo("INFO")) && + assert(instrumentationScopeName)(equalTo("without tracing context")) && + assert(attributes)(equalTo(Map("zio" -> "logging"))) && + assert(traceId)(equalTo("00000000000000000000000000000000")) && + assert(spanId)(equalTo("0000000000000000")) + } + }.provide(loggingMockLayer("without tracing context"), ContextStorage.fiberRef), + test("filter log level") { + for { + _ <- ZIO.logInfo("test") + _ <- ZIO.logWarning("test") + logRecords <- getFinishedLogRecords + } yield { + val r = logRecords.head + val body = r.getBody.asString() + val severityNumber = r.getSeverity.getSeverityNumber + val severityText = r.getSeverityText + val instrumentationScopeName = r.getInstrumentationScopeInfo.getName + val attributes = r.getAttributes.asMap().asScala.toMap.map { case (k, v) => k.getKey -> v.toString } + val traceId = r.getSpanContext.getTraceId + val spanId = r.getSpanContext.getSpanId + + assert(logRecords.length)(equalTo(1)) && + assert(body)(equalTo("test")) && + assert(severityNumber)(equalTo(Severity.WARN.getSeverityNumber)) && + assert(severityText)(equalTo("WARN")) && + assert(instrumentationScopeName)(equalTo("filter log level")) && + assert(attributes)(equalTo(Map.empty[String, String])) && + assert(traceId)(equalTo("00000000000000000000000000000000")) && + assert(spanId)(equalTo("0000000000000000")) + } + }.provide(loggingMockLayer("filter log level", LogLevel.Warning), ContextStorage.fiberRef), + test("multiple loggers") { + for { + logRecords1 <- + ZIO.logInfo("test1").flatMap(_ => getFinishedLogRecords).provideLayer(loggingMockLayer("test1")) + logRecords2 <- + ZIO.logInfo("test2").flatMap(_ => getFinishedLogRecords).provideLayer(loggingMockLayer("test2")) + } yield { + val r1 = logRecords1.head + val r2 = logRecords2.head + + assert(r1.getInstrumentationScopeInfo.getName)(equalTo("test1")) && + assert(r2.getInstrumentationScopeInfo.getName)(equalTo("test2")) + } + }.provide(ContextStorage.fiberRef), + test("tracing context (fiberRef)") { + ZIO.serviceWithZIO[Tracing] { tracing => + tracing.root("ROOT")( + for { + spanCtx <- tracing.getCurrentSpanContextUnsafe + _ <- ZIO.logInfo("test") + logRecords <- getFinishedLogRecords + } yield { + val r = logRecords.head + val body = r.getBody.asString() + val severityNumber = r.getSeverity.getSeverityNumber + val severityText = r.getSeverityText + val instrumentationScopeName = r.getInstrumentationScopeInfo.getName + val attributes = r.getAttributes.asMap().asScala.toMap.map { case (k, v) => k.getKey -> v.toString } + val traceId = r.getSpanContext.getTraceId + val spanId = r.getSpanContext.getSpanId + + assert(logRecords.length)(equalTo(1)) && + assert(body)(equalTo("test")) && + assert(severityNumber)(equalTo(Severity.INFO.getSeverityNumber)) && + assert(severityText)(equalTo("INFO")) && + assert(instrumentationScopeName)(equalTo("tracing context (fiberRef)")) && + assert(attributes)(equalTo(Map.empty[String, String])) && + assert(traceId)(equalTo(spanCtx.getTraceId)) && + assert(spanId)(equalTo(spanCtx.getSpanId)) + } + ) + } + }.provide( + loggingMockLayer("tracing context (fiberRef)"), + TracingTest.tracingMockLayer, + ContextStorage.fiberRef + ), + test("tracing context (openTelemtryContext)") { + ZIO.serviceWithZIO[Tracing] { tracing => + tracing.root("ROOT")( + for { + spanCtx <- tracing.getCurrentSpanContextUnsafe + _ <- ZIO.logInfo("test") + logRecords <- getFinishedLogRecords + } yield { + val r = logRecords.head + val body = r.getBody.asString() + val severityNumber = r.getSeverity.getSeverityNumber + val severityText = r.getSeverityText + val instrumentationScopeName = r.getInstrumentationScopeInfo.getName + val attributes = r.getAttributes.asMap().asScala.toMap.map { case (k, v) => k.getKey -> v.toString } + val traceId = r.getSpanContext.getTraceId + val spanId = r.getSpanContext.getSpanId + + assert(logRecords.length)(equalTo(1)) && + assert(body)(equalTo("test")) && + assert(severityNumber)(equalTo(Severity.INFO.getSeverityNumber)) && + assert(severityText)(equalTo("INFO")) && + assert(instrumentationScopeName)(equalTo("tracing context (openTelemtryContext)")) && + assert(attributes)(equalTo(Map.empty[String, String])) && + assert(traceId)(equalTo(spanCtx.getTraceId)) && + assert(spanId)(equalTo(spanCtx.getSpanId)) + } + ) + } + }.provide( + loggingMockLayer("tracing context (openTelemtryContext)"), + TracingTest.tracingMockLayer, + ContextStorage.native + ) + ) + ) + +} diff --git a/opentelemetry/src/test/scala/zio/telemetry/opentelemetry/tracing/TracingTest.scala b/opentelemetry/src/test/scala/zio/telemetry/opentelemetry/tracing/TracingTest.scala index 55fe299f..75cb5cd2 100644 --- a/opentelemetry/src/test/scala/zio/telemetry/opentelemetry/tracing/TracingTest.scala +++ b/opentelemetry/src/test/scala/zio/telemetry/opentelemetry/tracing/TracingTest.scala @@ -26,12 +26,12 @@ object TracingTest extends ZIOSpecDefault { tracer = tracerProvider.get("TracingTest") } yield (spanExporter, tracer) - val inMemoryTracerLayer: ULayer[InMemorySpanExporter with Tracer with ContextStorage] = + val inMemoryTracerLayer: ULayer[InMemorySpanExporter with Tracer] = ZLayer.fromZIOEnvironment(inMemoryTracer.map { case (inMemorySpanExporter, tracer) => ZEnvironment(inMemorySpanExporter).add(tracer) - }) ++ ContextStorage.fiberRef + }) - val tracingMockLayer: ULayer[Tracing with InMemorySpanExporter with Tracer] = + val tracingMockLayer: URLayer[ContextStorage, Tracing with InMemorySpanExporter with Tracer] = inMemoryTracerLayer >>> (Tracing.live ++ inMemoryTracerLayer) def getFinishedSpans: ZIO[InMemorySpanExporter, Nothing, List[SpanData]] = @@ -55,7 +55,7 @@ object TracingTest extends ZIOSpecDefault { _ <- ZIO.scoped(Tracing.live.build) finishedSpans <- getFinishedSpans } yield assert(finishedSpans)(hasSize(equalTo(0))) - }.provideLayer(inMemoryTracerLayer) + }.provide(inMemoryTracerLayer, ContextStorage.fiberRef) ) private val spansSpec = @@ -554,7 +554,7 @@ object TracingTest extends ZIOSpecDefault { } yield assert(ko)(isSome(failureAssertion)) && assert(ok)(isSome(successAssertion)) } } - ).provideLayer(tracingMockLayer) + ).provide(tracingMockLayer, ContextStorage.fiberRef) private val spanScopedSpec = suite("scoped spans")( @@ -656,5 +656,5 @@ object TracingTest extends ZIOSpecDefault { } yield assert(tags.get(AttributeKey.stringKey("string")))(equalTo("bar")) } } - ).provideLayer(tracingMockLayer) + ).provide(tracingMockLayer, ContextStorage.fiberRef) } diff --git a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/ProxyApp.scala b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/ProxyApp.scala index f3507bc6..5c7cd051 100644 --- a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/ProxyApp.scala +++ b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/ProxyApp.scala @@ -1,11 +1,12 @@ package zio.telemetry.opentracing.example import zio._ +import zio.http._ import zio.config.magnolia._ import zio.config.typesafe.TypesafeConfig import zio.telemetry.opentracing.OpenTracing import zio.telemetry.opentracing.example.config.AppConfig -import zio.telemetry.opentracing.example.http.{Client, ProxyHttpApp, ProxyHttpServer} +import zio.telemetry.opentracing.example.http.{BackendClient, ProxyHttpApp, ProxyHttpServer} object ProxyApp extends ZIOAppDefault { @@ -16,8 +17,8 @@ object ProxyApp extends ZIOAppDefault { .serviceWithZIO[ProxyHttpServer](_.start.exitCode) .provide( configLayer, - zio.http.Client.default, - Client.live, + Client.default, + BackendClient.live, ProxyHttpServer.live, ProxyHttpApp.live, OpenTracing.live(), diff --git a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/Client.scala b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/BackendClient.scala similarity index 83% rename from opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/Client.scala rename to opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/BackendClient.scala index 56def3ff..1aa4db15 100644 --- a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/Client.scala +++ b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/BackendClient.scala @@ -7,7 +7,7 @@ import zio.telemetry.opentracing.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 @@ -32,9 +32,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 _) } diff --git a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/BackendHttpApp.scala b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/BackendHttpApp.scala index 7b626a58..7f62e5b9 100644 --- a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/BackendHttpApp.scala +++ b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/BackendHttpApp.scala @@ -2,8 +2,8 @@ package zio.telemetry.opentracing.example.http import io.opentracing.propagation.Format.Builtin.{HTTP_HEADERS => HttpHeadersFormat} import io.opentracing.propagation.TextMapAdapter -import zhttp.http.{->, /, Http, HttpApp, Method, Response} import zio._ +import zio.http._ import zio.json.EncoderOps import zio.telemetry.opentracing._ import zio.telemetry.opentracing.example.http.{Status => ServiceStatus} @@ -14,9 +14,9 @@ case class BackendHttpApp(tracing: OpenTracing) { import tracing.aspects._ - def routes: HttpApp[Any, Throwable] = + def routes: HttpApp[Any, Nothing] = Http.collectZIO { case request @ Method.GET -> _ / "status" => - val headers = request.headers.toList.toMap + val headers = request.headers.map(h => h.headerName -> h.renderedValue).toMap (ZIO.unit @@ spanFrom(HttpHeadersFormat, new TextMapAdapter(headers.asJava), "/status")) .as(Response.json(ServiceStatus.up("backend").toJson)) diff --git a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/BackendHttpServer.scala b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/BackendHttpServer.scala index d1ef10cf..e296c770 100644 --- a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/BackendHttpServer.scala +++ b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/BackendHttpServer.scala @@ -1,6 +1,6 @@ package zio.telemetry.opentracing.example.http -import zhttp.service.Server +import zio.http._ import zio.Console.printLine import zio._ import zio.telemetry.opentracing.example.config.AppConfig @@ -9,7 +9,7 @@ case class BackendHttpServer(config: AppConfig, httpApp: BackendHttpApp) { def start: ZIO[Any, Throwable, Nothing] = printLine(s"Starting BackendHttpServer on port ${config.backend.port}") *> - Server.start(config.backend.port, httpApp.routes) + Server.serve(httpApp.routes).provide(Server.defaultWithPort(config.backend.port)) } diff --git a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/ProxyHttpApp.scala b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/ProxyHttpApp.scala index ad72e250..53d06b27 100644 --- a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/ProxyHttpApp.scala +++ b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/ProxyHttpApp.scala @@ -4,7 +4,7 @@ import io.opentracing.propagation.Format.Builtin.{HTTP_HEADERS => HttpHeadersFor import io.opentracing.propagation.TextMapAdapter import io.opentracing.tag.Tags import sttp.model.Method.GET -import zhttp.http._ +import zio.http._ import zio._ import zio.json.EncoderOps import zio.telemetry.opentracing.OpenTracing @@ -12,12 +12,12 @@ import zio.telemetry.opentracing.OpenTracing import scala.collection.mutable import scala.jdk.CollectionConverters._ -case class ProxyHttpApp(client: Client, tracing: OpenTracing) { +case class ProxyHttpApp(client: BackendClient, tracing: OpenTracing) { import tracing.aspects._ - def routes: HttpApp[Any, Throwable] = - Http.collectZIO { case Method.GET -> _ / "statuses" => + def routes: HttpApp[Any, Nothing] = + Http.collectZIO { case Method.GET -> Root / "statuses" => (for { _ <- tracing.tag(Tags.SPAN_KIND.getKey, Tags.SPAN_KIND_CLIENT) _ <- tracing.tag(Tags.HTTP_METHOD.getKey, GET.method) @@ -25,7 +25,9 @@ case class ProxyHttpApp(client: Client, tracing: OpenTracing) { carrier = new TextMapAdapter(mutable.Map.empty[String, String].asJava) _ <- tracing.inject(HttpHeadersFormat, carrier) headers <- extractHeaders(carrier) - statuses <- client.status(headers) + statuses <- client + .status(headers) + .catchAll(_ => ZIO.succeed(Statuses(List.empty))) } yield Response.json(statuses.toJson)) @@ root("/statuses") } @@ -43,7 +45,7 @@ case class ProxyHttpApp(client: Client, tracing: OpenTracing) { object ProxyHttpApp { - val live: URLayer[Client with OpenTracing, ProxyHttpApp] = + val live: URLayer[BackendClient with OpenTracing, ProxyHttpApp] = ZLayer.fromFunction(ProxyHttpApp.apply _) } diff --git a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/ProxyHttpServer.scala b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/ProxyHttpServer.scala index 3c236403..fd95bf21 100644 --- a/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/ProxyHttpServer.scala +++ b/opentracing-example/src/main/scala/zio/telemetry/opentracing/example/http/ProxyHttpServer.scala @@ -1,6 +1,6 @@ package zio.telemetry.opentracing.example.http -import zhttp.service.Server +import zio.http._ import zio.Console.printLine import zio.telemetry.opentracing.example.config.AppConfig import zio._ @@ -9,7 +9,7 @@ case class ProxyHttpServer(config: AppConfig, httpApp: ProxyHttpApp) { def start: ZIO[Any, Throwable, Nothing] = printLine(s"Starting ProxyHttpServer on port ${config.proxy.port}") *> - Server.start(config.proxy.port, httpApp.routes) + Server.serve(httpApp.routes).provide(Server.defaultWithPort(config.proxy.port)) } diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 7a8e7ec0..262b7a8b 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -11,30 +11,33 @@ object Dependencies { } object Orgs { - val zio = "dev.zio" - val opentracing = "io.opentracing" - val opentelemetry = "io.opentelemetry" - val opencensus = "io.opencensus" - val jaegertracing = "io.jaegertracing" - val scalaLangModules = "org.scala-lang.modules" - val typelevel = "org.typelevel" - val d11 = "io.d11" - val softwaremillSttpClient3 = "com.softwaremill.sttp.client3" - val slf4j = "org.slf4j" - val grpc = "io.grpc" + val zio = "dev.zio" + val opentracing = "io.opentracing" + val opentelemetry = "io.opentelemetry" + val opentelemetrySemconv = "io.opentelemetry.semconv" + val opentelemetryInstrumentation = "io.opentelemetry.instrumentation" + val opencensus = "io.opencensus" + val jaegertracing = "io.jaegertracing" + val scalaLangModules = "org.scala-lang.modules" + val typelevel = "org.typelevel" + val softwaremillSttpClient3 = "com.softwaremill.sttp.client3" + val slf4j = "org.slf4j" + val grpc = "io.grpc" + val logback = "ch.qos.logback" } private object ExampleVersions { - val cats = "2.7.0" - val grpcNetty = "1.47.0" - val jaeger = "1.8.0" - val slf4j = "1.7.36" - val sttp3 = "3.7.0" - val zipkin = "2.16.3" - val zHttp = "2.0.0-RC10" - val zioJson = "0.3.0-RC10" - val zioConfig = "3.0.1" - val zioHttp = "3.0.0-RC1" + val cats = "2.7.0" + val grpcNetty = "1.47.0" + val jaeger = "1.8.0" + val slf4j = "1.7.36" + val sttp3 = "3.7.0" + val zipkin = "2.16.3" + val zioJson = "0.3.0-RC10" + val zioConfig = "3.0.1" + val zioHttp = "3.0.0-RC2" + val zioLogging = "2.1.15" + val logback = "1.4.11" } lazy val zio = Seq( @@ -68,7 +71,6 @@ object Dependencies { Orgs.jaegertracing % "jaeger-client" % ExampleVersions.jaeger, Orgs.jaegertracing % "jaeger-zipkin" % ExampleVersions.jaeger, Orgs.softwaremillSttpClient3 %% "zio-json" % ExampleVersions.sttp3, - Orgs.d11 %% "zhttp" % ExampleVersions.zHttp, Orgs.zio %% "zio-json" % ExampleVersions.zioJson, Orgs.zio %% "zio-config" % ExampleVersions.zioConfig, Orgs.zio %% "zio-config-magnolia" % ExampleVersions.zioConfig, @@ -84,14 +86,24 @@ object Dependencies { ) lazy val opentelemetryExample = example ++ Seq( - Orgs.opentelemetry % "opentelemetry-exporter-otlp" % Versions.opentelemetry, - Orgs.opentelemetry % "opentelemetry-sdk" % Versions.opentelemetry, - Orgs.grpc % "grpc-netty-shaded" % ExampleVersions.grpcNetty, - Orgs.zio %% "zio-http" % ExampleVersions.zioHttp + Orgs.opentelemetry % "opentelemetry-exporter-otlp" % Versions.opentelemetry, + Orgs.opentelemetry % "opentelemetry-sdk" % Versions.opentelemetry, + Orgs.opentelemetrySemconv % "opentelemetry-semconv" % "1.22.0-alpha", + Orgs.grpc % "grpc-netty-shaded" % ExampleVersions.grpcNetty, + Orgs.zio %% "zio-http" % ExampleVersions.zioHttp ) lazy val opentelemetryInstrumentationExample = example ++ Seq( - Orgs.zio %% "zio-http" % ExampleVersions.zioHttp + Orgs.opentelemetry % "opentelemetry-exporter-otlp" % Versions.opentelemetry, + Orgs.opentelemetry % "opentelemetry-sdk" % Versions.opentelemetry, + Orgs.opentelemetrySemconv % "opentelemetry-semconv" % "1.22.0-alpha", + Orgs.grpc % "grpc-netty-shaded" % ExampleVersions.grpcNetty, + Orgs.opentelemetryInstrumentation % "opentelemetry-logback-appender-1.0" % "1.31.0-alpha", + Orgs.zio %% "zio-http" % ExampleVersions.zioHttp, + Orgs.zio %% "zio-logging" % ExampleVersions.zioLogging, + Orgs.zio %% "zio-logging-slf4j2" % ExampleVersions.zioLogging, + Orgs.logback % "logback-classic" % ExampleVersions.logback, + Orgs.logback % "logback-core" % ExampleVersions.logback ) }