Skip to content

Commit

Permalink
sdk-trace: add LinkData
Browse files Browse the repository at this point in the history
  • Loading branch information
iRevive committed Nov 14, 2023
1 parent 88d43bb commit 3a39752
Show file tree
Hide file tree
Showing 5 changed files with 404 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright 2023 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.sdk
package trace
package data

import cats.Hash
import cats.Show
import cats.syntax.show._
import org.typelevel.otel4s.trace.SpanContext

/** Data representation of a link.
*
* Link can be also used to reference spans from the same trace.
*
* @see
* [[https://opentelemetry.io/docs/specs/otel/trace/api/#link]]
*/
sealed trait LinkData {

/** The [[org.typelevel.otel4s.trace.SpanContext SpanContext]] of the span
* this link refers to.
*/
def spanContext: SpanContext

/** The [[Attributes]] associated with this link.
*/
def attributes: Attributes

override final def hashCode(): Int =
Hash[LinkData].hash(this)

override final def equals(obj: Any): Boolean =
obj match {
case other: LinkData => Hash[LinkData].eqv(this, other)
case _ => false
}

override final def toString: String =
Show[LinkData].show(this)
}

object LinkData {

/** Creates a [[LinkData]] with the given `context`.
*
* @param context
* the context of the span the link refers to
*/
def apply(context: SpanContext): LinkData =
Impl(context, Attributes.Empty)

/** Creates a [[LinkData]] with the given `context`.
*
* @param context
* the context of the span the link refers to
*/
def apply(context: SpanContext, attributes: Attributes): LinkData =
Impl(context, attributes)

implicit val linkDataHash: Hash[LinkData] =
Hash.by(data => (data.spanContext, data.attributes))

implicit val linkDataShow: Show[LinkData] =
Show.show { data =>
show"LinkData{spanContext=${data.spanContext}, attributes=${data.attributes}}"
}

private final case class Impl(
spanContext: SpanContext,
attributes: Attributes
) extends LinkData

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2023 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.sdk
package trace

import org.scalacheck.Arbitrary
import org.typelevel.otel4s.Attribute
import org.typelevel.otel4s.sdk.trace.samplers.SamplingDecision

object Arbitraries {

implicit val attributeArbitrary: Arbitrary[Attribute[_]] =
Arbitrary(Gens.attribute)

implicit val attributesArbitrary: Arbitrary[Attributes] =
Arbitrary(Gens.attributes)

implicit val resourceArbitrary: Arbitrary[Resource] =
Arbitrary(Gens.resource)

implicit val samplingDecisionArbitrary: Arbitrary[SamplingDecision] =
Arbitrary(Gens.samplingDecision)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright 2023 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.sdk
package trace

import org.scalacheck.Cogen
import org.scalacheck.rng.Seed
import org.typelevel.otel4s.Attribute
import org.typelevel.otel4s.AttributeKey
import org.typelevel.otel4s.AttributeType
import org.typelevel.otel4s.sdk.trace.samplers.SamplingDecision
import org.typelevel.otel4s.trace.SpanContext
import org.typelevel.otel4s.trace.TraceFlags
import org.typelevel.otel4s.trace.TraceState

object Cogens {

implicit val attributeTypeCogen: Cogen[AttributeType[_]] =
Cogen[String].contramap(_.toString)

implicit def attributeKeyCogen[A]: Cogen[AttributeKey[A]] =
Cogen[(String, String)].contramap[AttributeKey[A]] { attribute =>
(attribute.name, attribute.`type`.toString)
}

implicit def attributeCogen[A: Cogen]: Cogen[Attribute[A]] =
Cogen[(AttributeKey[A], A)].contramap(a => (a.key, a.value))

implicit val attributeExistentialCogen: Cogen[Attribute[_]] =
Cogen { (seed, attr) =>
def primitive[A: Cogen](seed: Seed): Seed =
Cogen[A].perturb(seed, attr.value.asInstanceOf[A])

def list[A: Cogen](seed: Seed): Seed =
Cogen[List[A]].perturb(seed, attr.value.asInstanceOf[List[A]])

val valueCogen: Seed => Seed = attr.key.`type` match {
case AttributeType.Boolean => primitive[Boolean]
case AttributeType.Double => primitive[Double]
case AttributeType.String => primitive[String]
case AttributeType.Long => primitive[Long]
case AttributeType.BooleanList => list[Boolean]
case AttributeType.DoubleList => list[Double]
case AttributeType.StringList => list[String]
case AttributeType.LongList => list[Long]
}

valueCogen(attributeKeyCogen.perturb(seed, attr.key))
}

implicit val attributesCogen: Cogen[Attributes] =
Cogen[List[Attribute[_]]].contramap(_.toList)

implicit val resourceCogen: Cogen[Resource] =
Cogen[(Attributes, Option[String])].contramap { r =>
(r.attributes, r.schemaUrl)
}

implicit val samplingDecisionCogen: Cogen[SamplingDecision] =
Cogen[String].contramap(_.toString)

implicit val traceFlagsCogen: Cogen[TraceFlags] =
Cogen[Byte].contramap(_.toByte)

implicit val traceStateCogen: Cogen[TraceState] =
Cogen[Map[String, String]].contramap(_.asMap)

implicit val spanContextCogen: Cogen[SpanContext] =
Cogen[(String, String, TraceFlags, TraceState, Boolean, Boolean)]
.contramap { c =>
(
c.traceIdHex,
c.spanIdHex,
c.traceFlags,
c.traceState,
c.isRemote,
c.isValid
)
}

}
117 changes: 117 additions & 0 deletions sdk/trace/src/test/scala/org/typelevel/otel4s/sdk/trace/Gens.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* Copyright 2023 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.sdk
package trace

import org.scalacheck.Arbitrary
import org.scalacheck.Gen
import org.typelevel.otel4s.Attribute
import org.typelevel.otel4s.Attribute.KeySelect
import org.typelevel.otel4s.sdk.trace.samplers.SamplingDecision
import org.typelevel.otel4s.trace.SpanContext
import org.typelevel.otel4s.trace.SpanKind
import org.typelevel.otel4s.trace.TraceFlags
import org.typelevel.otel4s.trace.TraceState
import scodec.bits.ByteVector

object Gens {

private val nonEmptyString: Gen[String] =
Arbitrary.arbitrary[String].suchThat(_.nonEmpty)

val attribute: Gen[Attribute[_]] = {
implicit val stringArb: Arbitrary[String] =
Arbitrary(nonEmptyString)

implicit def listArb[A: Arbitrary]: Arbitrary[List[A]] =
Arbitrary(Gen.nonEmptyListOf(Arbitrary.arbitrary[A]))

def attribute[A: KeySelect: Arbitrary]: Gen[Attribute[A]] =
for {
key <- nonEmptyString
value <- Arbitrary.arbitrary[A]
} yield Attribute(key, value)

val string: Gen[Attribute[String]] = attribute[String]
val boolean: Gen[Attribute[Boolean]] = attribute[Boolean]
val long: Gen[Attribute[Long]] = attribute[Long]
val double: Gen[Attribute[Double]] = attribute[Double]

val stringList: Gen[Attribute[List[String]]] = attribute[List[String]]
val booleanList: Gen[Attribute[List[Boolean]]] = attribute[List[Boolean]]
val longList: Gen[Attribute[List[Long]]] = attribute[List[Long]]
val doubleList: Gen[Attribute[List[Double]]] = attribute[List[Double]]

Gen.oneOf(
boolean,
string,
long,
double,
stringList,
booleanList,
longList,
doubleList
)
}

val attributes: Gen[Attributes] =
for {
attributes <- Gen.listOf(attribute)
} yield Attributes(attributes: _*)

val resource: Gen[Resource] =
for {
attributes <- Gens.attributes
schemaUrl <- Gen.option(nonEmptyString)
} yield Resource(attributes, schemaUrl)

val samplingDecision: Gen[SamplingDecision] =
Gen.oneOf(
SamplingDecision.Drop,
SamplingDecision.RecordOnly,
SamplingDecision.RecordAndSample
)

val spanKind: Gen[SpanKind] =
Gen.oneOf(
SpanKind.Internal,
SpanKind.Server,
SpanKind.Client,
SpanKind.Producer,
SpanKind.Consumer
)

val traceId: Gen[ByteVector] =
for {
hi <- Gen.long
lo <- Gen.long.suchThat(_ != 0)
} yield SpanContext.TraceId.fromLongs(hi, lo)

val spanId: Gen[ByteVector] =
for {
value <- Gen.long.suchThat(_ != 0)
} yield SpanContext.SpanId.fromLong(value)

val spanContext: Gen[SpanContext] =
for {
traceId <- traceId
spanId <- spanId
traceFlags <- Gen.oneOf(TraceFlags.Sampled, TraceFlags.Default)
remote <- Gen.oneOf(true, false)
} yield SpanContext(traceId, spanId, traceFlags, TraceState.empty, remote)

}
Loading

0 comments on commit 3a39752

Please sign in to comment.