diff --git a/sdk/common/src/main/scala/org/typelevel/otel4s/sdk/Attributes.scala b/sdk/common/src/main/scala/org/typelevel/otel4s/sdk/Attributes.scala index 7a70e7e14..32783439c 100644 --- a/sdk/common/src/main/scala/org/typelevel/otel4s/sdk/Attributes.scala +++ b/sdk/common/src/main/scala/org/typelevel/otel4s/sdk/Attributes.scala @@ -16,9 +16,7 @@ package org.typelevel.otel4s.sdk -import cats.Applicative import cats.Hash -import cats.Monad import cats.Monoid import cats.Show import cats.implicits._ @@ -26,12 +24,15 @@ import org.typelevel.otel4s.Attribute import org.typelevel.otel4s.Attribute.KeySelect import org.typelevel.otel4s.AttributeKey +import scala.collection.SpecificIterableFactory +import scala.collection.mutable + /** An immutable collection of [[Attribute]]s. It contains only unique keys. * * @see * [[https://opentelemetry.io/docs/specs/otel/common/#attribute-collections]] */ -sealed trait Attributes { +sealed trait Attributes extends Iterable[Attribute[_]] { /** Returns an attribute for the given attribute name, or `None` if not found. */ @@ -41,31 +42,10 @@ sealed trait Attributes { */ def get[T](key: AttributeKey[T]): Option[Attribute[T]] - /** Whether the attributes collection is empty or not. - */ - def isEmpty: Boolean - - /** The size of the attributes collection. - */ - def size: Int - /** Whether this attributes collection contains the given key. */ def contains(key: AttributeKey[_]): Boolean - /** Applies an operation to each attribute and accumulates the results from - * left to right. - */ - def foldLeft[F[_]: Monad, B](z: B)(f: (B, Attribute[_]) => F[B]): F[B] - - /** Checks if the given predicate holds for all attributes in the collection. - */ - def forall[F[_]: Monad](p: Attribute[_] => F[Boolean]): F[Boolean] - - /** Applies the given operation to each attribute in the collection. - */ - def foreach[F[_]: Applicative](f: Attribute[_] => F[Unit]): F[Unit] - /** Returns the `Map` representation of the attributes collection. */ def toMap: Map[AttributeKey[_], Attribute[_]] @@ -87,55 +67,8 @@ sealed trait Attributes { Show[Attributes].show(this) } -object Attributes { - - /** A '''mutable''' builder of [[Attributes]]. - */ - sealed trait Builder { - - /** Adds the given `attribute` to the builder. - * - * @note - * if the key of the given `attribute` is already present in the builder, - * the value will be overwritten with the corresponding given attribute. - * - * @param attribute - * the attribute to add - */ - def addOne[A](attribute: Attribute[A]): Builder - - /** Adds the attribute with the given `key` and `value` to the builder. - * - * @note - * if the given `key` is already present in the builder, the value will - * be overwritten with the given `value`. - * - * @param key - * the key of the attribute. Denotes the types of the `value` - * - * @param value - * the value of the attribute - */ - def addOne[A](key: AttributeKey[A], value: A): Builder - - /** Adds the given `attributes` to the builder. - * - * @note - * if the keys of the given `attributes` are already present in the - * builder, the values will be overwritten with the corresponding given - * attributes. - * - * @param attributes - * the attributes to add - */ - def addAll(attributes: Attributes): Builder - - /** Creates [[Attributes]] with the attributes of this builder. - */ - def result(): Attributes - } - - val Empty: Attributes = new MapAttributes(Map.empty) +object Attributes extends SpecificIterableFactory[Attribute[_], Attributes] { + private val Empty = new MapAttributes(Map.empty) /** Creates [[Attributes]] with the given `attributes`. * @@ -146,10 +79,18 @@ object Attributes { * @param attributes * the attributes to use */ - def apply(attributes: Attribute[_]*): Attributes = - fromIterable(attributes) + override def apply(attributes: Attribute[_]*): Attributes = + fromSpecific(attributes) - /** Creates [[Attributes]] from the given collection of `attributes`. + /** Creates an empty [[Builder]] of [[Attributes]]. + */ + def newBuilder: Builder = new Builder + + /** Returns empty [[Attributes]]. + */ + def empty: Attributes = Empty + + /** Creates [[Attributes]] from the given collection. * * @note * if there are duplicated keys in the given `attributes`, only the last @@ -158,13 +99,11 @@ object Attributes { * @param attributes * the attributes to use */ - def fromIterable(attributes: Iterable[Attribute[_]]): Attributes = { - val builder = Map.newBuilder[AttributeKey[_], Attribute[_]] - attributes.foreach { a => - builder += (a.key -> a) + def fromSpecific(attributes: IterableOnce[Attribute[_]]): Attributes = + attributes match { + case a: Attributes => a + case other => (newBuilder ++= other).result() } - new MapAttributes(builder.result()) - } /** Creates [[Attributes]] from the given map of `attributes`. * @@ -174,11 +113,6 @@ object Attributes { def fromMap(attributes: Map[AttributeKey[_], Attribute[_]]): Attributes = new MapAttributes(attributes) - /** Creates an empty [[Builder]] of [[Attributes]]. - */ - def builder: Builder = - new MutableBuilder - implicit val showAttributes: Show[Attributes] = Show.show { attributes => attributes.toList .map(a => show"$a") @@ -197,6 +131,72 @@ object Attributes { else new MapAttributes(x.toMap ++ y.toMap) } + /** A '''mutable''' builder of [[Attributes]]. + */ + final class Builder extends mutable.Builder[Attribute[_], Attributes] { + private val builder = Map.newBuilder[AttributeKey[_], Attribute[_]] + + /** Adds the attribute with the given `key` and `value` to the builder. + * + * @note + * if the given `key` is already present in the builder, the value will + * be overwritten with the given `value`. + * + * @param key + * the key of the attribute. Denotes the types of the `value` + * + * @param value + * the value of the attribute + */ + def addOne[A](key: AttributeKey[A], value: A): this.type = { + builder.addOne((key, Attribute(key, value))) + this + } + + /** Adds the given `attribute` to the builder. + * + * @note + * if the key of the given `attribute` is already present in the builder, + * the value will be overwritten with the corresponding given attribute. + * + * @param attribute + * the attribute to add + */ + def addOne(attribute: Attribute[_]): this.type = { + builder.addOne((attribute.key, attribute)) + this + } + + /** Adds the given `attributes` to the builder. + * + * @note + * if the keys of the given `attributes` are already present in the + * builder, the values will be overwritten with the corresponding given + * attributes. + * + * @param attributes + * the attributes to add + */ + override def addAll(attributes: IterableOnce[Attribute[_]]): this.type = { + attributes match { + case a: Attributes => builder.addAll(a.toMap) + case other => super.addAll(other) + } + this + } + + override def sizeHint(size: Int): Unit = + builder.sizeHint(size) + + def clear(): Unit = + builder.clear() + + /** Creates [[Attributes]] with the attributes of this builder. + */ + def result(): Attributes = + new MapAttributes(builder.result()) + } + private final class MapAttributes( private val m: Map[AttributeKey[_], Attribute[_]] ) extends Attributes { @@ -208,50 +208,24 @@ object Attributes { def get[T](key: AttributeKey[T]): Option[Attribute[T]] = m.get(key).map(_.asInstanceOf[Attribute[T]]) - def isEmpty: Boolean = m.isEmpty - - def size: Int = m.size - def contains(key: AttributeKey[_]): Boolean = m.contains(key) - - def foldLeft[F[_]: Monad, B](z: B)(f: (B, Attribute[_]) => F[B]): F[B] = - m.foldLeft(Monad[F].pure(z)) { (acc, v) => - acc.flatMap(b => f(b, v._2)) - } - - def forall[F[_]: Monad](p: Attribute[_] => F[Boolean]): F[Boolean] = - foldLeft(true) { (b, a) => - if (b) p(a).map(b && _) - else Monad[F].pure(false) - } - - def foreach[F[_]: Applicative](f: Attribute[_] => F[Unit]): F[Unit] = - m.foldLeft(Applicative[F].unit)((acc, v) => acc *> f(v._2)) - def toMap: Map[AttributeKey[_], Attribute[_]] = m - - def toList: List[Attribute[_]] = m.values.toList + def iterator: Iterator[Attribute[_]] = m.valuesIterator + + override def isEmpty: Boolean = m.isEmpty + override def size: Int = m.size + override def knownSize: Int = m.knownSize + override def empty: Attributes = Attributes.empty + override def toList: List[Attribute[_]] = m.values.toList + + override protected def fromSpecific( + coll: IterableOnce[Attribute[_]] + ): Attributes = + Attributes.fromSpecific(coll) + + override protected def newSpecificBuilder + : mutable.Builder[Attribute[_], Attributes] = + Attributes.newBuilder } - private final class MutableBuilder extends Builder { - private val builder = Map.newBuilder[AttributeKey[_], Attribute[_]] - - def addOne[A](attribute: Attribute[A]): Builder = { - val _ = builder.addOne((attribute.key, attribute)) - this - } - - def addOne[A](key: AttributeKey[A], value: A): Builder = { - val _ = builder.addOne((key, Attribute(key, value))) - this - } - - def addAll(attributes: Attributes): Builder = { - val _ = builder.addAll(attributes.toMap) - this - } - - def result(): Attributes = - new MapAttributes(builder.result()) - } } diff --git a/sdk/common/src/main/scala/org/typelevel/otel4s/sdk/Resource.scala b/sdk/common/src/main/scala/org/typelevel/otel4s/sdk/Resource.scala index 3547b19ed..a9819af3a 100644 --- a/sdk/common/src/main/scala/org/typelevel/otel4s/sdk/Resource.scala +++ b/sdk/common/src/main/scala/org/typelevel/otel4s/sdk/Resource.scala @@ -95,7 +95,7 @@ object Resource { * @return * an empty [[Resource]]. */ - val Empty: Resource = Resource(Attributes.Empty) + val Empty: Resource = Resource(Attributes.empty) private val TelemetrySdk: Resource = Resource( Attributes( diff --git a/sdk/common/src/main/scala/org/typelevel/otel4s/sdk/common/InstrumentationScope.scala b/sdk/common/src/main/scala/org/typelevel/otel4s/sdk/common/InstrumentationScope.scala index ee2b43927..093203c53 100644 --- a/sdk/common/src/main/scala/org/typelevel/otel4s/sdk/common/InstrumentationScope.scala +++ b/sdk/common/src/main/scala/org/typelevel/otel4s/sdk/common/InstrumentationScope.scala @@ -104,7 +104,7 @@ object InstrumentationScope { } private val Empty: InstrumentationScope = - apply("", None, None, Attributes.Empty) + apply("", None, None, Attributes.empty) /** An empty [[InstrumentationScope]] */ def empty: InstrumentationScope = Empty @@ -115,7 +115,7 @@ object InstrumentationScope { * the name of the instrumentation scope */ def builder(name: String): Builder = - ScopeImpl(name, None, None, Attributes.Empty) + ScopeImpl(name, None, None, Attributes.empty) /** Creates an [[InstrumentationScope]]. * diff --git a/sdk/common/src/test/scala/org/typelevel/otel4s/sdk/AttributesProps.scala b/sdk/common/src/test/scala/org/typelevel/otel4s/sdk/AttributesProps.scala index e189b504b..3099e4742 100644 --- a/sdk/common/src/test/scala/org/typelevel/otel4s/sdk/AttributesProps.scala +++ b/sdk/common/src/test/scala/org/typelevel/otel4s/sdk/AttributesProps.scala @@ -16,7 +16,6 @@ package org.typelevel.otel4s.sdk -import cats.Id import cats.Show import cats.syntax.semigroup._ import munit.ScalaCheckSuite @@ -34,7 +33,7 @@ class AttributesProps extends ScalaCheckSuite { property("Attributes#size is equal to the number of unique keys") { forAll(listOfAttributes) { attributes => val keysSet = attributes.map(_.key).toSet - val attrs = Attributes(attributes: _*) + val attrs = Attributes.fromSpecific(attributes) keysSet.size == attrs.size } @@ -43,7 +42,7 @@ class AttributesProps extends ScalaCheckSuite { property("Attributes#isEmpty is true when there are no attributes") { forAll(listOfAttributes) { attributes => val keysSet = attributes.map(_.key).toSet - val attrs = Attributes(attributes: _*) + val attrs = Attributes.fromSpecific(attributes) keysSet.isEmpty == attrs.isEmpty } @@ -52,7 +51,7 @@ class AttributesProps extends ScalaCheckSuite { property("Attributes#contains is true when the key is present") { forAll(listOfAttributes) { attributes => val keysSet = attributes.map(_.key).toSet - val attrs = Attributes(attributes: _*) + val attrs = Attributes.fromSpecific(attributes) keysSet.forall(attrs.contains) } @@ -60,10 +59,10 @@ class AttributesProps extends ScalaCheckSuite { property("Attributes#foreach iterates over all attributes") { forAll(listOfAttributes) { attributes => - val attrs = Attributes(attributes: _*) + val attrs = Attributes.fromSpecific(attributes) var count = 0 - attrs.foreach[Id] { _ => count += 1 } + attrs.foreach(_ => count += 1) count == attrs.size } @@ -71,7 +70,7 @@ class AttributesProps extends ScalaCheckSuite { property("Attributes#toList returns a list of all attributes") { forAll(listOfAttributes) { attributes => - val attrs = Attributes(attributes: _*) + val attrs = Attributes.fromSpecific(attributes) val list = attrs.toList list.size == attrs.size && list.forall(a => attrs.contains(a.key)) @@ -80,10 +79,10 @@ class AttributesProps extends ScalaCheckSuite { property("Attributes#foldLeft folds over all attributes") { forAll(listOfAttributes) { attributes => - val attrs = Attributes(attributes: _*) + val attrs = Attributes.fromSpecific(attributes) val list = attrs.toList - val folded = attrs.foldLeft[Id, Int](0) { (acc, _) => acc + 1 } + val folded = attrs.foldLeft[Int](0) { (acc, _) => acc + 1 } folded == list.size } @@ -93,15 +92,15 @@ class AttributesProps extends ScalaCheckSuite { "Attributes#forall returns true when all attributes match the predicate" ) { forAll(listOfAttributes) { attributes => - val attrs = Attributes(attributes: _*) + val attrs = Attributes.fromSpecific(attributes) - attrs.forall[Id](_ => true) + attrs.forall(_ => true) } } property("Attributes#toMap returns a map of all attributes") { forAll(listOfAttributes) { attributes => - val attrs = Attributes(attributes: _*) + val attrs = Attributes.fromSpecific(attributes) val map = attrs.toMap map.size == attrs.size && map.forall { case (k, v) => diff --git a/sdk/common/src/test/scala/org/typelevel/otel4s/sdk/ResourceProps.scala b/sdk/common/src/test/scala/org/typelevel/otel4s/sdk/ResourceProps.scala index 488016cb4..63a646bb1 100644 --- a/sdk/common/src/test/scala/org/typelevel/otel4s/sdk/ResourceProps.scala +++ b/sdk/common/src/test/scala/org/typelevel/otel4s/sdk/ResourceProps.scala @@ -16,7 +16,6 @@ package org.typelevel.otel4s.sdk -import cats.Id import cats.Show import cats.syntax.show._ import munit.ScalaCheckSuite @@ -34,7 +33,7 @@ class ResourceProps extends ScalaCheckSuite { val keys = resource1.attributes.toMap.keySet ++ resource2.attributes.toMap.keySet - mergedAttrs.size == keys.size && mergedAttrs.forall[Id] { a => + mergedAttrs.size == keys.size && mergedAttrs.forall { a => resource2.attributes .get(a.key) .orElse(resource1.attributes.get(a.key)) diff --git a/sdk/common/src/test/scala/org/typelevel/otel4s/sdk/ResourceSuite.scala b/sdk/common/src/test/scala/org/typelevel/otel4s/sdk/ResourceSuite.scala index de75cd99b..abfcd7693 100644 --- a/sdk/common/src/test/scala/org/typelevel/otel4s/sdk/ResourceSuite.scala +++ b/sdk/common/src/test/scala/org/typelevel/otel4s/sdk/ResourceSuite.scala @@ -30,8 +30,8 @@ class ResourceSuite extends FunSuite { expected: Either[ResourceInitiationError, Option[String]] ): Unit = assertEquals( - Resource(Attributes.Empty, leftSchemaUrl) - .mergeInto(Resource(Attributes.Empty, rightSchemaUrl)) + Resource(Attributes.empty, leftSchemaUrl) + .mergeInto(Resource(Attributes.empty, rightSchemaUrl)) .map(_.schemaUrl), expected ) diff --git a/sdk/common/src/test/scala/org/typelevel/otel4s/sdk/common/InstrumentationScopeSuite.scala b/sdk/common/src/test/scala/org/typelevel/otel4s/sdk/common/InstrumentationScopeSuite.scala index 419fda2c1..3e337c91a 100644 --- a/sdk/common/src/test/scala/org/typelevel/otel4s/sdk/common/InstrumentationScopeSuite.scala +++ b/sdk/common/src/test/scala/org/typelevel/otel4s/sdk/common/InstrumentationScopeSuite.scala @@ -77,7 +77,7 @@ class InstrumentationScopeSuite extends DisciplineSuite { } test("empty instance") { - val expected = InstrumentationScope("", None, None, Attributes.Empty) + val expected = InstrumentationScope("", None, None, Attributes.empty) assertEquals(InstrumentationScope.empty, expected) } diff --git a/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/data/EventData.scala b/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/data/EventData.scala index e43d5a99b..cdf78599f 100644 --- a/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/data/EventData.scala +++ b/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/data/EventData.scala @@ -105,19 +105,17 @@ object EventData { attributes: Attributes, escaped: Boolean ): EventData = { - import SemanticAttributes.ExceptionEscaped - import SemanticAttributes.ExceptionMessage - import SemanticAttributes.ExceptionStacktrace - import SemanticAttributes.ExceptionType - val allAttributes = { - val builder = Attributes.builder + val builder = Attributes.newBuilder - builder.addOne(ExceptionType, exception.getClass.getName) + builder.addOne( + SemanticAttributes.ExceptionType, + exception.getClass.getName + ) val message = exception.getMessage if (message != null) { - val _ = builder.addOne(ExceptionMessage, message) + builder.addOne(SemanticAttributes.ExceptionMessage, message) } if (exception.getStackTrace.nonEmpty) { @@ -125,10 +123,13 @@ object EventData { val printWriter = new PrintWriter(stringWriter) exception.printStackTrace(printWriter) - val _ = builder.addOne(ExceptionStacktrace, stringWriter.toString) + builder.addOne( + SemanticAttributes.ExceptionStacktrace, + stringWriter.toString + ) } - builder.addOne(ExceptionEscaped, escaped) + builder.addOne(SemanticAttributes.ExceptionEscaped, escaped) builder.addAll(attributes) builder.result() diff --git a/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/data/LinkData.scala b/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/data/LinkData.scala index 7abb9ebdf..d48d43ead 100644 --- a/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/data/LinkData.scala +++ b/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/data/LinkData.scala @@ -62,7 +62,7 @@ object LinkData { * the context of the span the link refers to */ def apply(context: SpanContext): LinkData = - Impl(context, Attributes.Empty) + Impl(context, Attributes.empty) /** Creates a [[LinkData]] with the given `context`. * diff --git a/sdk/trace/src/test/scala/org/typelevel/otel4s/sdk/trace/Gens.scala b/sdk/trace/src/test/scala/org/typelevel/otel4s/sdk/trace/Gens.scala index c5a55c947..718885612 100644 --- a/sdk/trace/src/test/scala/org/typelevel/otel4s/sdk/trace/Gens.scala +++ b/sdk/trace/src/test/scala/org/typelevel/otel4s/sdk/trace/Gens.scala @@ -77,7 +77,7 @@ object Gens { val attributes: Gen[Attributes] = for { attributes <- Gen.listOf(attribute) - } yield Attributes.fromIterable(attributes) + } yield Attributes.fromSpecific(attributes) val instrumentationScope: Gen[InstrumentationScope] = for {