diff --git a/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/OpenTelemetry.scala b/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/OpenTelemetry.scala index 26b774a6..e2979fe0 100644 --- a/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/OpenTelemetry.scala +++ b/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/OpenTelemetry.scala @@ -57,7 +57,8 @@ object OpenTelemetry { def tracing( instrumentationScopeName: String, instrumentationVersion: Option[String] = None, - schemaUrl: Option[String] = None + schemaUrl: Option[String] = None, + logAnnotated: Boolean = false ): URLayer[api.OpenTelemetry with ContextStorage, Tracing] = { val tracerLayer = ZLayer( ZIO.serviceWith[api.OpenTelemetry] { openTelemetry => @@ -70,7 +71,7 @@ object OpenTelemetry { } ) - tracerLayer >>> Tracing.live + tracerLayer >>> Tracing.live(logAnnotated) } /** 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 af75bbda..72c4934c 100644 --- a/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/tracing/Tracing.scala +++ b/opentelemetry/src/main/scala/zio/telemetry/opentelemetry/tracing/Tracing.scala @@ -516,16 +516,16 @@ trait Tracing { self => object Tracing { - def live: URLayer[Tracer with ContextStorage, Tracing] = + def live(logAnnotated: Boolean = false): URLayer[Tracer with ContextStorage, Tracing] = ZLayer.scoped { for { tracer <- ZIO.service[Tracer] ctxStorage <- ZIO.service[ContextStorage] - tracing <- scoped(tracer, ctxStorage) + tracing <- scoped(tracer, ctxStorage, logAnnotated) } yield tracing } - def scoped(tracer: Tracer, ctxStorage: ContextStorage): URIO[Scope, Tracing] = { + def scoped(tracer: Tracer, ctxStorage: ContextStorage, logAnnotated: Boolean = false): URIO[Scope, Tracing] = { val acquire = ZIO.succeed { new Tracing { self => @@ -819,17 +819,18 @@ object Tracing { links: Seq[SpanContext] )(implicit trace: Trace): UIO[(UIO[Unit], Context)] = for { - nanos <- currentNanos - span <- ZIO.succeed( - tracer - .spanBuilder(spanName) - .setNoParent() - .setAllAttributes(attributes) - .setSpanKind(spanKind) - .setStartTimestamp(nanos, TimeUnit.NANOSECONDS) - .addLinks(links) - .startSpan() - ) + nanos <- currentNanos + allAttributes <- injectLogAnnotations(attributes) + span <- ZIO.succeed( + tracer + .spanBuilder(spanName) + .setNoParent() + .setAllAttributes(allAttributes) + .setSpanKind(spanKind) + .setStartTimestamp(nanos, TimeUnit.NANOSECONDS) + .addLinks(links) + .startSpan() + ) } yield (endSpan(span), Context.root().`with`(span)) private def createChild( @@ -840,17 +841,18 @@ object Tracing { links: Seq[SpanContext] )(implicit trace: Trace): UIO[(UIO[Unit], Context)] = for { - nanos <- currentNanos - span <- ZIO.succeed( - tracer - .spanBuilder(spanName) - .setParent(parentCtx) - .setAllAttributes(attributes) - .setSpanKind(spanKind) - .setStartTimestamp(nanos, TimeUnit.NANOSECONDS) - .addLinks(links) - .startSpan() - ) + nanos <- currentNanos + allAttributes <- injectLogAnnotations(attributes) + span <- ZIO.succeed( + tracer + .spanBuilder(spanName) + .setParent(parentCtx) + .setAllAttributes(allAttributes) + .setSpanKind(spanKind) + .setStartTimestamp(nanos, TimeUnit.NANOSECONDS) + .addLinks(links) + .startSpan() + ) } yield (endSpan(span), parentCtx.`with`(span)) private implicit class SpanBuilderOps(spanBuilder: SpanBuilder) { @@ -866,13 +868,14 @@ object Tracing { links: Seq[SpanContext] )(implicit trace: Trace): UIO[Context] = for { - nanos <- currentNanos - span <- + nanos <- currentNanos + allAttributes <- injectLogAnnotations(attributes) + span <- ZIO.succeed( tracer .spanBuilder(spanName) .setParent(parentCtx) - .setAllAttributes(attributes) + .setAllAttributes(allAttributes) .setSpanKind(spanKind) .setStartTimestamp(nanos, TimeUnit.NANOSECONDS) .addLinks(links) @@ -907,6 +910,19 @@ object Tracing { )(implicit trace: Trace): UIO[Unit] = ZIO.succeed(propagator.instance.inject(ctx, carrier.kernel, carrier)) + private def injectLogAnnotations(attributes: Attributes): UIO[Attributes] = + if (logAnnotated) { + for { + annotations <- ZIO.logAnnotations + } yield annotations + .foldLeft(Attributes.builder()) { case (builder, (annotationKey, annotationValue)) => + builder.put(annotationKey, annotationValue) + } + .putAll(attributes) + .build() + } else { + ZIO.succeed(attributes) + } } } diff --git a/opentelemetry/src/test/scala/zio/telemetry/opentelemetry/logging/LoggingTest.scala b/opentelemetry/src/test/scala/zio/telemetry/opentelemetry/logging/LoggingTest.scala index 90cc87e2..1d152e64 100644 --- a/opentelemetry/src/test/scala/zio/telemetry/opentelemetry/logging/LoggingTest.scala +++ b/opentelemetry/src/test/scala/zio/telemetry/opentelemetry/logging/LoggingTest.scala @@ -134,7 +134,7 @@ object LoggingTest extends ZIOSpecDefault { } }.provide( loggingMockLayer("tracing context (fiberRef)"), - TracingTest.tracingMockLayer, + TracingTest.tracingMockLayer(), ContextStorage.fiberRef ), test("tracing context (openTelemtryContext)") { @@ -167,7 +167,7 @@ object LoggingTest extends ZIOSpecDefault { } }.provide( loggingMockLayer("tracing context (openTelemtryContext)"), - TracingTest.tracingMockLayer, + TracingTest.tracingMockLayer(), ContextStorage.native ) ) diff --git a/opentelemetry/src/test/scala/zio/telemetry/opentelemetry/metrics/MeterTest.scala b/opentelemetry/src/test/scala/zio/telemetry/opentelemetry/metrics/MeterTest.scala index aab5e051..3abd8b11 100644 --- a/opentelemetry/src/test/scala/zio/telemetry/opentelemetry/metrics/MeterTest.scala +++ b/opentelemetry/src/test/scala/zio/telemetry/opentelemetry/metrics/MeterTest.scala @@ -174,7 +174,7 @@ object MeterTest extends ZIOSpecDefault { ) } } - ).provide(inMemoryMetricReaderLayer, meterLayer(), ContextStorage.fiberRef, TracingTest.tracingMockLayer) + ).provide(inMemoryMetricReaderLayer, meterLayer(), ContextStorage.fiberRef, TracingTest.tracingMockLayer()) private val logAnnotatedSpec = suite("log annotated")( 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 d1736b09..55f107f6 100644 --- a/opentelemetry/src/test/scala/zio/telemetry/opentelemetry/tracing/TracingTest.scala +++ b/opentelemetry/src/test/scala/zio/telemetry/opentelemetry/tracing/TracingTest.scala @@ -32,20 +32,21 @@ object TracingTest extends ZIOSpecDefault { ZEnvironment(inMemorySpanExporter).add(tracer) }) - val tracingMockLayer: URLayer[ContextStorage, Tracing with InMemorySpanExporter with Tracer] = - inMemoryTracerLayer >>> (Tracing.live ++ inMemoryTracerLayer) + 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 - .service[InMemorySpanExporter] - .map(_.getFinishedSpanItems.asScala.toList) + ZIO.serviceWith[InMemorySpanExporter](_.getFinishedSpanItems.asScala.toList) def spec: Spec[Any, Throwable] = suite("zio opentelemetry")( suite("Tracing")( creationSpec, spansSpec, - spanScopedSpec + spanScopedSpec, + spanWithLogAnnotationsSpec ) ) @@ -53,7 +54,7 @@ object TracingTest extends ZIOSpecDefault { suite("creation")( test("live") { for { - _ <- ZIO.scoped(Tracing.live.build) + _ <- ZIO.scoped(Tracing.live().build) finishedSpans <- getFinishedSpans } yield assert(finishedSpans)(hasSize(equalTo(0))) }.provide(inMemoryTracerLayer, ContextStorage.fiberRef) @@ -549,7 +550,7 @@ object TracingTest extends ZIOSpecDefault { } yield assert(ko)(isSome(failureAssertion)) && assert(ok)(isSome(successAssertion)) } } - ).provide(tracingMockLayer, ContextStorage.fiberRef) + ).provide(tracingMockLayer(), ContextStorage.fiberRef) private val spanScopedSpec = suite("scoped spans")( @@ -651,5 +652,49 @@ object TracingTest extends ZIOSpecDefault { } yield assert(tags.get(AttributeKey.stringKey("string")))(equalTo("bar")) } } - ).provide(tracingMockLayer, ContextStorage.fiberRef) + ).provide(tracingMockLayer(), ContextStorage.fiberRef) + + private val spanWithLogAnnotationsSpec = suite("spans with log annotations")( + test("add log annotations") { + ZIO.serviceWithZIO[Tracing] { tracing => + import tracing.aspects._ + + for { + _ <- ZIO.logAnnotate("log-attribute", "foo") { + ZIO.unit @@ span("Root", attributes = Attributes(Attribute.string("root-attribute", "bar"))) + } + spans <- getFinishedSpans + tags = spans.head.getAttributes + } yield assert(tags.get(AttributeKey.stringKey("root-attribute")))(equalTo("bar")) && + assert(tags.get(AttributeKey.stringKey("log-attribute")))(equalTo("foo")) + } + }.provide(tracingMockLayer(true), ContextStorage.fiberRef), + test("span attributes override log annotated") { + ZIO.serviceWithZIO[Tracing] { tracing => + import tracing.aspects._ + + for { + _ <- ZIO.logAnnotate("some-attribute", "foo") { + ZIO.unit @@ span("Root", attributes = Attributes(Attribute.string("some-attribute", "bar"))) + } + spans <- getFinishedSpans + tags = spans.head.getAttributes + } yield assert(tags.get(AttributeKey.stringKey("some-attribute")))(equalTo("bar")) + } + }.provide(tracingMockLayer(true), ContextStorage.fiberRef), + test("not add log annotations") { + ZIO.serviceWithZIO[Tracing] { tracing => + import tracing.aspects._ + + for { + _ <- ZIO.logAnnotate("log-attribute", "foo") { + ZIO.unit @@ span("Root", attributes = Attributes(Attribute.string("root-attribute", "bar"))) + } + spans <- getFinishedSpans + tags = spans.head.getAttributes + } yield assert(tags.get(AttributeKey.stringKey("root-attribute")))(equalTo("bar")) && + assert(Option(tags.get(AttributeKey.stringKey("log-attribute"))))(isNone) + } + }.provide(tracingMockLayer(), ContextStorage.fiberRef) + ) }