From 96af41c52d169ebfd90cd2a4c02831a524d75c19 Mon Sep 17 00:00:00 2001 From: Andrzej Ressel Date: Fri, 24 May 2024 21:36:01 +0200 Subject: [PATCH 01/13] Allow direct access to trace and span id for zio-logging --- .../opentelemetry/logging/ZioLogging.scala | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 opentelemetry/src/main/scala/zio/telemetry/opentelemetry/logging/ZioLogging.scala diff --git a/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/logging/ZioLogging.scala b/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/logging/ZioLogging.scala new file mode 100644 index 00000000..3b1bf59a --- /dev/null +++ b/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/logging/ZioLogging.scala @@ -0,0 +1,44 @@ +package zio.telemetry.opentelemetry.logging + +import io.opentelemetry.api.trace.{Span, SpanContext} +import io.opentelemetry.context.Context +import zio.{FiberRefs, ZIO} +import zio.telemetry.opentelemetry.context.ContextStorage + +/** + * zio-telemetry does not related directly on zio-logging so the LogFormat has to be constructed manually + * + * {{{ + * import zio.logging.LogFormat + * import zio.telemetry.opentelemetry.context.ContextStorage + * import zio.telemetry.opentelemetry.logging.ZioLogging + * + * def traceIdLogFormat(contextStorage: ContextStorage) = LogFormat.make { (builder, _, _, _, _, _, fiberRefs, _, _) => + * builder.appendText(ZioLogging.traceId(contextStorage, fiberRefs).getOrElse("not-available")) + * } + * + * def spanIdLogFormat(contextStorage: ContextStorage) = LogFormat.make { (builder, _, _, _, _, _, fiberRefs, _, _) => + * builder.appendText(ZioLogging.spanId(contextStorage, fiberRefs).getOrElse("not-available")) + * } + * }}} + */ +object ZioLogging { + + def traceId(ctxStorage: ContextStorage, fiberRefs: FiberRefs): Option[String] = { + getSpanContext(ctxStorage, fiberRefs).map(_.getTraceId) + } + + def spanId(ctxStorage: ContextStorage, fiberRefs: FiberRefs): Option[String] = { + getSpanContext(ctxStorage, fiberRefs).map(_.getSpanId) + } + + private def getSpanContext(ctxStorage: ContextStorage, fiberRefs: FiberRefs): Option[SpanContext] = { + (ctxStorage match { + case ref: ContextStorage.ZIOFiberRef => fiberRefs.get(ref.ref) + case ContextStorage.Native => Some(Context.current()) + }) + .map(Span.fromContext) + .map(_.getSpanContext) + } + +} From fbb0bca32ae85f4137c6b45470edfa9e649517f4 Mon Sep 17 00:00:00 2001 From: Andrzej Ressel Date: Fri, 24 May 2024 21:46:59 +0200 Subject: [PATCH 02/13] Allow direct access to trace and span id for zio-logging --- .../opentelemetry/logging/ZioLogging.scala | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/logging/ZioLogging.scala b/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/logging/ZioLogging.scala index 3b1bf59a..d2dfad83 100644 --- a/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/logging/ZioLogging.scala +++ b/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/logging/ZioLogging.scala @@ -2,7 +2,7 @@ package zio.telemetry.opentelemetry.logging import io.opentelemetry.api.trace.{Span, SpanContext} import io.opentelemetry.context.Context -import zio.{FiberRefs, ZIO} +import zio.FiberRefs import zio.telemetry.opentelemetry.context.ContextStorage /** @@ -24,21 +24,18 @@ import zio.telemetry.opentelemetry.context.ContextStorage */ object ZioLogging { - def traceId(ctxStorage: ContextStorage, fiberRefs: FiberRefs): Option[String] = { + def traceId(ctxStorage: ContextStorage, fiberRefs: FiberRefs): Option[String] = getSpanContext(ctxStorage, fiberRefs).map(_.getTraceId) - } - def spanId(ctxStorage: ContextStorage, fiberRefs: FiberRefs): Option[String] = { + def spanId(ctxStorage: ContextStorage, fiberRefs: FiberRefs): Option[String] = getSpanContext(ctxStorage, fiberRefs).map(_.getSpanId) - } - private def getSpanContext(ctxStorage: ContextStorage, fiberRefs: FiberRefs): Option[SpanContext] = { + private def getSpanContext(ctxStorage: ContextStorage, fiberRefs: FiberRefs): Option[SpanContext] = (ctxStorage match { case ref: ContextStorage.ZIOFiberRef => fiberRefs.get(ref.ref) - case ContextStorage.Native => Some(Context.current()) + case ContextStorage.Native => Some(Context.current()) }) .map(Span.fromContext) .map(_.getSpanContext) - } } From fde9c0fe42c44d19b60f53e56531462542ba7704 Mon Sep 17 00:00:00 2001 From: Andrzej Ressel Date: Sat, 25 May 2024 18:36:40 +0200 Subject: [PATCH 03/13] Implement LogFormats for spanId and traceId --- build.sbt | 24 ++++++- docs/opentelemetry-zio-logging.md | 36 +++++++++++ docs/sidebars.js | 1 + .../zio/logging/TelemetryLogFormats.scala | 39 ++++++++++++ .../zio/logging/TelemetryLogFormatsSpec.scala | 62 +++++++++++++++++++ project/Dependencies.scala | 4 ++ 6 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 docs/opentelemetry-zio-logging.md create mode 100644 opentelemetry-zio-logging/src/main/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormats.scala create mode 100644 opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala diff --git a/build.sbt b/build.sbt index b417d036..097d35b7 100644 --- a/build.sbt +++ b/build.sbt @@ -103,7 +103,7 @@ lazy val root = project .in(file(".")) .settings(publish / skip := true) - .aggregate(opentracing, opentelemetry, opencensus, docs) + .aggregate(opentracing, opentelemetry, opencensus, opentelemetryZioLogging, docs) lazy val opentracing = project @@ -146,6 +146,19 @@ lazy val opencensus = project .settings(mimaSettings(failOnProblem = true)) .settings(unusedCompileDependenciesFilter -= moduleFilter("io.opencensus", "opencensus-impl")) +lazy val opentelemetryZioLogging = project + .in(file("opentelemetry-zio-logging")) + .settings(enableZIO()) + .settings( + stdModuleSettings( + name = Some("zio-opentelemetry-zio-logging"), + packageName = Some("zio.telemetry.opentelemetry.zio.logging") + ) + ) + .settings(libraryDependencies ++= Dependencies.opentelemetryZioLogging) + .settings(mimaSettings(failOnProblem = true)) + .dependsOn(opentelemetry) + lazy val opentracingExample = project .in(file("opentracing-example")) @@ -194,9 +207,14 @@ lazy val docs = projectName := "ZIO Telemetry", mainModuleName := (opentracing / moduleName).value, projectStage := ProjectStage.ProductionReady, - ScalaUnidoc / unidoc / unidocProjectFilter := inProjects(opentracing, opentelemetry, opencensus), + ScalaUnidoc / unidoc / unidocProjectFilter := inProjects( + opentracing, + opentelemetry, + opencensus, + opentelemetryZioLogging + ), scalacOptions --= Seq("-Yno-imports", "-Xfatal-warnings") ) .settings(unusedCompileDependenciesFilter -= moduleFilter("org.scalameta", "mdoc")) - .dependsOn(opentracing, opentelemetry, opencensus) + .dependsOn(opentracing, opentelemetry, opencensus, opentelemetryZioLogging) .enablePlugins(WebsitePlugin) diff --git a/docs/opentelemetry-zio-logging.md b/docs/opentelemetry-zio-logging.md new file mode 100644 index 00000000..9f6427ca --- /dev/null +++ b/docs/opentelemetry-zio-logging.md @@ -0,0 +1,36 @@ +--- +id: opentelemetry-zio-logging +title: "OpenTelemetry ZIO Logging" +--- + +`zio-opentelemetry` logging facilities are implemented around OpenTelemetry Logging. + +In order to use `zio-opentelemetry` feature with `zio-logging` you should use `zio-opentelemetry-zio-logging` module. + +`OpenTelemetry ZIO Logging` contains utilities for combining ZIO Opentelemetry with ZIO Logging + +## Installation + +```scala +"dev.zio" %% "zio-opentelemetry-zio-logging" % "" +``` + +## Features + +### Log formats + +This library implements [Log Format](https://zio.dev/zio-logging/formatting-log-records) for span information (`spanId` and `traceId`) + +```scala +ZIO.serviceWithZIO[ContextStorage] { ctxStorage => + import zio.logging.console + import zio.logging.LogFormat._ + import zio.telemetry.opentelemetry.zio.logging.TelemetryLogFormats + + val spanIdLabel = label("spanId", TelemetryLogFormats.spanId(ctxStorage)) + val traceIdLabel = label("traceId", TelemetryLogFormats.traceId(ctxStorage)) + + val myLogFormat = timestamp.fixed(32) |-| level |-| label("message", quoted(line)) |-| spanIdLabel |-| traceIdLabel + val myConsoleLogger = console(myLogFormat) +} +``` \ No newline at end of file diff --git a/docs/sidebars.js b/docs/sidebars.js index a6c12a02..228cc320 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -10,6 +10,7 @@ const sidebars = { "opentracing-example", "opencensus", "opentelemetry", + "opentelemetry-zio-logging", "opentelemetry-example", "opentelemetry-instrumentation-example" ] diff --git a/opentelemetry-zio-logging/src/main/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormats.scala b/opentelemetry-zio-logging/src/main/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormats.scala new file mode 100644 index 00000000..6e5c22f4 --- /dev/null +++ b/opentelemetry-zio-logging/src/main/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormats.scala @@ -0,0 +1,39 @@ +package zio.telemetry.opentelemetry.zio.logging + +import io.opentelemetry.api.trace.{Span, SpanContext} +import io.opentelemetry.context.Context +import zio.FiberRefs +import zio.logging.LogFormat +import zio.telemetry.opentelemetry.context.ContextStorage + +object TelemetryLogFormats { + + /** + * Will print traceId from current span or nothing when not in span + */ + def traceId(contextStorage: ContextStorage): LogFormat = LogFormat.make { (builder, _, _, _, _, _, fiberRefs, _, _) => + getSpanContext(contextStorage, fiberRefs).map(_.getTraceId) match { + case Some(traceId) => builder.appendText(traceId) + case None => () + } + } + + /** + * Will print spanId from current span or nothing when not in span + */ + def spanId(contextStorage: ContextStorage): LogFormat = LogFormat.make { (builder, _, _, _, _, _, fiberRefs, _, _) => + getSpanContext(contextStorage, fiberRefs).map(_.getSpanId) match { + case Some(spanId) => builder.appendText(spanId) + case None => () + } + } + + private def getSpanContext(ctxStorage: ContextStorage, fiberRefs: FiberRefs): Option[SpanContext] = + (ctxStorage match { + case ref: ContextStorage.ZIOFiberRef => fiberRefs.get(ref.ref) + case ContextStorage.Native => Some(Context.current()) + }) + .map(Span.fromContext) + .map(_.getSpanContext) + +} diff --git a/opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala b/opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala new file mode 100644 index 00000000..78f27d61 --- /dev/null +++ b/opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala @@ -0,0 +1,62 @@ +package zio.telemetry.opentelemetry.zio.logging + +import io.opentelemetry.api.trace.Tracer +import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter +import io.opentelemetry.sdk.trace.SdkTracerProvider +import io.opentelemetry.sdk.trace.`export`.SimpleSpanProcessor +import io.opentelemetry.sdk.trace.data.SpanData +import zio.Runtime.removeDefaultLoggers +import zio.logging.LogFormat.label +import zio.telemetry.opentelemetry.context.ContextStorage +import zio.telemetry.opentelemetry.tracing.Tracing +import zio.test.{Spec, TestEnvironment, ZIOSpecDefault, assertTrue} +import zio.{Scope, UIO, ULayer, URLayer, ZEnvironment, ZIO, ZLayer} + +import scala.collection.mutable +import scala.jdk.CollectionConverters._ + +object TelemetryLogFormatsSpec extends ZIOSpecDefault { + + val inMemoryTracer: UIO[(InMemorySpanExporter, Tracer)] = for { + spanExporter <- ZIO.succeed(InMemorySpanExporter.create()) + spanProcessor <- ZIO.succeed(SimpleSpanProcessor.create(spanExporter)) + tracerProvider <- ZIO.succeed(SdkTracerProvider.builder().addSpanProcessor(spanProcessor).build()) + tracer = tracerProvider.get("TracingTest") + } yield (spanExporter, tracer) + + val inMemoryTracerLayer: ULayer[InMemorySpanExporter with Tracer] = + ZLayer.fromZIOEnvironment(inMemoryTracer.map { case (inMemorySpanExporter, tracer) => + ZEnvironment(inMemorySpanExporter).add(tracer) + }) + + def tracingMockLayer( + logAnnotated: Boolean = false + ): URLayer[ContextStorage, Tracing with InMemorySpanExporter with Tracer] = + inMemoryTracerLayer >>> (Tracing.live(logAnnotated) ++ inMemoryTracerLayer) + + def getFinishedSpans: ZIO[InMemorySpanExporter, Nothing, List[SpanData]] = + ZIO.serviceWith[InMemorySpanExporter](_.getFinishedSpanItems.asScala.toList) + + override def spec: Spec[TestEnvironment with Scope, Any] = + suite("opentelemetry-zio-logging LogFormats")(test("SpanId and traceId are extracted") { + ZIO.serviceWithZIO[Tracing] { tracing => + import tracing.aspects._ + val logs = mutable.Buffer[String]() + + for { + contextStorage <- ZIO.service[ContextStorage] + format = label("spanId", TelemetryLogFormats.spanId(contextStorage)) |-| label( + "traceId", + TelemetryLogFormats.traceId(contextStorage) + ) + zLogger = format.toLogger.map(logs.append) + _ <- zio.ZIO.logInfo("TEST").withLogger(zLogger) @@ span("Span") @@ root("Root") + + spans <- getFinishedSpans + child = spans.find(_.getName == "Span").get + log = logs.head + } yield assertTrue(log == s"spanId=${child.getSpanId} traceId=${child.getTraceId}") + } + }).provide(removeDefaultLoggers, tracingMockLayer(), ContextStorage.fiberRef) + +} diff --git a/project/Dependencies.scala b/project/Dependencies.scala index d8c56e36..0003b76a 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -67,6 +67,10 @@ object Dependencies { Orgs.scalaLangModules %% "scala-collection-compat" % Versions.scalaCollectionCompat % Test ) + lazy val opentelemetryZioLogging = Seq( + Orgs.zio %% "zio-logging" % ExampleVersions.zioLogging + ) + lazy val example = zio ++ Seq( Orgs.typelevel %% "cats-core" % ExampleVersions.cats, Orgs.jaegertracing % "jaeger-core" % ExampleVersions.jaeger, From 2b7874bc56342ff2272c1ac4407b9ac8ecdefe31 Mon Sep 17 00:00:00 2001 From: Andrzej Ressel Date: Sat, 25 May 2024 18:38:54 +0200 Subject: [PATCH 04/13] Implement LogFormats for spanId and traceId --- .../zio/logging/TelemetryLogFormatsSpec.scala | 40 +++++++++--------- .../opentelemetry/logging/ZioLogging.scala | 41 ------------------- 2 files changed, 21 insertions(+), 60 deletions(-) delete mode 100644 opentelemetry/src/main/scala/zio/telemetry/opentelemetry/logging/ZioLogging.scala diff --git a/opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala b/opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala index 78f27d61..9446daf2 100644 --- a/opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala +++ b/opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala @@ -38,25 +38,27 @@ object TelemetryLogFormatsSpec extends ZIOSpecDefault { ZIO.serviceWith[InMemorySpanExporter](_.getFinishedSpanItems.asScala.toList) override def spec: Spec[TestEnvironment with Scope, Any] = - suite("opentelemetry-zio-logging LogFormats")(test("SpanId and traceId are extracted") { - ZIO.serviceWithZIO[Tracing] { tracing => - import tracing.aspects._ - val logs = mutable.Buffer[String]() - - for { - contextStorage <- ZIO.service[ContextStorage] - format = label("spanId", TelemetryLogFormats.spanId(contextStorage)) |-| label( - "traceId", - TelemetryLogFormats.traceId(contextStorage) - ) - zLogger = format.toLogger.map(logs.append) - _ <- zio.ZIO.logInfo("TEST").withLogger(zLogger) @@ span("Span") @@ root("Root") - - spans <- getFinishedSpans - child = spans.find(_.getName == "Span").get - log = logs.head - } yield assertTrue(log == s"spanId=${child.getSpanId} traceId=${child.getTraceId}") + suite("opentelemetry-zio-logging LogFormats") { + test("SpanId and traceId are extracted") { + ZIO.serviceWithZIO[Tracing] { tracing => + import tracing.aspects._ + val logs = mutable.Buffer[String]() + + for { + contextStorage <- ZIO.service[ContextStorage] + format = label("spanId", TelemetryLogFormats.spanId(contextStorage)) |-| label( + "traceId", + TelemetryLogFormats.traceId(contextStorage) + ) + zLogger = format.toLogger.map(logs.append) + _ <- zio.ZIO.logInfo("TEST").withLogger(zLogger) @@ span("Span") @@ root("Root") + + spans <- getFinishedSpans + child = spans.find(_.getName == "Span").get + log = logs.head + } yield assertTrue(log == s"spanId=${child.getSpanId} traceId=${child.getTraceId}") + } } - }).provide(removeDefaultLoggers, tracingMockLayer(), ContextStorage.fiberRef) + }.provide(removeDefaultLoggers, tracingMockLayer(), ContextStorage.fiberRef) } diff --git a/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/logging/ZioLogging.scala b/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/logging/ZioLogging.scala deleted file mode 100644 index d2dfad83..00000000 --- a/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/logging/ZioLogging.scala +++ /dev/null @@ -1,41 +0,0 @@ -package zio.telemetry.opentelemetry.logging - -import io.opentelemetry.api.trace.{Span, SpanContext} -import io.opentelemetry.context.Context -import zio.FiberRefs -import zio.telemetry.opentelemetry.context.ContextStorage - -/** - * zio-telemetry does not related directly on zio-logging so the LogFormat has to be constructed manually - * - * {{{ - * import zio.logging.LogFormat - * import zio.telemetry.opentelemetry.context.ContextStorage - * import zio.telemetry.opentelemetry.logging.ZioLogging - * - * def traceIdLogFormat(contextStorage: ContextStorage) = LogFormat.make { (builder, _, _, _, _, _, fiberRefs, _, _) => - * builder.appendText(ZioLogging.traceId(contextStorage, fiberRefs).getOrElse("not-available")) - * } - * - * def spanIdLogFormat(contextStorage: ContextStorage) = LogFormat.make { (builder, _, _, _, _, _, fiberRefs, _, _) => - * builder.appendText(ZioLogging.spanId(contextStorage, fiberRefs).getOrElse("not-available")) - * } - * }}} - */ -object ZioLogging { - - def traceId(ctxStorage: ContextStorage, fiberRefs: FiberRefs): Option[String] = - getSpanContext(ctxStorage, fiberRefs).map(_.getTraceId) - - def spanId(ctxStorage: ContextStorage, fiberRefs: FiberRefs): Option[String] = - getSpanContext(ctxStorage, fiberRefs).map(_.getSpanId) - - private def getSpanContext(ctxStorage: ContextStorage, fiberRefs: FiberRefs): Option[SpanContext] = - (ctxStorage match { - case ref: ContextStorage.ZIOFiberRef => fiberRefs.get(ref.ref) - case ContextStorage.Native => Some(Context.current()) - }) - .map(Span.fromContext) - .map(_.getSpanContext) - -} From f9a184a5f222d46c62a6b8d10fbdd70d49568e09 Mon Sep 17 00:00:00 2001 From: Andrzej Ressel Date: Sat, 25 May 2024 18:40:48 +0200 Subject: [PATCH 05/13] Implement LogFormats for spanId and traceId --- .../opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala | 2 +- project/Dependencies.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala b/opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala index 9446daf2..26277994 100644 --- a/opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala +++ b/opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala @@ -38,7 +38,7 @@ object TelemetryLogFormatsSpec extends ZIOSpecDefault { ZIO.serviceWith[InMemorySpanExporter](_.getFinishedSpanItems.asScala.toList) override def spec: Spec[TestEnvironment with Scope, Any] = - suite("opentelemetry-zio-logging LogFormats") { + suiteAll("opentelemetry-zio-logging LogFormats") { test("SpanId and traceId are extracted") { ZIO.serviceWithZIO[Tracing] { tracing => import tracing.aspects._ diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 0003b76a..a4a1b4b2 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -67,7 +67,7 @@ object Dependencies { Orgs.scalaLangModules %% "scala-collection-compat" % Versions.scalaCollectionCompat % Test ) - lazy val opentelemetryZioLogging = Seq( + lazy val opentelemetryZioLogging = opentelemetry ++ Seq( Orgs.zio %% "zio-logging" % ExampleVersions.zioLogging ) From 6e4dccb96e91a85a9d94608fdfd479d53509e30c Mon Sep 17 00:00:00 2001 From: Andrzej Ressel Date: Sat, 25 May 2024 18:44:26 +0200 Subject: [PATCH 06/13] Implement LogFormats for spanId and traceId --- .../opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala b/opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala index 26277994..c331cc0b 100644 --- a/opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala +++ b/opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala @@ -50,7 +50,7 @@ object TelemetryLogFormatsSpec extends ZIOSpecDefault { "traceId", TelemetryLogFormats.traceId(contextStorage) ) - zLogger = format.toLogger.map(logs.append) + zLogger = format.toLogger.map(logs.append(_)) _ <- zio.ZIO.logInfo("TEST").withLogger(zLogger) @@ span("Span") @@ root("Root") spans <- getFinishedSpans From b26627afd36e1566c1b903f06c7398385445f9d8 Mon Sep 17 00:00:00 2001 From: Andrzej Ressel Date: Sat, 25 May 2024 18:48:56 +0200 Subject: [PATCH 07/13] Implement LogFormats for spanId and traceId --- .../opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala b/opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala index c331cc0b..71f6719f 100644 --- a/opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala +++ b/opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala @@ -3,8 +3,8 @@ package zio.telemetry.opentelemetry.zio.logging import io.opentelemetry.api.trace.Tracer import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter import io.opentelemetry.sdk.trace.SdkTracerProvider -import io.opentelemetry.sdk.trace.`export`.SimpleSpanProcessor import io.opentelemetry.sdk.trace.data.SpanData +import io.opentelemetry.sdk.trace.`export`.SimpleSpanProcessor import zio.Runtime.removeDefaultLoggers import zio.logging.LogFormat.label import zio.telemetry.opentelemetry.context.ContextStorage From 99460281629660e5ab1b482a289e4a8dcb271ca8 Mon Sep 17 00:00:00 2001 From: Andrzej Ressel Date: Tue, 28 May 2024 02:29:53 +0200 Subject: [PATCH 08/13] Implement LogFormats for spanId and traceId PR fixes --- build.sbt | 4 +- .../zio/logging/TelemetryLogFormats.scala | 39 ------ .../zio/logging/ZioLogging.scala | 60 +++++++++ .../zio/logging/TelemetryLogFormatsSpec.scala | 21 ++-- project/Dependencies.scala | 28 ++--- scala-cli/opentelemetry/ZioLoggingApp.scala | 117 ++++++++++++++++++ 6 files changed, 201 insertions(+), 68 deletions(-) delete mode 100644 opentelemetry-zio-logging/src/main/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormats.scala create mode 100644 opentelemetry-zio-logging/src/main/scala/zio/telemetry/opentelemetry/zio/logging/ZioLogging.scala create mode 100644 scala-cli/opentelemetry/ZioLoggingApp.scala diff --git a/build.sbt b/build.sbt index 097d35b7..c8f37911 100644 --- a/build.sbt +++ b/build.sbt @@ -210,8 +210,8 @@ lazy val docs = ScalaUnidoc / unidoc / unidocProjectFilter := inProjects( opentracing, opentelemetry, - opencensus, - opentelemetryZioLogging + opencensus + // opentelemetryZioLogging TODO: Causes some weird import issues ), scalacOptions --= Seq("-Yno-imports", "-Xfatal-warnings") ) diff --git a/opentelemetry-zio-logging/src/main/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormats.scala b/opentelemetry-zio-logging/src/main/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormats.scala deleted file mode 100644 index 6e5c22f4..00000000 --- a/opentelemetry-zio-logging/src/main/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormats.scala +++ /dev/null @@ -1,39 +0,0 @@ -package zio.telemetry.opentelemetry.zio.logging - -import io.opentelemetry.api.trace.{Span, SpanContext} -import io.opentelemetry.context.Context -import zio.FiberRefs -import zio.logging.LogFormat -import zio.telemetry.opentelemetry.context.ContextStorage - -object TelemetryLogFormats { - - /** - * Will print traceId from current span or nothing when not in span - */ - def traceId(contextStorage: ContextStorage): LogFormat = LogFormat.make { (builder, _, _, _, _, _, fiberRefs, _, _) => - getSpanContext(contextStorage, fiberRefs).map(_.getTraceId) match { - case Some(traceId) => builder.appendText(traceId) - case None => () - } - } - - /** - * Will print spanId from current span or nothing when not in span - */ - def spanId(contextStorage: ContextStorage): LogFormat = LogFormat.make { (builder, _, _, _, _, _, fiberRefs, _, _) => - getSpanContext(contextStorage, fiberRefs).map(_.getSpanId) match { - case Some(spanId) => builder.appendText(spanId) - case None => () - } - } - - private def getSpanContext(ctxStorage: ContextStorage, fiberRefs: FiberRefs): Option[SpanContext] = - (ctxStorage match { - case ref: ContextStorage.ZIOFiberRef => fiberRefs.get(ref.ref) - case ContextStorage.Native => Some(Context.current()) - }) - .map(Span.fromContext) - .map(_.getSpanContext) - -} diff --git a/opentelemetry-zio-logging/src/main/scala/zio/telemetry/opentelemetry/zio/logging/ZioLogging.scala b/opentelemetry-zio-logging/src/main/scala/zio/telemetry/opentelemetry/zio/logging/ZioLogging.scala new file mode 100644 index 00000000..4edaef5d --- /dev/null +++ b/opentelemetry-zio-logging/src/main/scala/zio/telemetry/opentelemetry/zio/logging/ZioLogging.scala @@ -0,0 +1,60 @@ +package zio.telemetry.opentelemetry.zio.logging + +import io.opentelemetry.api.trace.{Span, SpanContext} +import io.opentelemetry.context.Context +import zio._ +import zio.logging.LogFormat +import zio.logging.LogFormat.label +import zio.telemetry.opentelemetry.context.ContextStorage + +trait ZioLogging { + + /** + * Will print traceId from current span or nothing when not in span + */ + def traceId(): LogFormat + + /** + * Will print spanId from current span or nothing when not in span + */ + def spanId(): LogFormat + + /** + * Label with `traceId` key and [[traceId]] value + */ + def traceIdLabel(): LogFormat = label("traceId", traceId()) + + /** + * Label with `spanId` key and [[spanId]] value + */ + def spanIdLabel(): LogFormat = label("spanId", spanId()) +} + +object ZioLogging { + + def live: ZLayer[ContextStorage, Nothing, ZioLogging] = ZLayer { + for { + ctxStorage <- ZIO.service[ContextStorage] + } yield new ZioLogging { + override def traceId(): LogFormat = LogFormat.make { (builder, _, _, _, _, _, fiberRefs, _, _) => + getSpanContext(ctxStorage, fiberRefs).map(_.getTraceId).fold(())(builder.appendText(_)) + } + + override def spanId(): LogFormat = LogFormat.make { (builder, _, _, _, _, _, fiberRefs, _, _) => + getSpanContext(ctxStorage, fiberRefs).map(_.getSpanId).fold(())(builder.appendText(_)) + } + + private def getSpanContext(ctxStorage: ContextStorage, fiberRefs: FiberRefs): Option[SpanContext] = { + val maybeOtelContext = ctxStorage match { + case ref: ContextStorage.ZIOFiberRef => fiberRefs.get(ref.ref) + case ContextStorage.Native => Some(Context.current()) + } + + maybeOtelContext + .map(Span.fromContext) + .map(_.getSpanContext) + } + } + } + +} diff --git a/opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala b/opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala index 71f6719f..7a5a0bfb 100644 --- a/opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala +++ b/opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala @@ -6,7 +6,6 @@ import io.opentelemetry.sdk.trace.SdkTracerProvider import io.opentelemetry.sdk.trace.data.SpanData import io.opentelemetry.sdk.trace.`export`.SimpleSpanProcessor import zio.Runtime.removeDefaultLoggers -import zio.logging.LogFormat.label import zio.telemetry.opentelemetry.context.ContextStorage import zio.telemetry.opentelemetry.tracing.Tracing import zio.test.{Spec, TestEnvironment, ZIOSpecDefault, assertTrue} @@ -45,20 +44,16 @@ object TelemetryLogFormatsSpec extends ZIOSpecDefault { val logs = mutable.Buffer[String]() for { - contextStorage <- ZIO.service[ContextStorage] - format = label("spanId", TelemetryLogFormats.spanId(contextStorage)) |-| label( - "traceId", - TelemetryLogFormats.traceId(contextStorage) - ) - zLogger = format.toLogger.map(logs.append(_)) - _ <- zio.ZIO.logInfo("TEST").withLogger(zLogger) @@ span("Span") @@ root("Root") - - spans <- getFinishedSpans - child = spans.find(_.getName == "Span").get - log = logs.head + zioLogging <- ZIO.service[ZioLogging] + format = zioLogging.spanIdLabel() |-| zioLogging.traceIdLabel() + zLogger = format.toLogger.map(logs.append(_)) + _ <- zio.ZIO.logInfo("TEST").withLogger(zLogger) @@ span("Span") @@ root("Root") + spans <- getFinishedSpans + child = spans.find(_.getName == "Span").get + log = logs.head } yield assertTrue(log == s"spanId=${child.getSpanId} traceId=${child.getTraceId}") } } - }.provide(removeDefaultLoggers, tracingMockLayer(), ContextStorage.fiberRef) + }.provide(removeDefaultLoggers, tracingMockLayer(), ContextStorage.fiberRef, ZioLogging.live) } diff --git a/project/Dependencies.scala b/project/Dependencies.scala index a4a1b4b2..e662fa12 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -8,6 +8,7 @@ object Dependencies { val opencensus = "0.31.1" val scalaCollectionCompat = "2.12.0" val zio = "2.1.1" + val zioLogging = "2.1.15" val izumiReflect = "2.3.9" } @@ -28,17 +29,16 @@ object Dependencies { } 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 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" + 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 logback = "1.4.11" } lazy val zio = Seq( @@ -68,7 +68,7 @@ object Dependencies { ) lazy val opentelemetryZioLogging = opentelemetry ++ Seq( - Orgs.zio %% "zio-logging" % ExampleVersions.zioLogging + Orgs.zio %% "zio-logging" % Versions.zioLogging ) lazy val example = zio ++ Seq( @@ -107,8 +107,8 @@ object Dependencies { 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.zio %% "zio-logging" % Versions.zioLogging, + Orgs.zio %% "zio-logging-slf4j2" % Versions.zioLogging, Orgs.logback % "logback-classic" % ExampleVersions.logback, Orgs.logback % "logback-core" % ExampleVersions.logback ) diff --git a/scala-cli/opentelemetry/ZioLoggingApp.scala b/scala-cli/opentelemetry/ZioLoggingApp.scala new file mode 100644 index 00000000..e2218339 --- /dev/null +++ b/scala-cli/opentelemetry/ZioLoggingApp.scala @@ -0,0 +1,117 @@ +//> using scala "2.13.14" +//> using dep dev.zio::zio:2.1.1 +//> using dep dev.zio::zio-opentelemetry:3.0.0-RC23+18-b26627af+20240528-0156-SNAPSHOT +//> using dep dev.zio::zio-opentelemetry-zio-logging:3.0.0-RC23+18-b26627af+20240528-0156-SNAPSHOT +//> using dep io.opentelemetry:opentelemetry-sdk:1.38.0 +//> using dep io.opentelemetry:opentelemetry-sdk-trace:1.38.0 +//> using dep io.opentelemetry:opentelemetry-exporter-logging-otlp:1.38.0 +//> using dep io.opentelemetry.semconv:opentelemetry-semconv:1.22.0-alpha + +import io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingSpanExporter +import io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingLogRecordExporter +import io.opentelemetry.api.common.Attributes +import io.opentelemetry.sdk.trace.SdkTracerProvider +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor +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 io.opentelemetry.sdk.OpenTelemetrySdk +import io.opentelemetry.api +import zio._ +import zio.logging.console +import zio.logging.LogFormat._ +import zio.telemetry.opentelemetry.tracing.Tracing +import zio.telemetry.opentelemetry.OpenTelemetry +import zio.telemetry.opentelemetry.context.ContextStorage +import zio.telemetry.opentelemetry.zio.logging.ZioLogging + +object ZioLoggingApp extends ZIOAppDefault { + + val instrumentationScopeName = "dev.zio.LoggingApp" + val resourceName = "logging-app" + + // Prints to stdout in OTLP Json format + val stdoutLoggerProvider: RIO[Scope, SdkLoggerProvider] = + for { + logRecordExporter <- ZIO.fromAutoCloseable(ZIO.succeed(OtlpJsonLoggingLogRecordExporter.create())) + logRecordProcessor <- ZIO.fromAutoCloseable(ZIO.succeed(SimpleLogRecordProcessor.create(logRecordExporter))) + loggerProvider <- + ZIO.fromAutoCloseable( + ZIO.succeed( + SdkLoggerProvider + .builder() + .setResource(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, resourceName))) + .addLogRecordProcessor(logRecordProcessor) + .build() + ) + ) + } yield loggerProvider + + // Prints to stdout in OTLP Json format + val stdoutTracerProvider: RIO[Scope, SdkTracerProvider] = + for { + spanExporter <- ZIO.fromAutoCloseable(ZIO.succeed(OtlpJsonLoggingSpanExporter.create())) + spanProcessor <- ZIO.fromAutoCloseable(ZIO.succeed(SimpleSpanProcessor.create(spanExporter))) + tracerProvider <- + ZIO.fromAutoCloseable( + ZIO.succeed( + SdkTracerProvider + .builder() + .setResource(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, resourceName))) + .addSpanProcessor(spanProcessor) + .build() + ) + ) + } yield tracerProvider + + val otelSdkLayer: TaskLayer[api.OpenTelemetry] = + OpenTelemetry.custom( + for { + tracerProvider <- stdoutTracerProvider + loggerProvider <- stdoutLoggerProvider + sdk <- ZIO.fromAutoCloseable( + ZIO.succeed( + OpenTelemetrySdk + .builder() + .setTracerProvider(tracerProvider) + .setLoggerProvider(loggerProvider) + .build() + ) + ) + } yield sdk + ) + + // Setup zio-logging with spanId and traceId labels + val loggingLayer: URLayer[ZioLogging, Unit] = ZLayer { + for { + zioLogging <- ZIO.service[ZioLogging] + format = timestamp.fixed(32) |-| level |-| label("message", quoted(line)) |-| zioLogging.spanIdLabel() |-| zioLogging.traceIdLabel() + myConsoleLogger = console(format) + } yield Runtime.removeDefaultLoggers >>> myConsoleLogger + }.flatten + + + override def run = + ZIO + .serviceWithZIO[Tracing] { tracing => + val logic = for { + // Read user input + message <- Console.readLine + // Print span and trace ids along with message + _ <- ZIO.logInfo(s"User message: $message") + } yield () + + // All log messages produced by `logic` will be correlated with a "root_span" automatically + logic @@ tracing.aspects.root("root_span") + } + .provide( + otelSdkLayer, + OpenTelemetry.logging(instrumentationScopeName), + OpenTelemetry.tracing(instrumentationScopeName), + OpenTelemetry.contextZIO, + ZioLogging.live, + loggingLayer + ) + +} From a55ed40c8d29955f2a74ebcfb7d11017acf5ab77 Mon Sep 17 00:00:00 2001 From: Andrzej Ressel Date: Tue, 28 May 2024 02:40:38 +0200 Subject: [PATCH 09/13] Implement LogFormats for spanId and traceId PR fixes --- project/Dependencies.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index e662fa12..51a45d1e 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -67,8 +67,11 @@ object Dependencies { Orgs.scalaLangModules %% "scala-collection-compat" % Versions.scalaCollectionCompat % Test ) - lazy val opentelemetryZioLogging = opentelemetry ++ Seq( - Orgs.zio %% "zio-logging" % Versions.zioLogging + lazy val opentelemetryZioLogging = Seq( + Orgs.opentelemetry % "opentelemetry-api" % Versions.opentelemetry, + Orgs.opentelemetry % "opentelemetry-context" % Versions.opentelemetry, + Orgs.opentelemetry % "opentelemetry-sdk-testing" % Versions.opentelemetry % Test, + Orgs.zio %% "zio-logging" % Versions.zioLogging ) lazy val example = zio ++ Seq( From bff3d74641a8acf779562ba16013b161289dd89a Mon Sep 17 00:00:00 2001 From: Andrzej Ressel Date: Thu, 30 May 2024 22:36:40 +0200 Subject: [PATCH 10/13] Implement LogFormats for spanId and traceId --- README.md | 7 + build.sbt | 1 + docs/index.md | 7 + docs/opentelemetry-zio-logging.md | 127 ++++++++++++++++-- .../zio/logging/LogFormats.scala | 27 ++++ .../zio/logging/ZioLogging.scala | 32 +---- .../zio/logging/TelemetryLogFormatsSpec.scala | 6 +- .../ZioLoggingApp.scala | 15 ++- 8 files changed, 174 insertions(+), 48 deletions(-) create mode 100644 opentelemetry-zio-logging/src/main/scala/zio/telemetry/opentelemetry/zio/logging/LogFormats.scala rename scala-cli/{opentelemetry => opentelemetry-zio-logging}/ZioLoggingApp.scala (90%) diff --git a/README.md b/README.md index e4190e6d..74269768 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,12 @@ In order to use this library, we need to add the following line in our `build.sb libraryDependencies += "dev.zio" %% "zio-opentelemetry" % "" ``` +If you're also using [ZIO Logging](https://github.com/zio/zio-logging) you can combine OpenTelemetry with ZIO Logging using: + +```scala +libraryDependencies += "dev.zio" %% "zio-opentelemetry-zio-logging" % "" +``` + For using [OpenTracing](https://opentracing.io/) client we should add the following line in our `build.sbt` file: ```scala @@ -49,6 +55,7 @@ libraryDependencies += "dev.zio" %% "zio-opencensus" % "" You can find examples with full source code and instructions of how to run by following the links: - [OpenTelemetry Example](docs/opentelemetry-example.md) - [OpenTelemetry Instrumentation Example](docs/opentelemetry-instrumentation-example.md) +- [OpenTelemetry ZIO Logging Example](docs/opentelemetry-zio-logging.md) - [OpenTracing Example](docs/opentracing-example.md) ## Articles diff --git a/build.sbt b/build.sbt index c8f37911..97bd4764 100644 --- a/build.sbt +++ b/build.sbt @@ -157,6 +157,7 @@ lazy val opentelemetryZioLogging = project ) .settings(libraryDependencies ++= Dependencies.opentelemetryZioLogging) .settings(mimaSettings(failOnProblem = true)) + .settings(missinglinkIgnoreDestinationPackages += IgnoredPackage("scala.reflect")) .dependsOn(opentelemetry) lazy val opentracingExample = diff --git a/docs/index.md b/docs/index.md index ba83c7c4..65dbed1d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -32,6 +32,12 @@ In order to use this library, we need to add the following line in our `build.sb libraryDependencies += "dev.zio" %% "zio-opentelemetry" % "" ``` +If you're using [ZIO Logging](https://github.com/zio/zio-logging) you can combine OpenTelemetry with ZIO Logging using: + +```scala +libraryDependencies += "dev.zio" %% "zio-opentelemetry-zio-logging" % "" +``` + For using [OpenTracing](https://opentracing.io/) client we should add the following line in our `build.sbt` file: ```scala @@ -49,6 +55,7 @@ libraryDependencies += "dev.zio" %% "zio-opencensus" % "" You can find examples with full source code and instructions of how to run by following the links: - [OpenTelemetry Example](opentelemetry-example.md) - [OpenTelemetry Instrumentation Example](opentelemetry-instrumentation-example.md) +- [OpenTelemetry ZIO Logging Example](opentelemetry-zio-logging.md) - [OpenTracing Example](opentracing-example.md) ## Articles diff --git a/docs/opentelemetry-zio-logging.md b/docs/opentelemetry-zio-logging.md index 9f6427ca..de4b1c7d 100644 --- a/docs/opentelemetry-zio-logging.md +++ b/docs/opentelemetry-zio-logging.md @@ -22,15 +22,122 @@ In order to use `zio-opentelemetry` feature with `zio-logging` you should use `z This library implements [Log Format](https://zio.dev/zio-logging/formatting-log-records) for span information (`spanId` and `traceId`) ```scala -ZIO.serviceWithZIO[ContextStorage] { ctxStorage => - import zio.logging.console - import zio.logging.LogFormat._ - import zio.telemetry.opentelemetry.zio.logging.TelemetryLogFormats - - val spanIdLabel = label("spanId", TelemetryLogFormats.spanId(ctxStorage)) - val traceIdLabel = label("traceId", TelemetryLogFormats.traceId(ctxStorage)) - - val myLogFormat = timestamp.fixed(32) |-| level |-| label("message", quoted(line)) |-| spanIdLabel |-| traceIdLabel - val myConsoleLogger = console(myLogFormat) +//> using scala "2.13.14" +//> using dep dev.zio::zio:2.1.1 +//> using dep dev.zio::zio-opentelemetry:3.0.0-RC23+20-a55ed40c+20240530-2226-SNAPSHOT +//> using dep dev.zio::zio-opentelemetry-zio-logging:3.0.0-RC23+20-a55ed40c+20240530-2226-SNAPSHOT +//> using dep io.opentelemetry:opentelemetry-sdk:1.38.0 +//> using dep io.opentelemetry:opentelemetry-sdk-trace:1.38.0 +//> using dep io.opentelemetry:opentelemetry-exporter-logging-otlp:1.38.0 +//> using dep io.opentelemetry.semconv:opentelemetry-semconv:1.22.0-alpha + +import io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingSpanExporter +import io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingLogRecordExporter +import io.opentelemetry.api.common.Attributes +import io.opentelemetry.sdk.trace.SdkTracerProvider +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor +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 io.opentelemetry.sdk.OpenTelemetrySdk +import io.opentelemetry.api +import zio._ +import zio.logging.console +import zio.logging.LogFormat._ +import zio.telemetry.opentelemetry.tracing.Tracing +import zio.telemetry.opentelemetry.OpenTelemetry +import zio.telemetry.opentelemetry.context.ContextStorage +import zio.telemetry.opentelemetry.zio.logging.LogFormats +import zio.telemetry.opentelemetry.zio.logging.ZioLogging + +object ZioLoggingApp extends ZIOAppDefault { + + val instrumentationScopeName = "dev.zio.LoggingApp" + val resourceName = "logging-app" + + // Prints to stdout in OTLP Json format + val stdoutLoggerProvider: RIO[Scope, SdkLoggerProvider] = + for { + logRecordExporter <- ZIO.fromAutoCloseable(ZIO.succeed(OtlpJsonLoggingLogRecordExporter.create())) + logRecordProcessor <- ZIO.fromAutoCloseable(ZIO.succeed(SimpleLogRecordProcessor.create(logRecordExporter))) + loggerProvider <- + ZIO.fromAutoCloseable( + ZIO.succeed( + SdkLoggerProvider + .builder() + .setResource(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, resourceName))) + .addLogRecordProcessor(logRecordProcessor) + .build() + ) + ) + } yield loggerProvider + + // Prints to stdout in OTLP Json format + val stdoutTracerProvider: RIO[Scope, SdkTracerProvider] = + for { + spanExporter <- ZIO.fromAutoCloseable(ZIO.succeed(OtlpJsonLoggingSpanExporter.create())) + spanProcessor <- ZIO.fromAutoCloseable(ZIO.succeed(SimpleSpanProcessor.create(spanExporter))) + tracerProvider <- + ZIO.fromAutoCloseable( + ZIO.succeed( + SdkTracerProvider + .builder() + .setResource(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, resourceName))) + .addSpanProcessor(spanProcessor) + .build() + ) + ) + } yield tracerProvider + + val otelSdkLayer: TaskLayer[api.OpenTelemetry] = + OpenTelemetry.custom( + for { + tracerProvider <- stdoutTracerProvider + loggerProvider <- stdoutLoggerProvider + sdk <- ZIO.fromAutoCloseable( + ZIO.succeed( + OpenTelemetrySdk + .builder() + .setTracerProvider(tracerProvider) + .setLoggerProvider(loggerProvider) + .build() + ) + ) + } yield sdk + ) + + // Setup zio-logging with spanId and traceId labels + val loggingLayer: URLayer[LogFormats, Unit] = ZLayer { + for { + logFormats <- ZIO.service[LogFormats] + format = timestamp.fixed(32) |-| level |-| label("message", quoted(line)) |-| logFormats.spanIdLabel |-| logFormats.traceIdLabel + myConsoleLogger = console(format.highlight) + } yield Runtime.removeDefaultLoggers >>> myConsoleLogger + }.flatten + + + override def run = + ZIO + .serviceWithZIO[Tracing] { tracing => + val logic = for { + // Read user input + message <- Console.readLine + // Print span and trace ids along with message + _ <- ZIO.logInfo(s"User message: $message") + } yield () + + // All log messages produced by `logic` will be correlated with a "root_span" automatically + logic @@ tracing.aspects.root("root_span") + } + .provide( + otelSdkLayer, + OpenTelemetry.logging(instrumentationScopeName), + OpenTelemetry.tracing(instrumentationScopeName), + OpenTelemetry.contextZIO, + ZioLogging.logFormats, + loggingLayer + ) + } ``` \ No newline at end of file diff --git a/opentelemetry-zio-logging/src/main/scala/zio/telemetry/opentelemetry/zio/logging/LogFormats.scala b/opentelemetry-zio-logging/src/main/scala/zio/telemetry/opentelemetry/zio/logging/LogFormats.scala new file mode 100644 index 00000000..bf382916 --- /dev/null +++ b/opentelemetry-zio-logging/src/main/scala/zio/telemetry/opentelemetry/zio/logging/LogFormats.scala @@ -0,0 +1,27 @@ +package zio.telemetry.opentelemetry.zio.logging + +import zio.logging.LogFormat +import zio.logging.LogFormat.label + +trait LogFormats { + + /** + * Will print traceId from current span or nothing when not in span + */ + def traceId: LogFormat + + /** + * Will print spanId from current span or nothing when not in span + */ + def spanId: LogFormat + + /** + * Label with `traceId` key and [[traceId]] value + */ + def traceIdLabel: LogFormat = label("traceId", traceId) + + /** + * Label with `spanId` key and [[spanId]] value + */ + def spanIdLabel: LogFormat = label("spanId", spanId) +} \ No newline at end of file diff --git a/opentelemetry-zio-logging/src/main/scala/zio/telemetry/opentelemetry/zio/logging/ZioLogging.scala b/opentelemetry-zio-logging/src/main/scala/zio/telemetry/opentelemetry/zio/logging/ZioLogging.scala index 4edaef5d..541b8511 100644 --- a/opentelemetry-zio-logging/src/main/scala/zio/telemetry/opentelemetry/zio/logging/ZioLogging.scala +++ b/opentelemetry-zio-logging/src/main/scala/zio/telemetry/opentelemetry/zio/logging/ZioLogging.scala @@ -4,43 +4,19 @@ import io.opentelemetry.api.trace.{Span, SpanContext} import io.opentelemetry.context.Context import zio._ import zio.logging.LogFormat -import zio.logging.LogFormat.label import zio.telemetry.opentelemetry.context.ContextStorage -trait ZioLogging { - - /** - * Will print traceId from current span or nothing when not in span - */ - def traceId(): LogFormat - - /** - * Will print spanId from current span or nothing when not in span - */ - def spanId(): LogFormat - - /** - * Label with `traceId` key and [[traceId]] value - */ - def traceIdLabel(): LogFormat = label("traceId", traceId()) - - /** - * Label with `spanId` key and [[spanId]] value - */ - def spanIdLabel(): LogFormat = label("spanId", spanId()) -} - object ZioLogging { - def live: ZLayer[ContextStorage, Nothing, ZioLogging] = ZLayer { + def logFormats: ZLayer[ContextStorage, Nothing, LogFormats] = ZLayer { for { ctxStorage <- ZIO.service[ContextStorage] - } yield new ZioLogging { - override def traceId(): LogFormat = LogFormat.make { (builder, _, _, _, _, _, fiberRefs, _, _) => + } yield new LogFormats { + override def traceId: LogFormat = LogFormat.make { (builder, _, _, _, _, _, fiberRefs, _, _) => getSpanContext(ctxStorage, fiberRefs).map(_.getTraceId).fold(())(builder.appendText(_)) } - override def spanId(): LogFormat = LogFormat.make { (builder, _, _, _, _, _, fiberRefs, _, _) => + override def spanId: LogFormat = LogFormat.make { (builder, _, _, _, _, _, fiberRefs, _, _) => getSpanContext(ctxStorage, fiberRefs).map(_.getSpanId).fold(())(builder.appendText(_)) } diff --git a/opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala b/opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala index 7a5a0bfb..da4400b5 100644 --- a/opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala +++ b/opentelemetry-zio-logging/src/test/scala/zio/telemetry/opentelemetry/zio/logging/TelemetryLogFormatsSpec.scala @@ -44,8 +44,8 @@ object TelemetryLogFormatsSpec extends ZIOSpecDefault { val logs = mutable.Buffer[String]() for { - zioLogging <- ZIO.service[ZioLogging] - format = zioLogging.spanIdLabel() |-| zioLogging.traceIdLabel() + logFormats <- ZIO.service[LogFormats] + format = logFormats.spanIdLabel |-| logFormats.traceIdLabel zLogger = format.toLogger.map(logs.append(_)) _ <- zio.ZIO.logInfo("TEST").withLogger(zLogger) @@ span("Span") @@ root("Root") spans <- getFinishedSpans @@ -54,6 +54,6 @@ object TelemetryLogFormatsSpec extends ZIOSpecDefault { } yield assertTrue(log == s"spanId=${child.getSpanId} traceId=${child.getTraceId}") } } - }.provide(removeDefaultLoggers, tracingMockLayer(), ContextStorage.fiberRef, ZioLogging.live) + }.provide(removeDefaultLoggers, tracingMockLayer(), ContextStorage.fiberRef, ZioLogging.logFormats) } diff --git a/scala-cli/opentelemetry/ZioLoggingApp.scala b/scala-cli/opentelemetry-zio-logging/ZioLoggingApp.scala similarity index 90% rename from scala-cli/opentelemetry/ZioLoggingApp.scala rename to scala-cli/opentelemetry-zio-logging/ZioLoggingApp.scala index e2218339..fa29db56 100644 --- a/scala-cli/opentelemetry/ZioLoggingApp.scala +++ b/scala-cli/opentelemetry-zio-logging/ZioLoggingApp.scala @@ -1,7 +1,7 @@ //> using scala "2.13.14" //> using dep dev.zio::zio:2.1.1 -//> using dep dev.zio::zio-opentelemetry:3.0.0-RC23+18-b26627af+20240528-0156-SNAPSHOT -//> using dep dev.zio::zio-opentelemetry-zio-logging:3.0.0-RC23+18-b26627af+20240528-0156-SNAPSHOT +//> using dep dev.zio::zio-opentelemetry:3.0.0-RC23+20-a55ed40c+20240530-2226-SNAPSHOT +//> using dep dev.zio::zio-opentelemetry-zio-logging:3.0.0-RC23+20-a55ed40c+20240530-2226-SNAPSHOT //> using dep io.opentelemetry:opentelemetry-sdk:1.38.0 //> using dep io.opentelemetry:opentelemetry-sdk-trace:1.38.0 //> using dep io.opentelemetry:opentelemetry-exporter-logging-otlp:1.38.0 @@ -24,6 +24,7 @@ import zio.logging.LogFormat._ import zio.telemetry.opentelemetry.tracing.Tracing import zio.telemetry.opentelemetry.OpenTelemetry import zio.telemetry.opentelemetry.context.ContextStorage +import zio.telemetry.opentelemetry.zio.logging.LogFormats import zio.telemetry.opentelemetry.zio.logging.ZioLogging object ZioLoggingApp extends ZIOAppDefault { @@ -83,11 +84,11 @@ object ZioLoggingApp extends ZIOAppDefault { ) // Setup zio-logging with spanId and traceId labels - val loggingLayer: URLayer[ZioLogging, Unit] = ZLayer { + val loggingLayer: URLayer[LogFormats, Unit] = ZLayer { for { - zioLogging <- ZIO.service[ZioLogging] - format = timestamp.fixed(32) |-| level |-| label("message", quoted(line)) |-| zioLogging.spanIdLabel() |-| zioLogging.traceIdLabel() - myConsoleLogger = console(format) + logFormats <- ZIO.service[LogFormats] + format = timestamp.fixed(32) |-| level |-| label("message", quoted(line)) |-| logFormats.spanIdLabel |-| logFormats.traceIdLabel + myConsoleLogger = console(format.highlight) } yield Runtime.removeDefaultLoggers >>> myConsoleLogger }.flatten @@ -110,7 +111,7 @@ object ZioLoggingApp extends ZIOAppDefault { OpenTelemetry.logging(instrumentationScopeName), OpenTelemetry.tracing(instrumentationScopeName), OpenTelemetry.contextZIO, - ZioLogging.live, + ZioLogging.logFormats, loggingLayer ) From a6e244b54ab569ea5508870454f1f71bcbd0c3c8 Mon Sep 17 00:00:00 2001 From: Andrzej Ressel Date: Thu, 30 May 2024 22:43:27 +0200 Subject: [PATCH 11/13] Implement LogFormats for spanId and traceId --- docs/opentelemetry-zio-logging.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/opentelemetry-zio-logging.md b/docs/opentelemetry-zio-logging.md index de4b1c7d..2dfb530c 100644 --- a/docs/opentelemetry-zio-logging.md +++ b/docs/opentelemetry-zio-logging.md @@ -19,7 +19,8 @@ In order to use `zio-opentelemetry` feature with `zio-logging` you should use `z ### Log formats -This library implements [Log Format](https://zio.dev/zio-logging/formatting-log-records) for span information (`spanId` and `traceId`) +This library implements [Log Format](https://zio.dev/zio-logging/formatting-log-records) for span information (`spanId` and `traceId`). +To use them you need a `LogFormats` service in the environment. For this, use the `ZioLogging.logFormats` layer which in turn required a suitable `ContextStorage` implementation. ```scala //> using scala "2.13.14" From 19739000071102f863b8877d4ba31d5c976b835f Mon Sep 17 00:00:00 2001 From: Andrzej Ressel Date: Thu, 30 May 2024 22:44:32 +0200 Subject: [PATCH 12/13] Implement LogFormats for spanId and traceId --- .../zio/telemetry/opentelemetry/zio/logging/LogFormats.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opentelemetry-zio-logging/src/main/scala/zio/telemetry/opentelemetry/zio/logging/LogFormats.scala b/opentelemetry-zio-logging/src/main/scala/zio/telemetry/opentelemetry/zio/logging/LogFormats.scala index bf382916..6682c523 100644 --- a/opentelemetry-zio-logging/src/main/scala/zio/telemetry/opentelemetry/zio/logging/LogFormats.scala +++ b/opentelemetry-zio-logging/src/main/scala/zio/telemetry/opentelemetry/zio/logging/LogFormats.scala @@ -24,4 +24,4 @@ trait LogFormats { * Label with `spanId` key and [[spanId]] value */ def spanIdLabel: LogFormat = label("spanId", spanId) -} \ No newline at end of file +} From d9abcf22737f30d7811ef3971b66b8cb9be8fa25 Mon Sep 17 00:00:00 2001 From: Andrzej Ressel Date: Fri, 31 May 2024 18:50:10 +0200 Subject: [PATCH 13/13] Implement LogFormats for spanId and traceId --- docs/opentelemetry-zio-logging.md | 4 ++-- scala-cli/opentelemetry-zio-logging/ZioLoggingApp.scala | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/opentelemetry-zio-logging.md b/docs/opentelemetry-zio-logging.md index 2dfb530c..fd6d9574 100644 --- a/docs/opentelemetry-zio-logging.md +++ b/docs/opentelemetry-zio-logging.md @@ -25,8 +25,8 @@ To use them you need a `LogFormats` service in the environment. For this, use th ```scala //> using scala "2.13.14" //> using dep dev.zio::zio:2.1.1 -//> using dep dev.zio::zio-opentelemetry:3.0.0-RC23+20-a55ed40c+20240530-2226-SNAPSHOT -//> using dep dev.zio::zio-opentelemetry-zio-logging:3.0.0-RC23+20-a55ed40c+20240530-2226-SNAPSHOT +//> using dep dev.zio::zio-opentelemetry:3.0.0-RC24 +//> using dep dev.zio::zio-opentelemetry-zio-logging:3.0.0-RC24 //> using dep io.opentelemetry:opentelemetry-sdk:1.38.0 //> using dep io.opentelemetry:opentelemetry-sdk-trace:1.38.0 //> using dep io.opentelemetry:opentelemetry-exporter-logging-otlp:1.38.0 diff --git a/scala-cli/opentelemetry-zio-logging/ZioLoggingApp.scala b/scala-cli/opentelemetry-zio-logging/ZioLoggingApp.scala index fa29db56..cd8825bd 100644 --- a/scala-cli/opentelemetry-zio-logging/ZioLoggingApp.scala +++ b/scala-cli/opentelemetry-zio-logging/ZioLoggingApp.scala @@ -1,7 +1,7 @@ //> using scala "2.13.14" //> using dep dev.zio::zio:2.1.1 -//> using dep dev.zio::zio-opentelemetry:3.0.0-RC23+20-a55ed40c+20240530-2226-SNAPSHOT -//> using dep dev.zio::zio-opentelemetry-zio-logging:3.0.0-RC23+20-a55ed40c+20240530-2226-SNAPSHOT +//> using dep dev.zio::zio-opentelemetry:3.0.0-RC24 +//> using dep dev.zio::zio-opentelemetry-zio-logging:3.0.0-RC24 //> using dep io.opentelemetry:opentelemetry-sdk:1.38.0 //> using dep io.opentelemetry:opentelemetry-sdk-trace:1.38.0 //> using dep io.opentelemetry:opentelemetry-exporter-logging-otlp:1.38.0