Skip to content

Commit

Permalink
Merge pull request #365 from NthPortal/scala-propagator-support/PR
Browse files Browse the repository at this point in the history
Support using Scala propagators with Java backend
  • Loading branch information
NthPortal authored Dec 20, 2023
2 parents 4c8e7c4 + c1565ad commit db4381d
Show file tree
Hide file tree
Showing 25 changed files with 1,249 additions and 297 deletions.
15 changes: 15 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
otel4s
Copyright 2022-2023 Typelevel
Licensed under Apache License 2.0 (see LICENSE)

This software contains portions of code derived from scala
https://github.com/scala/scala
Scala
Copyright (c) 2002-2023 EPFL
Copyright (c) 2011-2023 Lightbend, Inc.

Scala includes software developed at
LAMP/EPFL (https://lamp.epfl.ch/) and
Lightbend, Inc. (https://www.lightbend.com/).

Licensed under the Apache License, Version 2.0 (see licenses/LICENSE_scala).
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ lazy val `core-common` = crossProject(JVMPlatform, JSPlatform, NativePlatform)
name := "otel4s-core-common",
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-core" % CatsVersion,
"org.typelevel" %%% "cats-effect-kernel" % CatsEffectVersion,
"org.typelevel" %%% "cats-effect" % CatsEffectVersion,
"org.typelevel" %%% "cats-mtl" % CatsMtlVersion,
"org.typelevel" %%% "vault" % VaultVersion % Test,
"org.typelevel" %%% "cats-laws" % CatsVersion % Test,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
package propagation

import cats.effect.SyncIO
import org.typelevel.otel4s.context.syntax._

/** A [[TextMapPropagator]] that extracts a specified collection of fields and
* stores them in a context, and extracts them from a context later for
* injection. It does not interact with telemetry.
*/
final class PassThroughPropagator[Ctx, K[X] <: Key[X]] private (
val fields: Iterable[String],
entriesKey: K[List[(String, String)]]
)(implicit c: Contextual.Keyed[Ctx, K])
extends TextMapPropagator[Ctx] {

def extract[A](ctx: Ctx, carrier: A)(implicit
getter: TextMapGetter[A]
): Ctx = {
val list = fields.view
.flatMap(k => getter.get(carrier, k).map(k -> _))
.toList
if (list.isEmpty) ctx else ctx.updated(entriesKey, list)
}

def inject[A](ctx: Ctx, carrier: A)(implicit updater: TextMapUpdater[A]): A =
ctx
.getOrElse(entriesKey, Nil)
.foldLeft(carrier) { case (c, k -> v) => updater.updated(c, k, v) }

override def toString: String =
s"PassThroughPropagator{fields=${fields.mkString("[", ", ", "]")}}"
}

object PassThroughPropagator {
private def forDistinctFields[Ctx, K[X] <: Key[X]](fields: Seq[String])(
implicit
c: Contextual.Keyed[Ctx, K],
kp: Key.Provider[SyncIO, K]
): TextMapPropagator[Ctx] =
if (fields.isEmpty) TextMapPropagator.noop
else {
new PassThroughPropagator(
fields,
kp.uniqueKey[List[(String, String)]](
"otel4s-PassThroughPropagator-entries"
).unsafeRunSync()
)
}

/** Creates a `PassThroughPropagator` that propagates the given fields. */
def apply[Ctx, K[X] <: Key[X]](fields: String*)(implicit
c: Contextual.Keyed[Ctx, K],
kp: Key.Provider[SyncIO, K]
): TextMapPropagator[Ctx] =
forDistinctFields(fields.distinct)

/** Creates a `PassThroughPropagator` that propagates the given fields. */
def apply[Ctx, K[X] <: Key[X]](fields: Iterable[String])(implicit
c: Contextual.Keyed[Ctx, K],
kp: Key.Provider[SyncIO, K]
): TextMapPropagator[Ctx] =
forDistinctFields(fields.iterator.distinct.toSeq)
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ import scala.collection.generic.IsSeq
* behavior of such implicit instances will be made to match the specification.
*
* @see
* See [[TextMapSetter]] to set a value to a mutable carrier
* @see
* See [[TextMapUpdater]] to update values of an immutable carrier
*
* @tparam A
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,11 @@ object TextMapPropagator {
*/
def of[Ctx](
propagators: TextMapPropagator[Ctx]*
): TextMapPropagator[Ctx] =
propagators.combineAll
): TextMapPropagator[Ctx] = {
// reference stability for noop
if (propagators.lengthIs == 1) propagators.head
else propagators.combineAll
}

/** Creates a no-op implementation of the [[TextMapPropagator]].
*
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ import scala.collection.immutable.SortedMapOps
*
* @see
* See [[TextMapGetter]] to get a value from the carrier
* @see
* See [[TextMapSetter]] to set values to a mutable carrier
*
* @tparam A
* the type of the carrier
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* 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.ScalaCheckSuite
import org.scalacheck.Prop.forAll
import org.typelevel.otel4s.context.vault.VaultContext

class PassThroughPropagatorProps extends ScalaCheckSuite {
private[this] def vaultPropagator(
fields: Iterable[String]
): TextMapPropagator[VaultContext] =
PassThroughPropagator(fields)

property("retrieves all stored values matching fields and no others") {
forAll {
(
entries: Map[String, String],
extraFields: Set[String],
unpropagated: Map[String, String]
) =>
val propagator = vaultPropagator(entries.keys.view ++ extraFields)
val toSkip = unpropagated.view
.filterKeys(s => !entries.contains(s) && !extraFields.contains(s))
.toMap
val extracted = propagator.extract(VaultContext.root, entries ++ toSkip)
val injected = propagator.inject(extracted, Map.empty[String, String])
assertEquals(injected, entries)
for (key -> _ <- toSkip) assert(!injected.contains(key)) // redundant
}
}

property("retrieves no values when none were stored") {
forAll { (entries: Map[String, String], extraFields: Seq[String]) =>
val propagator = vaultPropagator(entries.keys.view ++ extraFields)
val injected =
propagator.inject(VaultContext.root, Map.empty[String, String])
assert(injected.isEmpty)
}
}

property("retrieves no values when created with no fields") {
forAll { (entries: Map[String, String]) =>
val propagator = vaultPropagator(Nil)
val extracted = propagator.extract(VaultContext.root, entries)
val injected = propagator.inject(extracted, Map.empty[String, String])
assertEquals(extracted, VaultContext.root)
assert(injected.isEmpty)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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.FunSuite
import org.typelevel.otel4s.context.vault.VaultContext

class PassThroughPropagatorSuite extends FunSuite {
private val propagator: TextMapPropagator[VaultContext] =
PassThroughPropagator("foo", "bar")

test("propagates only fields given to constructor") {
val entries = Map("foo" -> "0", "bar" -> "1", "baz" -> "2", "qux" -> "3")
val extracted = propagator.extract(VaultContext.root, entries)
val injected = propagator.inject(extracted, Map.empty[String, String])
assertEquals(injected, Map("foo" -> "0", "bar" -> "1"))
}

test("propagates nothing when no matching fields") {
val entries = Map("baz" -> "2", "qux" -> "3")
val extracted = propagator.extract(VaultContext.root, entries)
val injected = propagator.inject(extracted, Map.empty[String, String])
assert(injected.isEmpty)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,18 @@ class TextMapPropagatorSuite extends FunSuite {
val propagator = new TestPropagator[String](fields, "TestPropagator")
val composite = TextMapPropagator.of(propagator)

assertEquals(composite.fields, fields)
assert(composite eq propagator)
assertEquals(composite.toString, "TestPropagator")
}

test("of (single input, noop) - use same reference") {
val propagator = TextMapPropagator.noop[String]
val composite = TextMapPropagator.of(propagator)

assert(composite eq propagator)
assertEquals(composite.toString, "TextMapPropagator.Noop")
}

test("of (multiple) - create a multi instance") {
val fieldsA = List("a", "b")
val fieldsB = List("c", "d")
Expand Down

This file was deleted.

Loading

0 comments on commit db4381d

Please sign in to comment.