From 649a919f3762bf60e80fbef9c5a9a193f12cd0b0 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Thu, 5 Oct 2023 11:21:55 +0300 Subject: [PATCH 1/8] Remove effect constraint from TextMapPropagator, update packages, add tests --- .../scala/org/typelevel/otel4s/Otel4s.scala | 3 +- .../typelevel/otel4s/ContextPropagators.scala | 39 -------- .../propagation/ContextPropagators.scala | 80 +++++++++++++++ .../propagation}/TextMapGetter.scala | 2 +- .../propagation}/TextMapPropagator.scala | 99 ++++++++++++------- .../propagation}/TextMapSetter.scala | 2 +- .../propagation}/TextMapUpdater.scala | 2 +- .../propagation/ContextPropagatorsSuite.scala | 57 +++++++++++ .../propagation}/TextMapGetterProps.scala | 2 +- .../propagation}/TextMapGetterSuite.scala | 4 +- .../propagation/TextMapPropagatorSuite.scala | 69 +++++++++++++ .../propagation}/TextMapSetterProps.scala | 2 +- .../propagation}/TextMapSetterSuite.scala | 2 +- .../propagation}/TextMapUpdaterProps.scala | 2 +- .../propagation}/TextMapUpdaterSuite.scala | 4 +- .../org/typelevel/otel4s/trace/Tracer.scala | 4 +- .../org/typelevel/otel4s/java/OtelJava.scala | 6 +- .../otel4s/java/ContextPropagatorsImpl.scala | 9 +- .../typelevel/otel4s/java/Conversions.scala | 2 + .../otel4s/java/TextMapPropagatorImpl.scala | 24 ++--- .../otel4s/java/trace/TracerBuilderImpl.scala | 4 +- .../otel4s/java/trace/TracerImpl.scala | 10 +- .../java/trace/TracerProviderImpl.scala | 6 +- .../typelevel/otel4s/java/trace/Traces.scala | 5 +- .../otel4s/java/trace/TracerSuite.scala | 2 +- 25 files changed, 324 insertions(+), 117 deletions(-) delete mode 100644 core/common/src/main/scala/org/typelevel/otel4s/ContextPropagators.scala create mode 100644 core/common/src/main/scala/org/typelevel/otel4s/context/propagation/ContextPropagators.scala rename core/common/src/main/scala/org/typelevel/otel4s/{ => context/propagation}/TextMapGetter.scala (98%) rename core/common/src/main/scala/org/typelevel/otel4s/{ => context/propagation}/TextMapPropagator.scala (53%) rename core/common/src/main/scala/org/typelevel/otel4s/{ => context/propagation}/TextMapSetter.scala (97%) rename core/common/src/main/scala/org/typelevel/otel4s/{ => context/propagation}/TextMapUpdater.scala (98%) create mode 100644 core/common/src/test/scala/org/typelevel/otel4s/context/propagation/ContextPropagatorsSuite.scala rename core/common/src/test/scala/org/typelevel/otel4s/{ => context/propagation}/TextMapGetterProps.scala (98%) rename core/common/src/test/scala/org/typelevel/otel4s/{ => context/propagation}/TextMapGetterSuite.scala (97%) create mode 100644 core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapPropagatorSuite.scala rename core/common/src/test/scala/org/typelevel/otel4s/{ => context/propagation}/TextMapSetterProps.scala (97%) rename core/common/src/test/scala/org/typelevel/otel4s/{ => context/propagation}/TextMapSetterSuite.scala (97%) rename core/common/src/test/scala/org/typelevel/otel4s/{ => context/propagation}/TextMapUpdaterProps.scala (97%) rename core/common/src/test/scala/org/typelevel/otel4s/{ => context/propagation}/TextMapUpdaterSuite.scala (96%) diff --git a/core/all/src/main/scala/org/typelevel/otel4s/Otel4s.scala b/core/all/src/main/scala/org/typelevel/otel4s/Otel4s.scala index bc19940b6..ff3583878 100644 --- a/core/all/src/main/scala/org/typelevel/otel4s/Otel4s.scala +++ b/core/all/src/main/scala/org/typelevel/otel4s/Otel4s.scala @@ -16,13 +16,14 @@ package org.typelevel.otel4s +import org.typelevel.otel4s.context.propagation.ContextPropagators import org.typelevel.otel4s.metrics.MeterProvider import org.typelevel.otel4s.trace.TracerProvider trait Otel4s[F[_]] { type Ctx - def propagators: ContextPropagators[F, Ctx] + def propagators: ContextPropagators[Ctx] /** A registry for creating named meters. */ diff --git a/core/common/src/main/scala/org/typelevel/otel4s/ContextPropagators.scala b/core/common/src/main/scala/org/typelevel/otel4s/ContextPropagators.scala deleted file mode 100644 index f1978ddce..000000000 --- a/core/common/src/main/scala/org/typelevel/otel4s/ContextPropagators.scala +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2022 Typelevel - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.typelevel.otel4s - -import cats.Applicative - -trait ContextPropagators[F[_], Ctx] { - def textMapPropagator: TextMapPropagator[F, Ctx] -} - -object ContextPropagators { - - /** Creates a no-op implementation of the [[ContextPropagators]]. - * - * A [[TextMapPropagator]] has no-op implementation too. - * - * @tparam F - * the higher-kinded type of a polymorphic effect - */ - def noop[F[_]: Applicative, Ctx]: ContextPropagators[F, Ctx] = - new ContextPropagators[F, Ctx] { - def textMapPropagator: TextMapPropagator[F, Ctx] = - TextMapPropagator.noop - } -} diff --git a/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/ContextPropagators.scala b/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/ContextPropagators.scala new file mode 100644 index 000000000..3caa320ca --- /dev/null +++ b/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/ContextPropagators.scala @@ -0,0 +1,80 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.typelevel.otel4s.context.propagation + +/** A container of the registered propagators for every supported format. + * + * @tparam Ctx + * the type of the Context + */ +trait ContextPropagators[Ctx] { + + /** Returns a text map propagator to extract or inject data. + */ + def textMapPropagator: TextMapPropagator[Ctx] +} + +object ContextPropagators { + + /** Creates a [[ContextPropagators]] which can be used to extract and inject + * context in text payloads with the given [[TextMapPropagator]]. + * + * '''Hint''': use [[TextMapPropagator.composite]] to combine multiple text + * map propagators. + * + * @example + * {{{ + * val w3cPropagator: TextMapPropagator[Context] = ??? + * val httpTracePropagator: TextMapPropagator[Context] = ??? + * val textMapPropagator = TextMapPropagator.composite(w3cPropagator, httpTracePropagator) + * val contextPropagators = ContextPropagators.create(textMapPropagator) + * }}} + * + * @param textMapPropagator + * the text map propagator to extract or inject data + * + * @tparam Ctx + * the context to use to extra or inject data + */ + def create[Ctx]( + textMapPropagator: TextMapPropagator[Ctx] + ): ContextPropagators[Ctx] = + new Default(textMapPropagator) + + /** Creates a no-op implementation of the [[ContextPropagators]]. + * + * A [[TextMapPropagator]] has no-op implementation too. + */ + def noop[Ctx]: ContextPropagators[Ctx] = + new Noop + + private final class Noop[Ctx] extends ContextPropagators[Ctx] { + val textMapPropagator: TextMapPropagator[Ctx] = + TextMapPropagator.noop + + override def toString: String = + "ContextPropagators.Noop" + } + + private final class Default[Ctx]( + val textMapPropagator: TextMapPropagator[Ctx] + ) extends ContextPropagators[Ctx] { + override def toString: String = + s"ContextPropagators.Default{textMapPropagator=${textMapPropagator.toString}}" + } + +} diff --git a/core/common/src/main/scala/org/typelevel/otel4s/TextMapGetter.scala b/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/TextMapGetter.scala similarity index 98% rename from core/common/src/main/scala/org/typelevel/otel4s/TextMapGetter.scala rename to core/common/src/main/scala/org/typelevel/otel4s/context/propagation/TextMapGetter.scala index fb38f3f00..25e8347e4 100644 --- a/core/common/src/main/scala/org/typelevel/otel4s/TextMapGetter.scala +++ b/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/TextMapGetter.scala @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.typelevel.otel4s +package org.typelevel.otel4s.context.propagation import cats.Contravariant diff --git a/core/common/src/main/scala/org/typelevel/otel4s/TextMapPropagator.scala b/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/TextMapPropagator.scala similarity index 53% rename from core/common/src/main/scala/org/typelevel/otel4s/TextMapPropagator.scala rename to core/common/src/main/scala/org/typelevel/otel4s/context/propagation/TextMapPropagator.scala index d09ad7c63..490c3a378 100644 --- a/core/common/src/main/scala/org/typelevel/otel4s/TextMapPropagator.scala +++ b/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/TextMapPropagator.scala @@ -14,9 +14,10 @@ * limitations under the License. */ -package org.typelevel.otel4s +package org.typelevel.otel4s.context.propagation -import cats.Applicative +import cats.data.NonEmptyList +import cats.syntax.foldable._ /** The process of propagating data across process boundaries involves injecting * and extracting values in the form of text into carriers that travel in-band. @@ -30,10 +31,13 @@ import cats.Applicative * interceptors. On the client side, values are injected into the carriers, * while on the server side, values are extracted from them. * - * @tparam F - * the higher-kinded type of a polymorphic effect + * @tparam Ctx + * the context to use to extra or inject data */ -trait TextMapPropagator[F[_], Ctx] { +trait TextMapPropagator[Ctx] { + + /** The list of propagation fields. */ + def fields: List[String] /** Extracts key-value pairs from the given `carrier` and adds them to the * given context. @@ -52,20 +56,6 @@ trait TextMapPropagator[F[_], Ctx] { */ def extract[A: TextMapGetter](ctx: Ctx, carrier: A): Ctx - /** Injects data from the context into the given mutable `carrier` for - * downstream consumers, for example as HTTP headers. - * - * @param ctx - * the context containing the value to be injected - * - * @param carrier - * holds propagation fields - * - * @tparam A - * the type of the carrier, which is mutable - */ - def inject[A: TextMapSetter](ctx: Ctx, carrier: A): F[Unit] - /** Injects data from the context into a copy of the given immutable `carrier` * for downstream consumers, for example as HTTP headers. * @@ -84,31 +74,70 @@ trait TextMapPropagator[F[_], Ctx] { * @return * a copy of the carrier, with new fields injected */ - def injected[A: TextMapUpdater](ctx: Ctx, carrier: A): A + def inject[A: TextMapUpdater](ctx: Ctx, carrier: A): A } object TextMapPropagator { - def apply[F[_], Ctx](implicit - ev: TextMapPropagator[F, Ctx] - ): TextMapPropagator[F, Ctx] = ev + /** Creates a [[TextMapPropagator]] which delegates injection and extraction + * to the provided propagators. + * + * @example + * {{{ + * val w3cPropagator: TextMapPropagator[Context] = ??? + * val httpTracePropagator: TextMapPropagator[Context] = ??? + * val textMapPropagator = TextMapPropagator.composite(w3cPropagator, httpTracePropagator) + * }}} + * @tparam Ctx + * the context to use to extra or inject data + */ + def composite[Ctx]( + propagators: TextMapPropagator[Ctx]* + ): TextMapPropagator[Ctx] = + propagators.toList match { + case Nil => new Noop[Ctx] + case head :: Nil => head + case head :: tail => new Multi(NonEmptyList(head, tail)) + } /** Creates a no-op implementation of the [[TextMapPropagator]]. * * All propagation operations are no-op. - * - * @tparam F - * the higher-kinded type of a polymorphic effect */ - def noop[F[_]: Applicative, Ctx]: TextMapPropagator[F, Ctx] = - new TextMapPropagator[F, Ctx] { - def extract[A: TextMapGetter](ctx: Ctx, carrier: A): Ctx = - ctx + def noop[Ctx]: TextMapPropagator[Ctx] = + new Noop - def inject[A: TextMapSetter](ctx: Ctx, carrier: A): F[Unit] = - Applicative[F].unit + private final class Noop[Ctx] extends TextMapPropagator[Ctx] { + def fields: List[String] = + Nil + + def extract[A: TextMapGetter](ctx: Ctx, carrier: A): Ctx = + ctx + + def inject[A: TextMapUpdater](ctx: Ctx, carrier: A): A = + carrier + + override def toString: String = "TextMapPropagator.Noop" + } + + private final class Multi[Ctx]( + val propagators: NonEmptyList[TextMapPropagator[Ctx]] + ) extends TextMapPropagator[Ctx] { + val fields: List[String] = + propagators.toList.flatMap(_.fields) + + def extract[A: TextMapGetter](ctx: Ctx, carrier: A): Ctx = + propagators.foldLeft(ctx) { (ctx, propagator) => + propagator.extract(ctx, carrier) + } + + def inject[A: TextMapUpdater](ctx: Ctx, carrier: A): A = + propagators.foldLeft(carrier) { (carrier, propagator) => + propagator.inject(ctx, carrier) + } + + override def toString: String = + s"TextMapPropagator.Multi(${propagators.map(_.toString).mkString_(", ")})" + } - def injected[A: TextMapUpdater](ctx: Ctx, carrier: A): A = - carrier - } } diff --git a/core/common/src/main/scala/org/typelevel/otel4s/TextMapSetter.scala b/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/TextMapSetter.scala similarity index 97% rename from core/common/src/main/scala/org/typelevel/otel4s/TextMapSetter.scala rename to core/common/src/main/scala/org/typelevel/otel4s/context/propagation/TextMapSetter.scala index c6adfcdd5..c79d9c216 100644 --- a/core/common/src/main/scala/org/typelevel/otel4s/TextMapSetter.scala +++ b/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/TextMapSetter.scala @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.typelevel.otel4s +package org.typelevel.otel4s.context.propagation import scala.collection.mutable import scala.collection.mutable.Buffer diff --git a/core/common/src/main/scala/org/typelevel/otel4s/TextMapUpdater.scala b/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/TextMapUpdater.scala similarity index 98% rename from core/common/src/main/scala/org/typelevel/otel4s/TextMapUpdater.scala rename to core/common/src/main/scala/org/typelevel/otel4s/context/propagation/TextMapUpdater.scala index ed2fb6476..5bc179da5 100644 --- a/core/common/src/main/scala/org/typelevel/otel4s/TextMapUpdater.scala +++ b/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/TextMapUpdater.scala @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.typelevel.otel4s +package org.typelevel.otel4s.context.propagation import cats.Invariant diff --git a/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/ContextPropagatorsSuite.scala b/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/ContextPropagatorsSuite.scala new file mode 100644 index 000000000..d5728e080 --- /dev/null +++ b/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/ContextPropagatorsSuite.scala @@ -0,0 +1,57 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.typelevel.otel4s.context.propagation + +import munit._ + +class ContextPropagatorsSuite extends FunSuite { + + test("create a no-op instance") { + val propagators = ContextPropagators.noop + + assertEquals(propagators.textMapPropagator.fields, Nil) + assertEquals( + propagators.textMapPropagator.toString, + "TextMapPropagator.Noop" + ) + assertEquals(propagators.toString, "ContextPropagators.Noop") + } + + test("create (noop propagator) - use noop") { + val fields = List("a", "b", "c") + val propagator = new TestPropagator[String](fields, "TestPropagator") + val propagators = ContextPropagators.create(propagator) + + assertEquals(propagators.textMapPropagator.fields, fields) + assertEquals( + propagators.toString, + "ContextPropagators.Default{textMapPropagator=TestPropagator}" + ) + } + + private final class TestPropagator[Ctx]( + val fields: List[String], + name: String + ) extends TextMapPropagator[Ctx] { + def extract[A: TextMapGetter](ctx: Ctx, carrier: A): Ctx = ctx + + def inject[A: TextMapUpdater](ctx: Ctx, carrier: A): A = carrier + + override def toString: String = name + } + +} diff --git a/core/common/src/test/scala/org/typelevel/otel4s/TextMapGetterProps.scala b/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapGetterProps.scala similarity index 98% rename from core/common/src/test/scala/org/typelevel/otel4s/TextMapGetterProps.scala rename to core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapGetterProps.scala index 34e1733ab..0fbbf9b61 100644 --- a/core/common/src/test/scala/org/typelevel/otel4s/TextMapGetterProps.scala +++ b/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapGetterProps.scala @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.typelevel.otel4s +package org.typelevel.otel4s.context.propagation import lgbt.princess.platform.Platform import munit.ScalaCheckSuite diff --git a/core/common/src/test/scala/org/typelevel/otel4s/TextMapGetterSuite.scala b/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapGetterSuite.scala similarity index 97% rename from core/common/src/test/scala/org/typelevel/otel4s/TextMapGetterSuite.scala rename to core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapGetterSuite.scala index c1e92e6c5..72f64a59e 100644 --- a/core/common/src/test/scala/org/typelevel/otel4s/TextMapGetterSuite.scala +++ b/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapGetterSuite.scala @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.typelevel.otel4s +package org.typelevel.otel4s.context.propagation import cats.Eq import cats.laws.discipline._ @@ -23,6 +23,8 @@ import munit.DisciplineSuite import munit.FunSuite import org.scalacheck.Arbitrary import org.scalacheck.Gen +import org.typelevel.otel4s.Box +import org.typelevel.otel4s.NotQuiteExhaustiveChecks import scala.collection.immutable import scala.collection.mutable diff --git a/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapPropagatorSuite.scala b/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapPropagatorSuite.scala new file mode 100644 index 000000000..b25788677 --- /dev/null +++ b/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapPropagatorSuite.scala @@ -0,0 +1,69 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.typelevel.otel4s.context.propagation + +import munit._ + +class TextMapPropagatorSuite extends FunSuite { + + test("create a no-op instance") { + val propagator = TextMapPropagator.noop + + assertEquals(propagator.fields, Nil) + assertEquals(propagator.toString, "TextMapPropagator.Noop") + } + + test("composite (empty input) - use noop") { + val composite = TextMapPropagator.composite() + + assertEquals(composite.fields, Nil) + assertEquals(composite.toString, "TextMapPropagator.Noop") + } + + test("composite (single input) - use this input") { + val fields = List("a", "b", "c") + val propagator = new TestPropagator[String](fields, "TestPropagator") + val composite = TextMapPropagator.composite(propagator) + + assertEquals(composite.fields, fields) + assertEquals(composite.toString, "TestPropagator") + } + + test("composite (multiple) - create a multi instance") { + val fieldsA = List("a", "b") + val fieldsB = List("c", "d") + + val propagatorA = new TestPropagator[String](fieldsA, "PropagatorA") + val propagatorB = new TestPropagator[String](fieldsB, "PropagatorB") + val composite = TextMapPropagator.composite(propagatorA, propagatorB) + + assertEquals(composite.fields, fieldsA ++ fieldsB) + assertEquals( + composite.toString, + "TextMapPropagator.Multi(PropagatorA, PropagatorB)" + ) + } + + private final class TestPropagator[Ctx]( + val fields: List[String], + name: String + ) extends TextMapPropagator[Ctx] { + def extract[A: TextMapGetter](ctx: Ctx, carrier: A): Ctx = ctx + def inject[A: TextMapUpdater](ctx: Ctx, carrier: A): A = carrier + override def toString: String = name + } +} diff --git a/core/common/src/test/scala/org/typelevel/otel4s/TextMapSetterProps.scala b/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapSetterProps.scala similarity index 97% rename from core/common/src/test/scala/org/typelevel/otel4s/TextMapSetterProps.scala rename to core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapSetterProps.scala index c12e0ff35..df2cacf08 100644 --- a/core/common/src/test/scala/org/typelevel/otel4s/TextMapSetterProps.scala +++ b/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapSetterProps.scala @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.typelevel.otel4s +package org.typelevel.otel4s.context.propagation import lgbt.princess.platform.Platform import munit.ScalaCheckSuite diff --git a/core/common/src/test/scala/org/typelevel/otel4s/TextMapSetterSuite.scala b/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapSetterSuite.scala similarity index 97% rename from core/common/src/test/scala/org/typelevel/otel4s/TextMapSetterSuite.scala rename to core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapSetterSuite.scala index 7b162c1fd..349679361 100644 --- a/core/common/src/test/scala/org/typelevel/otel4s/TextMapSetterSuite.scala +++ b/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapSetterSuite.scala @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.typelevel.otel4s +package org.typelevel.otel4s.context.propagation import munit.FunSuite diff --git a/core/common/src/test/scala/org/typelevel/otel4s/TextMapUpdaterProps.scala b/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapUpdaterProps.scala similarity index 97% rename from core/common/src/test/scala/org/typelevel/otel4s/TextMapUpdaterProps.scala rename to core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapUpdaterProps.scala index 5f301592b..89521c22b 100644 --- a/core/common/src/test/scala/org/typelevel/otel4s/TextMapUpdaterProps.scala +++ b/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapUpdaterProps.scala @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.typelevel.otel4s +package org.typelevel.otel4s.context.propagation import munit.ScalaCheckSuite import org.scalacheck.Arbitrary diff --git a/core/common/src/test/scala/org/typelevel/otel4s/TextMapUpdaterSuite.scala b/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapUpdaterSuite.scala similarity index 96% rename from core/common/src/test/scala/org/typelevel/otel4s/TextMapUpdaterSuite.scala rename to core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapUpdaterSuite.scala index d0a2bb35f..4b07ceed4 100644 --- a/core/common/src/test/scala/org/typelevel/otel4s/TextMapUpdaterSuite.scala +++ b/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapUpdaterSuite.scala @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.typelevel.otel4s +package org.typelevel.otel4s.context.propagation import cats.Eq import cats.laws.discipline._ @@ -23,6 +23,8 @@ import munit.DisciplineSuite import munit.FunSuite import org.scalacheck.Arbitrary import org.scalacheck.Gen +import org.typelevel.otel4s.Box +import org.typelevel.otel4s.NotQuiteExhaustiveChecks import scala.collection.immutable diff --git a/core/trace/src/main/scala/org/typelevel/otel4s/trace/Tracer.scala b/core/trace/src/main/scala/org/typelevel/otel4s/trace/Tracer.scala index cf42bec8c..0dd3aa0c2 100644 --- a/core/trace/src/main/scala/org/typelevel/otel4s/trace/Tracer.scala +++ b/core/trace/src/main/scala/org/typelevel/otel4s/trace/Tracer.scala @@ -20,6 +20,8 @@ package trace import cats.Applicative import cats.effect.kernel.MonadCancelThrow import cats.~> +import org.typelevel.otel4s.context.propagation.TextMapGetter +import org.typelevel.otel4s.context.propagation.TextMapUpdater import org.typelevel.otel4s.meta.InstrumentMeta @annotation.implicitNotFound(""" @@ -182,7 +184,7 @@ trait Tracer[F[_]] extends TracerMacro[F] { * a copy of the immutable carrier with this tracer's context appended to * it * @see - * [[org.typelevel.otel4s.TextMapPropagator.injected TextMapPropagator#injected]] + * [[org.typelevel.otel4s.context.propagation.TextMapPropagator.inject TextMapPropagator#inject]] */ def propagate[C: TextMapUpdater](carrier: C): F[C] diff --git a/java/all/src/main/scala/org/typelevel/otel4s/java/OtelJava.scala b/java/all/src/main/scala/org/typelevel/otel4s/java/OtelJava.scala index 45218687a..0b12ed21c 100644 --- a/java/all/src/main/scala/org/typelevel/otel4s/java/OtelJava.scala +++ b/java/all/src/main/scala/org/typelevel/otel4s/java/OtelJava.scala @@ -25,8 +25,8 @@ import cats.syntax.all._ import io.opentelemetry.api.{OpenTelemetry => JOpenTelemetry} import io.opentelemetry.api.GlobalOpenTelemetry import io.opentelemetry.sdk.{OpenTelemetrySdk => JOpenTelemetrySdk} -import org.typelevel.otel4s.ContextPropagators import org.typelevel.otel4s.Otel4s +import org.typelevel.otel4s.context.propagation.ContextPropagators import org.typelevel.otel4s.java.Conversions.asyncFromCompletableResultCode import org.typelevel.otel4s.java.context.Context import org.typelevel.otel4s.java.context.LocalContext @@ -37,7 +37,7 @@ import org.typelevel.otel4s.metrics.MeterProvider import org.typelevel.otel4s.trace.TracerProvider sealed class OtelJava[F[_]] private ( - val propagators: ContextPropagators[F, Context], + val propagators: ContextPropagators[Context], val meterProvider: MeterProvider[F], val tracerProvider: TracerProvider[F], ) extends Otel4s[F] { @@ -66,7 +66,7 @@ object OtelJava { def local[F[_]: Async: LocalContext]( jOtel: JOpenTelemetry ): OtelJava[F] = { - val contextPropagators = new ContextPropagatorsImpl[F](jOtel.getPropagators) + val contextPropagators = new ContextPropagatorsImpl(jOtel.getPropagators) val metrics = Metrics.forAsync(jOtel) val traces = Traces.local(jOtel, contextPropagators) diff --git a/java/common/src/main/scala/org/typelevel/otel4s/java/ContextPropagatorsImpl.scala b/java/common/src/main/scala/org/typelevel/otel4s/java/ContextPropagatorsImpl.scala index 196ae90da..3e554da0d 100644 --- a/java/common/src/main/scala/org/typelevel/otel4s/java/ContextPropagatorsImpl.scala +++ b/java/common/src/main/scala/org/typelevel/otel4s/java/ContextPropagatorsImpl.scala @@ -17,15 +17,16 @@ package org.typelevel.otel4s package java -import cats.effect.kernel.Sync import io.opentelemetry.context.propagation.{ ContextPropagators => JContextPropagators } +import org.typelevel.otel4s.context.propagation.ContextPropagators +import org.typelevel.otel4s.context.propagation.TextMapPropagator import org.typelevel.otel4s.java.context.Context -private[java] class ContextPropagatorsImpl[F[_]: Sync]( +private[java] class ContextPropagatorsImpl( propagators: JContextPropagators -) extends ContextPropagators[F, Context] { - val textMapPropagator: TextMapPropagator[F, Context] = +) extends ContextPropagators[Context] { + val textMapPropagator: TextMapPropagator[Context] = new TextMapPropagatorImpl(propagators.getTextMapPropagator) } diff --git a/java/common/src/main/scala/org/typelevel/otel4s/java/Conversions.scala b/java/common/src/main/scala/org/typelevel/otel4s/java/Conversions.scala index 43ac18fcf..0d9c12e79 100644 --- a/java/common/src/main/scala/org/typelevel/otel4s/java/Conversions.scala +++ b/java/common/src/main/scala/org/typelevel/otel4s/java/Conversions.scala @@ -24,6 +24,8 @@ import io.opentelemetry.api.common.{Attributes => JAttributes} import io.opentelemetry.context.propagation.{TextMapGetter => JTextMapGetter} import io.opentelemetry.context.propagation.{TextMapSetter => JTextMapSetter} import io.opentelemetry.sdk.common.CompletableResultCode +import org.typelevel.otel4s.context.propagation.TextMapGetter +import org.typelevel.otel4s.context.propagation.TextMapSetter import scala.jdk.CollectionConverters._ diff --git a/java/common/src/main/scala/org/typelevel/otel4s/java/TextMapPropagatorImpl.scala b/java/common/src/main/scala/org/typelevel/otel4s/java/TextMapPropagatorImpl.scala index 6f54c8996..8b1656dfa 100644 --- a/java/common/src/main/scala/org/typelevel/otel4s/java/TextMapPropagatorImpl.scala +++ b/java/common/src/main/scala/org/typelevel/otel4s/java/TextMapPropagatorImpl.scala @@ -17,33 +17,33 @@ package org.typelevel.otel4s package java -import cats.effect.Sync import io.opentelemetry.context.propagation.{ TextMapPropagator => JTextMapPropagator } +import org.typelevel.otel4s.context.propagation.TextMapGetter +import org.typelevel.otel4s.context.propagation.TextMapPropagator +import org.typelevel.otel4s.context.propagation.TextMapUpdater import org.typelevel.otel4s.java.Conversions._ import org.typelevel.otel4s.java.context.Context -private[java] class TextMapPropagatorImpl[F[_]: Sync]( +import scala.jdk.CollectionConverters._ + +private[java] class TextMapPropagatorImpl( jPropagator: JTextMapPropagator -) extends TextMapPropagator[F, Context] { +) extends TextMapPropagator[Context] { + lazy val fields: List[String] = + jPropagator.fields().asScala.toList + def extract[A: TextMapGetter](ctx: Context, carrier: A): Context = ctx.map(jPropagator.extract(_, carrier, fromTextMapGetter)) - def inject[A: TextMapSetter](ctx: Context, carrier: A): F[Unit] = - Sync[F].delay( - jPropagator.inject(ctx.underlying, carrier, fromTextMapSetter) - ) - - def injected[A](ctx: Context, carrier: A)(implicit - injector: TextMapUpdater[A] - ): A = { + def inject[A: TextMapUpdater](ctx: Context, carrier: A): A = { var injectedCarrier = carrier jPropagator.inject[Null]( ctx.underlying, null, // explicitly allowed per opentelemetry-java, so our setter can be a lambda! (_, key, value) => { - injectedCarrier = injector.updated(injectedCarrier, key, value) + injectedCarrier = TextMapUpdater[A].updated(injectedCarrier, key, value) } ) injectedCarrier diff --git a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerBuilderImpl.scala b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerBuilderImpl.scala index bfd2b4f56..26efc7c75 100644 --- a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerBuilderImpl.scala +++ b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerBuilderImpl.scala @@ -18,14 +18,14 @@ package org.typelevel.otel4s.java.trace import cats.effect.Sync import io.opentelemetry.api.trace.{TracerProvider => JTracerProvider} -import org.typelevel.otel4s.ContextPropagators +import org.typelevel.otel4s.context.propagation.ContextPropagators import org.typelevel.otel4s.java.context.Context import org.typelevel.otel4s.java.context.LocalContext import org.typelevel.otel4s.trace._ private[java] final case class TracerBuilderImpl[F[_]: Sync: LocalContext]( jTracerProvider: JTracerProvider, - propagators: ContextPropagators[F, Context], + propagators: ContextPropagators[Context], name: String, version: Option[String] = None, schemaUrl: Option[String] = None diff --git a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerImpl.scala b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerImpl.scala index f737c1e8d..c50f092bb 100644 --- a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerImpl.scala +++ b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerImpl.scala @@ -19,9 +19,9 @@ package org.typelevel.otel4s.java.trace import cats.effect.Sync import io.opentelemetry.api.trace.{Span => JSpan} import io.opentelemetry.api.trace.{Tracer => JTracer} -import org.typelevel.otel4s.ContextPropagators -import org.typelevel.otel4s.TextMapGetter -import org.typelevel.otel4s.TextMapUpdater +import org.typelevel.otel4s.context.propagation.ContextPropagators +import org.typelevel.otel4s.context.propagation.TextMapGetter +import org.typelevel.otel4s.context.propagation.TextMapUpdater import org.typelevel.otel4s.java.context.Context import org.typelevel.otel4s.java.context.LocalContext import org.typelevel.otel4s.trace.SpanBuilder @@ -30,7 +30,7 @@ import org.typelevel.otel4s.trace.Tracer private[java] class TracerImpl[F[_]: Sync]( jTracer: JTracer, - propagators: ContextPropagators[F, Context] + propagators: ContextPropagators[Context] )(implicit L: LocalContext[F]) extends Tracer[F] { @@ -70,5 +70,5 @@ private[java] class TracerImpl[F[_]: Sync]( } def propagate[C: TextMapUpdater](carrier: C): F[C] = - L.reader(propagators.textMapPropagator.injected(_, carrier)) + L.reader(propagators.textMapPropagator.inject(_, carrier)) } diff --git a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerProviderImpl.scala b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerProviderImpl.scala index 25d4d7f7e..841d55c63 100644 --- a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerProviderImpl.scala +++ b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/TracerProviderImpl.scala @@ -18,7 +18,7 @@ package org.typelevel.otel4s.java.trace import cats.effect.Sync import io.opentelemetry.api.trace.{TracerProvider => JTracerProvider} -import org.typelevel.otel4s.ContextPropagators +import org.typelevel.otel4s.context.propagation.ContextPropagators import org.typelevel.otel4s.java.context.Context import org.typelevel.otel4s.java.context.LocalContext import org.typelevel.otel4s.trace.TracerBuilder @@ -26,7 +26,7 @@ import org.typelevel.otel4s.trace.TracerProvider private[java] class TracerProviderImpl[F[_]: Sync: LocalContext] private ( jTracerProvider: JTracerProvider, - propagators: ContextPropagators[F, Context] + propagators: ContextPropagators[Context] ) extends TracerProvider[F] { def tracer(name: String): TracerBuilder[F] = TracerBuilderImpl(jTracerProvider, propagators, name) @@ -36,7 +36,7 @@ private[java] object TracerProviderImpl { def local[F[_]: Sync: LocalContext]( jTracerProvider: JTracerProvider, - propagators: ContextPropagators[F, Context] + propagators: ContextPropagators[Context] ): TracerProvider[F] = new TracerProviderImpl(jTracerProvider, propagators) } diff --git a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/Traces.scala b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/Traces.scala index 0c0155561..f6e676d29 100644 --- a/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/Traces.scala +++ b/java/trace/src/main/scala/org/typelevel/otel4s/java/trace/Traces.scala @@ -21,6 +21,7 @@ import cats.effect.IOLocal import cats.effect.LiftIO import cats.effect.Sync import io.opentelemetry.api.{OpenTelemetry => JOpenTelemetry} +import org.typelevel.otel4s.context.propagation.ContextPropagators import org.typelevel.otel4s.java.context.Context import org.typelevel.otel4s.java.context.LocalContext import org.typelevel.otel4s.java.instances._ @@ -34,7 +35,7 @@ object Traces { def local[F[_]: Sync: LocalContext]( jOtel: JOpenTelemetry, - propagators: ContextPropagators[F, Context] + propagators: ContextPropagators[Context] ): Traces[F] = { val provider = TracerProviderImpl.local(jOtel.getTracerProvider, propagators) @@ -45,7 +46,7 @@ object Traces { def ioLocal[F[_]: LiftIO: Sync]( jOtel: JOpenTelemetry, - propagators: ContextPropagators[F, Context] + propagators: ContextPropagators[Context] ): F[Traces[F]] = IOLocal(Context.root) .map { implicit ioLocal: IOLocal[Context] => diff --git a/java/trace/src/test/scala/org/typelevel/otel4s/java/trace/TracerSuite.scala b/java/trace/src/test/scala/org/typelevel/otel4s/java/trace/TracerSuite.scala index b08122320..683857969 100644 --- a/java/trace/src/test/scala/org/typelevel/otel4s/java/trace/TracerSuite.scala +++ b/java/trace/src/test/scala/org/typelevel/otel4s/java/trace/TracerSuite.scala @@ -1123,7 +1123,7 @@ class TracerSuite extends CatsEffectSuite { val textMapPropagators = W3CTraceContextPropagator.getInstance() +: additionalPropagators - val propagators = new ContextPropagatorsImpl[IO]( + val propagators = new ContextPropagatorsImpl( JContextPropagators.create( JTextMapPropagator.composite(textMapPropagators.asJava) ) From 5691fa342f659cff1a6d4de855a41ad85db21b5b Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Thu, 5 Oct 2023 19:19:29 +0300 Subject: [PATCH 2/8] Make `ContextPropagators` sealed --- .../propagation/ContextPropagators.scala | 2 +- .../org/typelevel/otel4s/java/OtelJava.scala | 4 ++- .../otel4s/java/ContextPropagatorsImpl.scala | 32 ------------------- .../otel4s/java/trace/TracerSuite.scala | 10 +++--- 4 files changed, 8 insertions(+), 40 deletions(-) delete mode 100644 java/common/src/main/scala/org/typelevel/otel4s/java/ContextPropagatorsImpl.scala diff --git a/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/ContextPropagators.scala b/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/ContextPropagators.scala index 3caa320ca..841136dcd 100644 --- a/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/ContextPropagators.scala +++ b/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/ContextPropagators.scala @@ -21,7 +21,7 @@ package org.typelevel.otel4s.context.propagation * @tparam Ctx * the type of the Context */ -trait ContextPropagators[Ctx] { +sealed trait ContextPropagators[Ctx] { /** Returns a text map propagator to extract or inject data. */ diff --git a/java/all/src/main/scala/org/typelevel/otel4s/java/OtelJava.scala b/java/all/src/main/scala/org/typelevel/otel4s/java/OtelJava.scala index 0b12ed21c..ca985c8f1 100644 --- a/java/all/src/main/scala/org/typelevel/otel4s/java/OtelJava.scala +++ b/java/all/src/main/scala/org/typelevel/otel4s/java/OtelJava.scala @@ -66,7 +66,9 @@ object OtelJava { def local[F[_]: Async: LocalContext]( jOtel: JOpenTelemetry ): OtelJava[F] = { - val contextPropagators = new ContextPropagatorsImpl(jOtel.getPropagators) + val contextPropagators = ContextPropagators.create( + new TextMapPropagatorImpl(jOtel.getPropagators.getTextMapPropagator) + ) val metrics = Metrics.forAsync(jOtel) val traces = Traces.local(jOtel, contextPropagators) diff --git a/java/common/src/main/scala/org/typelevel/otel4s/java/ContextPropagatorsImpl.scala b/java/common/src/main/scala/org/typelevel/otel4s/java/ContextPropagatorsImpl.scala deleted file mode 100644 index 3e554da0d..000000000 --- a/java/common/src/main/scala/org/typelevel/otel4s/java/ContextPropagatorsImpl.scala +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2022 Typelevel - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.typelevel.otel4s -package java - -import io.opentelemetry.context.propagation.{ - ContextPropagators => JContextPropagators -} -import org.typelevel.otel4s.context.propagation.ContextPropagators -import org.typelevel.otel4s.context.propagation.TextMapPropagator -import org.typelevel.otel4s.java.context.Context - -private[java] class ContextPropagatorsImpl( - propagators: JContextPropagators -) extends ContextPropagators[Context] { - val textMapPropagator: TextMapPropagator[Context] = - new TextMapPropagatorImpl(propagators.getTextMapPropagator) -} diff --git a/java/trace/src/test/scala/org/typelevel/otel4s/java/trace/TracerSuite.scala b/java/trace/src/test/scala/org/typelevel/otel4s/java/trace/TracerSuite.scala index 683857969..246a24ebc 100644 --- a/java/trace/src/test/scala/org/typelevel/otel4s/java/trace/TracerSuite.scala +++ b/java/trace/src/test/scala/org/typelevel/otel4s/java/trace/TracerSuite.scala @@ -32,9 +32,6 @@ import io.opentelemetry.api.common.{AttributeKey => JAttributeKey} import io.opentelemetry.api.common.Attributes import io.opentelemetry.api.trace.StatusCode import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator -import io.opentelemetry.context.propagation.{ - ContextPropagators => JContextPropagators -} import io.opentelemetry.context.propagation.{ TextMapPropagator => JTextMapPropagator } @@ -51,7 +48,8 @@ import io.opentelemetry.sdk.trace.`export`.SimpleSpanProcessor import io.opentelemetry.sdk.trace.internal.data.ExceptionEventData import munit.CatsEffectSuite import org.typelevel.otel4s.Attribute -import org.typelevel.otel4s.java.ContextPropagatorsImpl +import org.typelevel.otel4s.context.propagation.ContextPropagators +import org.typelevel.otel4s.java.TextMapPropagatorImpl import org.typelevel.otel4s.java.context.Context import org.typelevel.otel4s.java.instances._ import org.typelevel.otel4s.trace.Span @@ -1123,8 +1121,8 @@ class TracerSuite extends CatsEffectSuite { val textMapPropagators = W3CTraceContextPropagator.getInstance() +: additionalPropagators - val propagators = new ContextPropagatorsImpl( - JContextPropagators.create( + val propagators = ContextPropagators.create( + new TextMapPropagatorImpl( JTextMapPropagator.composite(textMapPropagators.asJava) ) ) From b767cf6bb9922914d9a1fdcf14d756d0fdf01b2c Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Fri, 6 Oct 2023 09:44:12 +0300 Subject: [PATCH 3/8] Rename `create` -> `of`, add `Monoid[TextMapPropagator[Ctx]]` --- .../propagation/ContextPropagators.scala | 27 +++++---- .../propagation/TextMapPropagator.scala | 58 ++++++++++++++----- .../propagation/ContextPropagatorsSuite.scala | 20 ++++++- .../TextMapPropagatorLawTests.scala | 52 +++++++++++++++++ .../propagation/TextMapPropagatorSuite.scala | 36 +++++++++--- .../org/typelevel/otel4s/java/OtelJava.scala | 2 +- .../otel4s/java/trace/TracerSuite.scala | 2 +- 7 files changed, 159 insertions(+), 38 deletions(-) create mode 100644 core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapPropagatorLawTests.scala diff --git a/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/ContextPropagators.scala b/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/ContextPropagators.scala index 841136dcd..f23db2635 100644 --- a/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/ContextPropagators.scala +++ b/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/ContextPropagators.scala @@ -33,27 +33,34 @@ object ContextPropagators { /** Creates a [[ContextPropagators]] which can be used to extract and inject * context in text payloads with the given [[TextMapPropagator]]. * - * '''Hint''': use [[TextMapPropagator.composite]] to combine multiple text - * map propagators. + * If multiple text map propagators are passed, the combined (composite) + * TextMapPropagator instance will be created. + * + * It's a shortcut for: + * {{{ + * ContextPropagators.of(TextMapPropagator.of(w3cPropagator, httpTracePropagator)) + * }}} * * @example * {{{ * val w3cPropagator: TextMapPropagator[Context] = ??? * val httpTracePropagator: TextMapPropagator[Context] = ??? - * val textMapPropagator = TextMapPropagator.composite(w3cPropagator, httpTracePropagator) - * val contextPropagators = ContextPropagators.create(textMapPropagator) + * val contextPropagators = ContextPropagators.of(w3cPropagator, httpTracePropagator) * }}} * - * @param textMapPropagator - * the text map propagator to extract or inject data + * @see + * [[TextMapPropagator.of]] + * + * @param textMapPropagators + * the propagators to use for injection and extraction * * @tparam Ctx - * the context to use to extra or inject data + * the context to use to extract or inject data */ - def create[Ctx]( - textMapPropagator: TextMapPropagator[Ctx] + def of[Ctx]( + textMapPropagators: TextMapPropagator[Ctx]* ): ContextPropagators[Ctx] = - new Default(textMapPropagator) + new Default(TextMapPropagator.of(textMapPropagators: _*)) /** Creates a no-op implementation of the [[ContextPropagators]]. * diff --git a/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/TextMapPropagator.scala b/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/TextMapPropagator.scala index 490c3a378..ffc13b18f 100644 --- a/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/TextMapPropagator.scala +++ b/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/TextMapPropagator.scala @@ -16,7 +16,7 @@ package org.typelevel.otel4s.context.propagation -import cats.data.NonEmptyList +import cats.Monoid import cats.syntax.foldable._ /** The process of propagating data across process boundaries involves injecting @@ -32,7 +32,7 @@ import cats.syntax.foldable._ * while on the server side, values are extracted from them. * * @tparam Ctx - * the context to use to extra or inject data + * the context to use to extract or inject data */ trait TextMapPropagator[Ctx] { @@ -86,19 +86,19 @@ object TextMapPropagator { * {{{ * val w3cPropagator: TextMapPropagator[Context] = ??? * val httpTracePropagator: TextMapPropagator[Context] = ??? - * val textMapPropagator = TextMapPropagator.composite(w3cPropagator, httpTracePropagator) + * val textMapPropagator = TextMapPropagator.of(w3cPropagator, httpTracePropagator) * }}} + * + * @param propagators + * the propagators to use for injection and extraction + * * @tparam Ctx - * the context to use to extra or inject data + * the context to use to extract or inject data */ - def composite[Ctx]( + def of[Ctx]( propagators: TextMapPropagator[Ctx]* ): TextMapPropagator[Ctx] = - propagators.toList match { - case Nil => new Noop[Ctx] - case head :: Nil => head - case head :: tail => new Multi(NonEmptyList(head, tail)) - } + propagators.combineAll /** Creates a no-op implementation of the [[TextMapPropagator]]. * @@ -107,6 +107,34 @@ object TextMapPropagator { def noop[Ctx]: TextMapPropagator[Ctx] = new Noop + implicit def textMapPropagatorMonoid[Ctx]: Monoid[TextMapPropagator[Ctx]] = + new Monoid[TextMapPropagator[Ctx]] { + val empty: TextMapPropagator[Ctx] = + noop[Ctx] + + def combine( + x: TextMapPropagator[Ctx], + y: TextMapPropagator[Ctx] + ): TextMapPropagator[Ctx] = + (x, y) match { + case (that, _: Noop[Ctx]) => + that + case (_: Noop[Ctx], other) => + other + case (that: Multi[Ctx], other: Multi[Ctx]) => + multi(that.propagators ++ other.propagators) + case (that: Multi[Ctx], other) => + multi(that.propagators :+ other) + case (that, other: Multi[Ctx]) => + multi(that +: other.propagators) + case (that, other) => + multi(List(that, other)) + } + + private def multi(propagators: List[TextMapPropagator[Ctx]]): Multi[Ctx] = + Multi(propagators, propagators.flatMap(_.fields)) + } + private final class Noop[Ctx] extends TextMapPropagator[Ctx] { def fields: List[String] = Nil @@ -120,12 +148,10 @@ object TextMapPropagator { override def toString: String = "TextMapPropagator.Noop" } - private final class Multi[Ctx]( - val propagators: NonEmptyList[TextMapPropagator[Ctx]] + private final case class Multi[Ctx]( + propagators: List[TextMapPropagator[Ctx]], + fields: List[String] ) extends TextMapPropagator[Ctx] { - val fields: List[String] = - propagators.toList.flatMap(_.fields) - def extract[A: TextMapGetter](ctx: Ctx, carrier: A): Ctx = propagators.foldLeft(ctx) { (ctx, propagator) => propagator.extract(ctx, carrier) @@ -137,7 +163,7 @@ object TextMapPropagator { } override def toString: String = - s"TextMapPropagator.Multi(${propagators.map(_.toString).mkString_(", ")})" + s"TextMapPropagator.Multi(${propagators.map(_.toString).mkString(", ")})" } } diff --git a/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/ContextPropagatorsSuite.scala b/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/ContextPropagatorsSuite.scala index d5728e080..32fc7e6c7 100644 --- a/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/ContextPropagatorsSuite.scala +++ b/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/ContextPropagatorsSuite.scala @@ -31,10 +31,10 @@ class ContextPropagatorsSuite extends FunSuite { assertEquals(propagators.toString, "ContextPropagators.Noop") } - test("create (noop propagator) - use noop") { + test("of (single input) - use this input") { val fields = List("a", "b", "c") val propagator = new TestPropagator[String](fields, "TestPropagator") - val propagators = ContextPropagators.create(propagator) + val propagators = ContextPropagators.of(propagator) assertEquals(propagators.textMapPropagator.fields, fields) assertEquals( @@ -43,6 +43,22 @@ class ContextPropagatorsSuite extends FunSuite { ) } + test("of (multiple inputs) - create a multi text map propagator instance") { + val fieldsA = List("a", "b") + val fieldsB = List("c", "d") + + val propagatorA = new TestPropagator[String](fieldsA, "TestPropagatorA") + val propagatorB = new TestPropagator[String](fieldsB, "TestPropagatorB") + + val propagators = ContextPropagators.of(propagatorA, propagatorB) + + assertEquals(propagators.textMapPropagator.fields, fieldsA ++ fieldsB) + assertEquals( + propagators.toString, + "ContextPropagators.Default{textMapPropagator=TextMapPropagator.Multi(TestPropagatorA, TestPropagatorB)}" + ) + } + private final class TestPropagator[Ctx]( val fields: List[String], name: String diff --git a/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapPropagatorLawTests.scala b/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapPropagatorLawTests.scala new file mode 100644 index 000000000..a927bf61f --- /dev/null +++ b/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapPropagatorLawTests.scala @@ -0,0 +1,52 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.typelevel.otel4s.context.propagation + +import cats.Eq +import cats.kernel.laws.discipline.MonoidTests +import munit._ +import org.scalacheck.{Arbitrary, Gen} + +class TextMapPropagatorLawTests extends DisciplineSuite { + + implicit val textMapPropagatorEq: Eq[TextMapPropagator[String]] = + Eq.fromUniversalEquals + + implicit val textMapPropagatorArb: Arbitrary[TextMapPropagator[String]] = + Arbitrary( + for { + fields <- Gen.listOf(Gen.alphaNumStr) + name <- Gen.alphaNumStr + } yield TestPropagator(fields, name) + ) + + checkAll( + "TextMapPropagator.MonoidLaws", + MonoidTests[TextMapPropagator[String]].monoid + ) + + private case class TestPropagator[Ctx]( + fields: List[String], + name: String + ) extends TextMapPropagator[Ctx] { + def extract[A: TextMapGetter](ctx: Ctx, carrier: A): Ctx = ctx + + def inject[A: TextMapUpdater](ctx: Ctx, carrier: A): A = carrier + + override def toString: String = name + } +} diff --git a/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapPropagatorSuite.scala b/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapPropagatorSuite.scala index b25788677..567245664 100644 --- a/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapPropagatorSuite.scala +++ b/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapPropagatorSuite.scala @@ -27,37 +27,57 @@ class TextMapPropagatorSuite extends FunSuite { assertEquals(propagator.toString, "TextMapPropagator.Noop") } - test("composite (empty input) - use noop") { - val composite = TextMapPropagator.composite() + test("of (empty input) - use noop") { + val composite = TextMapPropagator.of() assertEquals(composite.fields, Nil) assertEquals(composite.toString, "TextMapPropagator.Noop") } - test("composite (single input) - use this input") { + test("of (single input) - use this input") { val fields = List("a", "b", "c") val propagator = new TestPropagator[String](fields, "TestPropagator") - val composite = TextMapPropagator.composite(propagator) + val composite = TextMapPropagator.of(propagator) assertEquals(composite.fields, fields) assertEquals(composite.toString, "TestPropagator") } - test("composite (multiple) - create a multi instance") { + test("of (multiple) - create a multi instance") { val fieldsA = List("a", "b") val fieldsB = List("c", "d") val propagatorA = new TestPropagator[String](fieldsA, "PropagatorA") val propagatorB = new TestPropagator[String](fieldsB, "PropagatorB") - val composite = TextMapPropagator.composite(propagatorA, propagatorB) - assertEquals(composite.fields, fieldsA ++ fieldsB) + val propagator = TextMapPropagator.of(propagatorA, propagatorB) + + assertEquals(propagator.fields, fieldsA ++ fieldsB) assertEquals( - composite.toString, + propagator.toString, "TextMapPropagator.Multi(PropagatorA, PropagatorB)" ) } + test("of (multiple) - flatten out nested multi instances") { + val fieldsA = List("a", "b") + val fieldsB = List("c", "d") + + val propagatorA = new TestPropagator[String](fieldsA, "PropagatorA") + val propagatorB = new TestPropagator[String](fieldsB, "PropagatorB") + + val multi1 = TextMapPropagator.of(propagatorA, propagatorB) + val multi2 = TextMapPropagator.of(propagatorA, propagatorB) + + val propagator = TextMapPropagator.of(multi1, multi2) + + assertEquals(propagator.fields, fieldsA ++ fieldsB ++ fieldsA ++ fieldsB) + assertEquals( + propagator.toString, + "TextMapPropagator.Multi(PropagatorA, PropagatorB, PropagatorA, PropagatorB)" + ) + } + private final class TestPropagator[Ctx]( val fields: List[String], name: String diff --git a/java/all/src/main/scala/org/typelevel/otel4s/java/OtelJava.scala b/java/all/src/main/scala/org/typelevel/otel4s/java/OtelJava.scala index ca985c8f1..baa63ec82 100644 --- a/java/all/src/main/scala/org/typelevel/otel4s/java/OtelJava.scala +++ b/java/all/src/main/scala/org/typelevel/otel4s/java/OtelJava.scala @@ -66,7 +66,7 @@ object OtelJava { def local[F[_]: Async: LocalContext]( jOtel: JOpenTelemetry ): OtelJava[F] = { - val contextPropagators = ContextPropagators.create( + val contextPropagators = ContextPropagators.of( new TextMapPropagatorImpl(jOtel.getPropagators.getTextMapPropagator) ) diff --git a/java/trace/src/test/scala/org/typelevel/otel4s/java/trace/TracerSuite.scala b/java/trace/src/test/scala/org/typelevel/otel4s/java/trace/TracerSuite.scala index 246a24ebc..c5646bc7a 100644 --- a/java/trace/src/test/scala/org/typelevel/otel4s/java/trace/TracerSuite.scala +++ b/java/trace/src/test/scala/org/typelevel/otel4s/java/trace/TracerSuite.scala @@ -1121,7 +1121,7 @@ class TracerSuite extends CatsEffectSuite { val textMapPropagators = W3CTraceContextPropagator.getInstance() +: additionalPropagators - val propagators = ContextPropagators.create( + val propagators = ContextPropagators.of( new TextMapPropagatorImpl( JTextMapPropagator.composite(textMapPropagators.asJava) ) From 367062f3983fbf6cebc828c17fe7dc9bdc961fb7 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Fri, 6 Oct 2023 09:45:28 +0300 Subject: [PATCH 4/8] TextMapPropagator - `fields: List[String]` -> `fields: Iterable[String]` --- .../otel4s/context/propagation/TextMapPropagator.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/TextMapPropagator.scala b/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/TextMapPropagator.scala index ffc13b18f..f024c388e 100644 --- a/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/TextMapPropagator.scala +++ b/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/TextMapPropagator.scala @@ -36,8 +36,8 @@ import cats.syntax.foldable._ */ trait TextMapPropagator[Ctx] { - /** The list of propagation fields. */ - def fields: List[String] + /** The collection of propagation fields. */ + def fields: Iterable[String] /** Extracts key-value pairs from the given `carrier` and adds them to the * given context. @@ -136,7 +136,7 @@ object TextMapPropagator { } private final class Noop[Ctx] extends TextMapPropagator[Ctx] { - def fields: List[String] = + def fields: Iterable[String] = Nil def extract[A: TextMapGetter](ctx: Ctx, carrier: A): Ctx = From 2fbfecc90962c4f4f33ea6847c7c6d2d6c4ee11b Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Fri, 6 Oct 2023 09:46:28 +0300 Subject: [PATCH 5/8] Run `scalafixAll` --- .../otel4s/context/propagation/TextMapPropagatorLawTests.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapPropagatorLawTests.scala b/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapPropagatorLawTests.scala index a927bf61f..53bd3877c 100644 --- a/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapPropagatorLawTests.scala +++ b/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapPropagatorLawTests.scala @@ -19,7 +19,8 @@ package org.typelevel.otel4s.context.propagation import cats.Eq import cats.kernel.laws.discipline.MonoidTests import munit._ -import org.scalacheck.{Arbitrary, Gen} +import org.scalacheck.Arbitrary +import org.scalacheck.Gen class TextMapPropagatorLawTests extends DisciplineSuite { From 63ef5df6553db71c93dfe1af1708bfe71a2fb41d Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Tue, 17 Oct 2023 14:07:20 +0300 Subject: [PATCH 6/8] Update java/common/src/main/scala/org/typelevel/otel4s/java/TextMapPropagatorImpl.scala Co-authored-by: Marissa | April <7505383+NthPortal@users.noreply.github.com> --- .../org/typelevel/otel4s/java/TextMapPropagatorImpl.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/common/src/main/scala/org/typelevel/otel4s/java/TextMapPropagatorImpl.scala b/java/common/src/main/scala/org/typelevel/otel4s/java/TextMapPropagatorImpl.scala index 8b1656dfa..e87805fed 100644 --- a/java/common/src/main/scala/org/typelevel/otel4s/java/TextMapPropagatorImpl.scala +++ b/java/common/src/main/scala/org/typelevel/otel4s/java/TextMapPropagatorImpl.scala @@ -31,8 +31,8 @@ import scala.jdk.CollectionConverters._ private[java] class TextMapPropagatorImpl( jPropagator: JTextMapPropagator ) extends TextMapPropagator[Context] { - lazy val fields: List[String] = - jPropagator.fields().asScala.toList + lazy val fields: Iterable[String] = + jPropagator.fields().asScala def extract[A: TextMapGetter](ctx: Context, carrier: A): Context = ctx.map(jPropagator.extract(_, carrier, fromTextMapGetter)) From 03ef5907c0cb3dc9307906f016b1e6a1db1f0a7c Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Tue, 17 Oct 2023 14:15:38 +0300 Subject: [PATCH 7/8] TextMapPropagatorImpl: access implicit `TextMapUpdater` directly --- .../org/typelevel/otel4s/java/TextMapPropagatorImpl.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/java/common/src/main/scala/org/typelevel/otel4s/java/TextMapPropagatorImpl.scala b/java/common/src/main/scala/org/typelevel/otel4s/java/TextMapPropagatorImpl.scala index e87805fed..19d20bda5 100644 --- a/java/common/src/main/scala/org/typelevel/otel4s/java/TextMapPropagatorImpl.scala +++ b/java/common/src/main/scala/org/typelevel/otel4s/java/TextMapPropagatorImpl.scala @@ -37,13 +37,15 @@ private[java] class TextMapPropagatorImpl( def extract[A: TextMapGetter](ctx: Context, carrier: A): Context = ctx.map(jPropagator.extract(_, carrier, fromTextMapGetter)) - def inject[A: TextMapUpdater](ctx: Context, carrier: A): A = { + def inject[A](ctx: Context, carrier: A)(implicit + injector: TextMapUpdater[A] + ): A = { var injectedCarrier = carrier jPropagator.inject[Null]( ctx.underlying, null, // explicitly allowed per opentelemetry-java, so our setter can be a lambda! (_, key, value) => { - injectedCarrier = TextMapUpdater[A].updated(injectedCarrier, key, value) + injectedCarrier = injector.updated(injectedCarrier, key, value) } ) injectedCarrier From d747bd6f6dcd220022187c028844ec0400977c26 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Tue, 17 Oct 2023 14:24:35 +0300 Subject: [PATCH 8/8] TextMapPropagator: keep unique fields when making a multi instance --- .../propagation/TextMapPropagator.scala | 2 +- .../propagation/TextMapPropagatorSuite.scala | 21 ++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/TextMapPropagator.scala b/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/TextMapPropagator.scala index f024c388e..7952de835 100644 --- a/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/TextMapPropagator.scala +++ b/core/common/src/main/scala/org/typelevel/otel4s/context/propagation/TextMapPropagator.scala @@ -132,7 +132,7 @@ object TextMapPropagator { } private def multi(propagators: List[TextMapPropagator[Ctx]]): Multi[Ctx] = - Multi(propagators, propagators.flatMap(_.fields)) + Multi(propagators, propagators.flatMap(_.fields).distinct) } private final class Noop[Ctx] extends TextMapPropagator[Ctx] { diff --git a/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapPropagatorSuite.scala b/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapPropagatorSuite.scala index 567245664..7bdce9367 100644 --- a/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapPropagatorSuite.scala +++ b/core/common/src/test/scala/org/typelevel/otel4s/context/propagation/TextMapPropagatorSuite.scala @@ -71,7 +71,26 @@ class TextMapPropagatorSuite extends FunSuite { val propagator = TextMapPropagator.of(multi1, multi2) - assertEquals(propagator.fields, fieldsA ++ fieldsB ++ fieldsA ++ fieldsB) + assertEquals(propagator.fields, fieldsA ++ fieldsB) + assertEquals( + propagator.toString, + "TextMapPropagator.Multi(PropagatorA, PropagatorB, PropagatorA, PropagatorB)" + ) + } + + test("of (multiple) - keep unique fields") { + val fieldsA = List("a", "b", "c", "d") + val fieldsB = List("c", "d", "e", "f") + + val propagatorA = new TestPropagator[String](fieldsA, "PropagatorA") + val propagatorB = new TestPropagator[String](fieldsB, "PropagatorB") + + val multi1 = TextMapPropagator.of(propagatorA, propagatorB) + val multi2 = TextMapPropagator.of(propagatorA, propagatorB) + + val propagator = TextMapPropagator.of(multi1, multi2) + + assertEquals(propagator.fields, List("a", "b", "c", "d", "e", "f")) assertEquals( propagator.toString, "TextMapPropagator.Multi(PropagatorA, PropagatorB, PropagatorA, PropagatorB)"