Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions modules/core/shared/src/main/scala/TraceValue.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

package natchez

import cats.*
import cats.syntax.all.*

sealed trait TraceValue extends Product with Serializable {
def value: Any
}
Expand All @@ -13,6 +16,12 @@ object TraceValue {
case class StringValue(value: String) extends TraceValue
case class BooleanValue(value: Boolean) extends TraceValue
case class NumberValue(value: Number) extends TraceValue
case class ListValue(value: List[TraceValue]) extends TraceValue
case object NoneValue extends TraceValue {
override def value: Nothing = throw new IllegalStateException(
"Cannot extract value from NoneValue"
)
}

implicit def viaTraceableValue[A: TraceableValue](a: A): TraceValue =
TraceableValue[A].toTraceValue(a)
Expand Down Expand Up @@ -57,4 +66,10 @@ object TraceableValue {
implicit val longToTraceValue: TraceableValue[Long] = TraceValue.NumberValue(_)
implicit val doubleToTraceValue: TraceableValue[Double] = TraceValue.NumberValue(_)
implicit val floatToTraceValue: TraceableValue[Float] = TraceValue.NumberValue(_)

implicit def foldableToTraceValue[F[_]: Foldable, A: TraceableValue]: TraceableValue[F[A]] =
fa => TraceValue.ListValue(fa.toList.map(TraceableValue[A].toTraceValue))

implicit def optionalToTraceValue[A: TraceableValue]: TraceableValue[Option[A]] =
_.fold[TraceValue](TraceValue.NoneValue)(TraceableValue[A].toTraceValue)
}
12 changes: 8 additions & 4 deletions modules/datadog/src/main/scala/DDSpan.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ package natchez
package datadog

import io.{opentracing => ot}
import cats.Applicative
import cats.data.Nested
import cats.effect.{Resource, Sync}
import cats.effect.Resource.ExitCase
import cats.syntax.all._
import io.opentracing.log.Fields
import io.opentracing.propagation.{Format, TextMapAdapter}
import io.opentracing.tag.Tags
import natchez.TraceValue.{BooleanValue, NumberValue, StringValue}
import natchez.TraceValue._
import _root_.datadog.trace.api.DDTags
import natchez.Span.Options
import natchez.datadog.DDTracer.{addLink, addSpanKind}
Expand Down Expand Up @@ -43,9 +44,12 @@ final case class DDSpan[F[_]: Sync](

def put(fields: (String, TraceValue)*): F[Unit] =
fields.toList.traverse_ {
case (str, StringValue(value)) => Sync[F].delay(span.setTag(str, value))
case (str, NumberValue(value)) => Sync[F].delay(span.setTag(str, value))
case (str, BooleanValue(value)) => Sync[F].delay(span.setTag(str, value))
case (str, StringValue(value)) => Sync[F].delay(span.setTag(str, value)).void
case (str, NumberValue(value)) => Sync[F].delay(span.setTag(str, value)).void
case (str, BooleanValue(value)) => Sync[F].delay(span.setTag(str, value)).void
case (str, ListValue(v)) =>
Sync[F].delay(span.setTag(str, v.map(_.toString).mkString(", "))).void
case (_, NoneValue) => Applicative[F].unit
}

override def log(fields: (String, TraceValue)*): F[Unit] = {
Expand Down
9 changes: 6 additions & 3 deletions modules/jaeger/src/main/scala/JaegerSpan.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package natchez
package jaeger

import io.{opentracing => ot}
import cats.Applicative
import cats.data.Nested
import cats.effect.Sync
import cats.effect.Resource
Expand Down Expand Up @@ -40,9 +41,11 @@ private[jaeger] final case class JaegerSpan[F[_]: Sync](

override def put(fields: (String, TraceValue)*): F[Unit] =
fields.toList.traverse_ {
case (k, StringValue(v)) => Sync[F].delay(span.setTag(k, v))
case (k, NumberValue(v)) => Sync[F].delay(span.setTag(k, v))
case (k, BooleanValue(v)) => Sync[F].delay(span.setTag(k, v))
case (k, StringValue(v)) => Sync[F].delay(span.setTag(k, v)).void
case (k, NumberValue(v)) => Sync[F].delay(span.setTag(k, v)).void
case (k, BooleanValue(v)) => Sync[F].delay(span.setTag(k, v)).void
case (k, ListValue(v)) => Sync[F].delay(span.setTag(k, v.map(_.toString).mkString(", "))).void
case (_, NoneValue) => Applicative[F].unit
}

override def attachError(err: Throwable, fields: (String, TraceValue)*): F[Unit] =
Expand Down
17 changes: 10 additions & 7 deletions modules/lightstep/src/main/scala/LightstepSpan.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@
package natchez
package lightstep

import cats.Applicative
import cats.effect.{Resource, Sync}
import cats.syntax.all._
import cats.syntax.all.*
import io.opentracing.log.Fields
import io.{opentracing => ot}
import io.opentracing as ot
import io.opentracing.propagation.{Format, TextMapAdapter}
import io.opentracing.tag.Tags
import natchez.Span.Options
import natchez.lightstep.Lightstep._
import natchez.lightstep.Lightstep.*

import scala.jdk.CollectionConverters._
import scala.jdk.CollectionConverters.*
import java.net.URI

private[lightstep] final case class LightstepSpan[F[_]: Sync](
Expand All @@ -36,9 +37,11 @@ private[lightstep] final case class LightstepSpan[F[_]: Sync](

override def put(fields: (String, TraceValue)*): F[Unit] =
fields.toList.traverse_ {
case (k, StringValue(v)) => Sync[F].delay(span.setTag(k, v))
case (k, NumberValue(v)) => Sync[F].delay(span.setTag(k, v))
case (k, BooleanValue(v)) => Sync[F].delay(span.setTag(k, v))
case (k, StringValue(v)) => Sync[F].delay(span.setTag(k, v)).void
case (k, NumberValue(v)) => Sync[F].delay(span.setTag(k, v)).void
case (k, BooleanValue(v)) => Sync[F].delay(span.setTag(k, v)).void
case (k, ListValue(v)) => Sync[F].delay(span.setTag(k, v.map(_.toString).mkString(", "))).void
case (_, NoneValue) => Applicative[F].unit
}

override def attachError(err: Throwable, fields: (String, TraceValue)*): F[Unit] =
Expand Down
4 changes: 3 additions & 1 deletion modules/log-odin/src/main/scala/LogSpan.scala
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ private[logodin] final case class LogSpan[F[_]: Sync: Logger](

private[logodin] object LogSpan {

implicit val EncodeTraceValue: Encoder[TraceValue] =
implicit lazy val EncodeTraceValue: Encoder[TraceValue] =
Encoder.instance {
case StringValue(s) => s.asJson
case BooleanValue(b) => b.asJson
Expand All @@ -138,6 +138,8 @@ private[logodin] object LogSpan {
case NumberValue(n: BigDecimal) => n.asJson
case NumberValue(n: BigInt) => n.asJson
case NumberValue(n) => n.doubleValue.asJson
case ListValue(vs) => vs.map(EncodeTraceValue(_)).asJson
case NoneValue => Json.Null
}

implicit val KeyEncodeCIString: KeyEncoder[CIString] = KeyEncoder[String].contramap(_.toString)
Expand Down
4 changes: 3 additions & 1 deletion modules/log/shared/src/main/scala/LogSpan.scala
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ private[log] final case class LogSpan[F[_]: Sync: Logger: UUIDGen](

private[log] object LogSpan {

implicit val EncodeTraceValue: Encoder[TraceValue] =
implicit lazy val EncodeTraceValue: Encoder[TraceValue] =
Encoder.instance {
case StringValue(s) => s.asJson
case BooleanValue(b) => b.asJson
Expand All @@ -126,6 +126,8 @@ private[log] object LogSpan {
case NumberValue(n: BigDecimal) => n.asJson
case NumberValue(n: BigInt) => n.asJson
case NumberValue(n) => n.doubleValue.asJson
case ListValue(vs) => vs.map(EncodeTraceValue(_)).asJson
case NoneValue => Json.Null
}

implicit val KeyEncodeCIString: KeyEncoder[CIString] = KeyEncoder[String].contramap(_.toString)
Expand Down
11 changes: 7 additions & 4 deletions modules/mock/src/main/scala/MockSpan.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ package mock

import scala.jdk.CollectionConverters._

import cats.Applicative
import cats.effect.{Resource, Sync}
import cats.syntax.all._
import io.opentracing.log.Fields
import io.{opentracing => ot}
import io.opentracing.propagation.{Format, TextMapAdapter}
import io.opentracing.tag.Tags
import natchez.TraceValue.{BooleanValue, NumberValue, StringValue}
import natchez.TraceValue._
import java.net.URI

final case class MockSpan[F[_]: Sync](tracer: ot.mock.MockTracer, span: ot.mock.MockSpan)
Expand All @@ -32,9 +33,11 @@ final case class MockSpan[F[_]: Sync](tracer: ot.mock.MockTracer, span: ot.mock.

def put(fields: (String, TraceValue)*): F[Unit] =
fields.toList.traverse_ {
case (k, StringValue(v)) => Sync[F].delay(span.setTag(k, v))
case (k, NumberValue(v)) => Sync[F].delay(span.setTag(k, v))
case (k, BooleanValue(v)) => Sync[F].delay(span.setTag(k, v))
case (k, StringValue(v)) => Sync[F].delay(span.setTag(k, v)).void
case (k, NumberValue(v)) => Sync[F].delay(span.setTag(k, v)).void
case (k, BooleanValue(v)) => Sync[F].delay(span.setTag(k, v)).void
case (k, ListValue(v)) => Sync[F].delay(span.setTag(k, v.map(_.toString).mkString(", "))).void
case (_, NoneValue) => Applicative[F].unit
}

def attachError(err: Throwable, fields: (String, TraceValue)*): F[Unit] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@

package natchez.newrelic

import cats.Applicative
import java.net.URI
import java.util.UUID
import cats.effect.Ref
import cats.effect.{Resource, Sync}
import cats.syntax.all._
import com.newrelic.telemetry.Attributes
import com.newrelic.telemetry.spans.{Span, SpanBatch, SpanBatchSender}
import natchez.TraceValue.{BooleanValue, NumberValue, StringValue}
import natchez.TraceValue._
import natchez.newrelic.NewrelicSpan.Headers
import natchez.{Kernel, TraceValue}
import org.typelevel.ci._
Expand Down Expand Up @@ -45,9 +46,11 @@ private[newrelic] final case class NewrelicSpan[F[_]: Sync](

override def put(fields: (String, TraceValue)*): F[Unit] =
fields.toList.traverse_ {
case (k, StringValue(v)) => attributes.update(att => att.put(k, v))
case (k, NumberValue(v)) => attributes.update(att => att.put(k, v))
case (k, BooleanValue(v)) => attributes.update(att => att.put(k, v))
case (k, StringValue(v)) => attributes.update(att => att.put(k, v)).void
case (k, NumberValue(v)) => attributes.update(att => att.put(k, v)).void
case (k, BooleanValue(v)) => attributes.update(att => att.put(k, v)).void
case (k, ListValue(vs)) => attributes.update(att => att.put(k, vs.mkString(", "))).void
case (_, NoneValue) => Applicative[F].unit
}

override def attachError(err: Throwable, fields: (String, TraceValue)*): F[Unit] =
Expand Down
34 changes: 24 additions & 10 deletions modules/opencensus/src/main/scala/OpenCensusSpan.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import io.opencensus.trace.{AttributeValue, Sampler, SpanBuilder, Tracer, Tracin
import io.opencensus.trace.propagation.SpanContextParseException
import io.opencensus.trace.propagation.TextFormat.Getter
import natchez.Span.{Options, SpanKind}
import natchez.TraceValue.{BooleanValue, NumberValue, StringValue}
import natchez.TraceValue._
import org.typelevel.ci._

import scala.collection.mutable
Expand All @@ -32,24 +32,38 @@ private[opencensus] final case class OpenCensusSpan[F[_]: Sync](
override protected val spanCreationPolicyOverride: Options.SpanCreationPolicy =
options.spanCreationPolicy

private def traceToAttribute(value: TraceValue): AttributeValue = value match {
private def traceToAttribute(value: TraceValue): Option[AttributeValue] = value match {
case StringValue(v) =>
val safeString = if (v == null) "null" else v
AttributeValue.stringAttributeValue(safeString)
AttributeValue.stringAttributeValue(safeString).some
case NumberValue(v) =>
AttributeValue.doubleAttributeValue(v.doubleValue())
AttributeValue.doubleAttributeValue(v.doubleValue()).some
case BooleanValue(v) =>
AttributeValue.booleanAttributeValue(v)
AttributeValue.booleanAttributeValue(v).some
case ListValue(v) =>
AttributeValue.stringAttributeValue(v.map(_.toString).mkString(", ")).some
case NoneValue => None
}

private def fieldsToAttributeValues(
fields: List[(String, TraceValue)]
): List[(String, AttributeValue)] =
fields.nested
.map(traceToAttribute)
.value
.collect { case (key, Some(value)) =>
key -> value
}

override def put(fields: (String, TraceValue)*): F[Unit] =
fields.toList.traverse_ { case (key, value) =>
Sync[F].delay(span.putAttribute(key, traceToAttribute(value)))
}
fieldsToAttributeValues(fields.toList)
.traverse_ { case (key, value) =>
Sync[F].delay(span.putAttribute(key, value))
}

override def log(fields: (String, TraceValue)*): F[Unit] = {
val map = fields.map { case (k, v) => k -> traceToAttribute(v) }.toMap.asJava
Sync[F].delay(span.addAnnotation("event", map)).void
val map = fieldsToAttributeValues(fields.toList).toMap.asJava
Sync[F].delay(span.addAnnotation("event", map)).unlessA(map.isEmpty)
}

override def log(event: String): F[Unit] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import io.opentelemetry.context.Context

import java.lang
import io.opentelemetry.api.{OpenTelemetry => OTel}
import TraceValue.{BooleanValue, NumberValue, StringValue}
import TraceValue._

import java.net.URI
import scala.collection.mutable
Expand Down Expand Up @@ -66,6 +66,17 @@ private[opentelemetry] final case class OpenTelemetrySpan[F[_]: Sync](
// and any other Number can fall back to a Double
case (k, NumberValue(v)) => bldr.put(k, v.doubleValue())
case (k, BooleanValue(v)) => bldr.put(k, v)
case (k, ListValue(vs)) =>
// TODO when support for HLists is merged, stop converting to string
// see https://opentelemetry.io/blog/2025/complex-attribute-types/#upcoming-support-for-complex-attribute-types-in-opentelemetry
val strings: List[String] = vs.collect {
case StringValue(v) => v
case BooleanValue(v) => v.toString
case NumberValue(v) => v.toString
case ListValue(v) => v.mkString(", ")
}
bldr.put(k, strings: _*)
case (_, NoneValue) => ()
}
bldr.build()
}
Expand Down
4 changes: 3 additions & 1 deletion modules/xray/src/main/scala/natchez/xray/XRaySpan.scala
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ private[xray] object XRaySpan {

final case class XRayException(id: String, ex: Throwable)

implicit val EncodeTraceValue: Encoder[TraceValue] =
implicit lazy val EncodeTraceValue: Encoder[TraceValue] =
Encoder.instance {
case StringValue(s) => s.asJson
case BooleanValue(b) => b.asJson
Expand All @@ -169,6 +169,8 @@ private[xray] object XRaySpan {
case NumberValue(n: BigDecimal) => n.asJson
case NumberValue(n: BigInt) => n.asJson
case NumberValue(n) => n.doubleValue.asJson
case ListValue(vs) => vs.map(EncodeTraceValue(_)).asJson
case NoneValue => Json.Null
}

val Header = ci"X-Amzn-Trace-Id"
Expand Down
Loading