Skip to content

Conversation

@bpholt
Copy link
Member

@bpholt bpholt commented Nov 26, 2025

I propose adding two new types to the TraceValue ADT, which currently consists of

sealed trait TraceValue extends Product with Serializable {
def value: Any
}
object TraceValue {
case class StringValue(value: String) extends TraceValue
case class BooleanValue(value: Boolean) extends TraceValue
case class NumberValue(value: Number) extends TraceValue

These new types would support two use cases that would be a helpful bridge to otel4s.

ListValue

case class ListValue(value: List[TraceValue]) extends TraceValue

ListValue contains a list of TraceValues and can be used by backends that have first-class support for list/arrays, such as OpenTelemetry and X-Ray.

My immediate use for this is to support adding the aws.xray.annotations attribute list, which X-Ray uses to signal that fields should be indexed and not simply recorded. Indexing makes them searchable, which helps tremendously in trace discoverability. Importantly, it does not work to serialize the list as a stringified JSON array. The backend must see aws.xray.annotations: Slice(["foo","bar","baz"]) and not e.g. aws.xray.annotations: Str(["foo", "bar", "baz"]).

Other backends can serialize these values to a stringified JSON array or just mkString(", ").

NoneValue

case object NoneValue extends TraceValue {
  def value: Nothing = throw new IllegalStateException("Cannot extract value from NoneValue")
}

Adding NoneValue allows generic tracing instrumentation to more easily add optional values to spans using a new [A: TraceableValue]: TraceableValue[Option[A]] instance that uses TraceableValue[A] on Some and NoneValue on None.

This is helpful when semi-autogenerating instrumentation for an final-tagless encoded trait using cats-tagless Aspect[Foo, TraceableValue, TraceableValue], because the compiler requires an instance of TraceableValue for all the domain and codomain types on the trait. e.g.

trait Foo[F[_]]:
  def foo(opt: Option[Int]): F[Option[String]]

would require TraceableValue[Option[Int]] and TraceableValue[Option[String]] in scope. We currently handle this by emitting StringValue("None"), but it would be nice to have the option to omit these values entirely.

Issues

Does this require a major version bump? MiMa doesn't report any issues, but adding new cases to the ADT could result in MatchErrors if any downstream code is pattern matching on TraceValue. I thought I remembered reading somewhere that was discouraged, but it's not forbidden by the types, so maybe we should accept that people might be doing it. This might not be worth doing if it requires a major version bump to Natchez. (I know I would prefer to spend time migrating to otel4s as opposed to managing a major upgrade here.)

NoneValue has a suitable alternative today, but I'm not sure it's possible to do what ListValue would enable without it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant