Skip to content
Merged
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
6 changes: 5 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,11 @@ lazy val `core-trace` = crossProject(JVMPlatform, JSPlatform, NativePlatform)
ProblemFilters.exclude[MissingClassProblem]("org.typelevel.otel4s.trace.TracerBuilder$MappedK"),
ProblemFilters.exclude[MissingClassProblem]("org.typelevel.otel4s.trace.TracerProvider$MappedK"),
// Span.Backend is a sealed trait
ProblemFilters.exclude[ReversedMissingMethodProblem]("org.typelevel.otel4s.trace.Span#Backend.isRecording")
ProblemFilters.exclude[ReversedMissingMethodProblem]("org.typelevel.otel4s.trace.Span#Backend.isRecording"),
// TraceFlags is a sealed trait
ProblemFilters.exclude[ReversedMissingMethodProblem]("org.typelevel.otel4s.trace.TraceFlags.isTraceIdRandom"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("org.typelevel.otel4s.trace.TraceFlags.withSampled"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("org.typelevel.otel4s.trace.TraceFlags.withRandomTraceId"),
),
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import scodec.bits.ByteVector

/** A span context contains the state that must propagate to child spans and across process boundaries.
*
* It contains the identifiers (a `trace_id` and `span_id`) associated with the span and a set of flags (currently only
* whether the context is sampled or not), as well as the remote flag.
* It contains the identifiers (a `trace_id` and `span_id`) associated with the span and a set of trace flags, as well
* as the remote flag.
*
* @see
* [[https://opentelemetry.io/docs/specs/otel/trace/api/#spancontext]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,18 @@ sealed trait TraceFlags {
*/
def isSampled: Boolean

/** Returns `true` if the random-trace-id bit is on, otherwise `false`.
*/
def isTraceIdRandom: Boolean

/** Returns a [[TraceFlags]] with the sampling bit set or cleared.
*/
def withSampled(value: Boolean): TraceFlags

/** Returns a [[TraceFlags]] with the random-trace-id bit set or cleared.
*/
def withRandomTraceId(value: Boolean): TraceFlags

@threadUnsafe3
override final lazy val hashCode: Int =
Hash[TraceFlags].hash(this)
Expand All @@ -55,13 +67,17 @@ sealed trait TraceFlags {

object TraceFlags {
private val SampledMask: Byte = 0x01
private val RandomTraceIdMask: Byte = 0x02

// The default trace flags (not sampled): with all flag bits off
val Default: TraceFlags = fromByte(0x00)

// The sampled trace flags: the sampled bit is on
val Sampled: TraceFlags = fromByte(SampledMask)

// The random trace id flags: the random-trace-id bit is on
val RandomTraceId: TraceFlags = fromByte(RandomTraceIdMask)

/** Creates the [[TraceFlags]] from the given byte representation.
*/
def fromByte(byte: Byte): TraceFlags =
Expand All @@ -88,6 +104,21 @@ object TraceFlags {
*/
def isSampled: Boolean =
(byte & TraceFlags.SampledMask) == TraceFlags.SampledMask

/** If set, the second least significant bit denotes the trace id was generated randomly.
*/
def isTraceIdRandom: Boolean =
(byte & TraceFlags.RandomTraceIdMask) == TraceFlags.RandomTraceIdMask

def withSampled(value: Boolean): TraceFlags = {
val next = if (value) byte | TraceFlags.SampledMask else byte & ~TraceFlags.SampledMask
TraceFlags.fromByte(next.toByte)
}

def withRandomTraceId(value: Boolean): TraceFlags = {
val next = if (value) byte | TraceFlags.RandomTraceIdMask else byte & ~TraceFlags.RandomTraceIdMask
TraceFlags.fromByte(next.toByte)
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,42 @@ class TraceFlagsSuite extends DisciplineSuite {
test("default instances") {
assertEquals(TraceFlags.Default.toHex, "00")
assertEquals(TraceFlags.Sampled.toHex, "01")
assertEquals(TraceFlags.RandomTraceId.toHex, "02")
}

test("is sampled") {
assertEquals(TraceFlags.fromByte(0xff.toByte).isSampled, true)
assertEquals(TraceFlags.fromByte(0x01).isSampled, true)
assertEquals(TraceFlags.fromByte(0x02).isSampled, false)
assertEquals(TraceFlags.fromByte(0x03).isSampled, true)
assertEquals(TraceFlags.fromByte(0x05).isSampled, true)
assertEquals(TraceFlags.fromByte(0x00).isSampled, false)
}

test("is trace id random") {
assertEquals(TraceFlags.fromByte(0xff.toByte).isTraceIdRandom, true)
assertEquals(TraceFlags.fromByte(0x01).isTraceIdRandom, false)
assertEquals(TraceFlags.fromByte(0x02).isTraceIdRandom, true)
assertEquals(TraceFlags.fromByte(0x03).isTraceIdRandom, true)
assertEquals(TraceFlags.fromByte(0x05).isTraceIdRandom, false)
assertEquals(TraceFlags.fromByte(0x00).isTraceIdRandom, false)
}

test("set and clear sampled bit") {
assertEquals(TraceFlags.Default.withSampled(value = true).toHex, "01")
assertEquals(TraceFlags.fromByte(0x03).withSampled(value = false).toHex, "02")
}

test("set and clear random-trace-id bit") {
assertEquals(TraceFlags.Default.withRandomTraceId(value = true).toHex, "02")
assertEquals(TraceFlags.fromByte(0x03).withRandomTraceId(value = false).toHex, "01")
}

test("bit mutation idempotency") {
assertEquals(TraceFlags.Default.withSampled(value = true).withSampled(value = true).toHex, "01")
assertEquals(TraceFlags.Default.withRandomTraceId(value = true).withRandomTraceId(value = true).toHex, "02")
}

test("create from byte") {
(0 until 256).foreach { i =>
assertEquals(TraceFlags.fromByte(i.toByte).toByte, i.toByte)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ trait Gens extends org.typelevel.otel4s.scalacheck.Gens {
} yield SpanContext.SpanId.fromLong(value)

val traceFlags: Gen[TraceFlags] =
Gen.oneOf(TraceFlags.Sampled, TraceFlags.Default)
Gen.chooseNum(0, 255).map(byte => TraceFlags.fromByte(byte.toByte))

val traceState: Gen[TraceState] =
for {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class SpanContextSuite extends ScalaCheckSuite {
for {
traceId <- traceIdGen
spanId <- spanIdGen
traceFlags <- Gen.oneOf(TraceFlags.Sampled, TraceFlags.Default)
traceFlags <- Gen.chooseNum(0, 255).map(byte => TraceFlags.fromByte(byte.toByte))
remote <- Arbitrary.arbitrary[Boolean]
} yield SpanContext(traceId, spanId, traceFlags, TraceState.empty, remote)

Expand Down