From 7cf98a6043cf90a17b5d0a51cf2399e35239cc0c Mon Sep 17 00:00:00 2001 From: Ivan Topolnjak Date: Wed, 19 Jul 2017 09:35:30 +0200 Subject: tests for Span building and the ExtendedB3 codec, related bugfixes --- kamon-core/src/main/resources/reference.conf | 2 +- kamon-core/src/main/scala/kamon/Kamon.scala | 1 + .../main/scala/kamon/trace/IdentityProvider.scala | 9 +- kamon-core/src/main/scala/kamon/trace/Span.scala | 16 +-- .../main/scala/kamon/trace/SpanContextCodec.scala | 38 ++--- kamon-core/src/main/scala/kamon/trace/Tracer.scala | 18 ++- .../test/scala/kamon/testkit/SpanBuilding.scala | 20 +++ .../test/scala/kamon/testkit/SpanInspector.scala | 52 ++++++- .../trace/ExtendedB3SpanContextCodecSpec.scala | 89 ++++++++++-- .../src/test/scala/kamon/trace/TracerSpec.scala | 154 ++++++++++++++++++++- 10 files changed, 345 insertions(+), 54 deletions(-) create mode 100644 kamon-core/src/test/scala/kamon/testkit/SpanBuilding.scala (limited to 'kamon-core/src') diff --git a/kamon-core/src/main/resources/reference.conf b/kamon-core/src/main/resources/reference.conf index 3e9d7b8d..dd42ab03 100644 --- a/kamon-core/src/main/resources/reference.conf +++ b/kamon-core/src/main/resources/reference.conf @@ -90,7 +90,7 @@ kamon { # and server) of a RPC call, if you are reporting data to such systems then this option should be enabled. # # If you are using Zipkin, keep this option enabled. If you are using Jaeger, disable it. - join-remote-parents-with-same-span-id = true + join-remote-parents-with-same-span-id = no # Configures a sample that decides which traces should be reported to the trace backends. The possible values are: # - always: report all traces. diff --git a/kamon-core/src/main/scala/kamon/Kamon.scala b/kamon-core/src/main/scala/kamon/Kamon.scala index a5df7e5a..fa9e78fe 100644 --- a/kamon-core/src/main/scala/kamon/Kamon.scala +++ b/kamon-core/src/main/scala/kamon/Kamon.scala @@ -55,6 +55,7 @@ object Kamon extends MetricLookup with ReporterRegistry with Tracer { _filters = Filters.fromConfig(config) _metrics.reconfigure(config) _reporters.reconfigure(config) + _tracer.reconfigure(config) _onReconfigureHooks.foreach(hook => { Try(hook.onReconfigure(config)).failed.foreach(error => diff --git a/kamon-core/src/main/scala/kamon/trace/IdentityProvider.scala b/kamon-core/src/main/scala/kamon/trace/IdentityProvider.scala index 0e7d3e7d..3f44629e 100644 --- a/kamon-core/src/main/scala/kamon/trace/IdentityProvider.scala +++ b/kamon-core/src/main/scala/kamon/trace/IdentityProvider.scala @@ -13,7 +13,14 @@ trait IdentityProvider { } object IdentityProvider { - case class Identifier(string: String, bytes: Array[Byte]) + case class Identifier(string: String, bytes: Array[Byte]) { + + override def equals(obj: Any): Boolean = { + if(obj != null && obj.isInstanceOf[Identifier]) + obj.asInstanceOf[Identifier].string == string + else false + } + } val NoIdentifier = Identifier("", new Array[Byte](0)) diff --git a/kamon-core/src/main/scala/kamon/trace/Span.scala b/kamon-core/src/main/scala/kamon/trace/Span.scala index ce09a36e..01cfbfc3 100644 --- a/kamon-core/src/main/scala/kamon/trace/Span.scala +++ b/kamon-core/src/main/scala/kamon/trace/Span.scala @@ -97,20 +97,20 @@ object Span { * * @param spanContext * @param initialOperationName - * @param initialTags + * @param initialSpanTags * @param startTimestampMicros * @param reporterRegistry */ - final class Real(spanContext: SpanContext, initialOperationName: String, initialTags: Map[String, Span.TagValue], - startTimestampMicros: Long, reporterRegistry: ReporterRegistryImpl, tracer: Tracer) extends Span { + final class Real(spanContext: SpanContext, initialOperationName: String, initialSpanTags: Map[String, Span.TagValue], + initialMetricTags: Map[String, String], startTimestampMicros: Long, reporterRegistry: ReporterRegistryImpl, tracer: Tracer) extends Span { private var collectMetrics: Boolean = true private var open: Boolean = true private val sampled: Boolean = spanContext.samplingDecision == SamplingDecision.Sample private var operationName: String = initialOperationName - private var spanTags: Map[String, Span.TagValue] = initialTags - private var customMetricTags = Map.empty[String, String] + private var spanTags: Map[String, Span.TagValue] = initialSpanTags + private var customMetricTags = initialMetricTags private var annotations = List.empty[Span.Annotation] def annotate(annotation: Annotation): Span = synchronized { @@ -201,9 +201,9 @@ object Span { } object Real { - def apply(spanContext: SpanContext, initialOperationName: String, initialTags: Map[String, Span.TagValue], - startTimestampMicros: Long, reporterRegistry: ReporterRegistryImpl, tracer: Tracer): Real = - new Real(spanContext, initialOperationName, initialTags, startTimestampMicros, reporterRegistry, tracer) + def apply(spanContext: SpanContext, initialOperationName: String, initialSpanTags: Map[String, Span.TagValue], + initialMetricTags: Map[String, String], startTimestampMicros: Long, reporterRegistry: ReporterRegistryImpl, tracer: Tracer): Real = + new Real(spanContext, initialOperationName, initialSpanTags, initialMetricTags, startTimestampMicros, reporterRegistry, tracer) } sealed trait TagValue diff --git a/kamon-core/src/main/scala/kamon/trace/SpanContextCodec.scala b/kamon-core/src/main/scala/kamon/trace/SpanContextCodec.scala index 11d6de2c..43b5e8e4 100644 --- a/kamon-core/src/main/scala/kamon/trace/SpanContextCodec.scala +++ b/kamon-core/src/main/scala/kamon/trace/SpanContextCodec.scala @@ -44,9 +44,12 @@ object SpanContextCodec { carrier.put(Headers.TraceIdentifier, urlEncode(spanContext.traceID.string)) carrier.put(Headers.SpanIdentifier, urlEncode(spanContext.spanID.string)) carrier.put(Headers.ParentSpanIdentifier, urlEncode(spanContext.parentID.string)) - carrier.put(Headers.Sampled, encodeSamplingDecision(spanContext.samplingDecision)) carrier.put(Headers.Baggage, encodeBaggage(spanContext.baggage)) + encodeSamplingDecision(spanContext.samplingDecision).foreach { samplingDecision => + carrier.put(Headers.Sampled, samplingDecision) + } + spanContext.baggage.get(Headers.Flags).foreach { flags => carrier.put(Headers.Flags, flags) } @@ -55,11 +58,8 @@ object SpanContextCodec { carrier } - override def inject(spanContext: SpanContext): TextMap = { - val mutableTextMap = TextMap.Default() - inject(spanContext, mutableTextMap) - mutableTextMap - } + override def inject(spanContext: SpanContext): TextMap = + inject(spanContext, TextMap.Default()) override def extract(carrier: TextMap): Option[SpanContext] = { val traceID = carrier.get(Headers.TraceIdentifier) @@ -97,14 +97,16 @@ object SpanContextCodec { if(baggage.getAll().nonEmpty) { val encodedBaggage = new StringBuilder() baggage.getAll().foreach { - case (key, value) if key != Headers.Flags => - if(encodedBaggage.length() > 0) - encodedBaggage.append(';') - - encodedBaggage - .append(urlEncode(key)) - .append('=') - .append(urlEncode(value)) + case (key, value) => + if(key != Headers.Flags) { + if (encodedBaggage.length() > 0) + encodedBaggage.append(';') + + encodedBaggage + .append(urlEncode(key)) + .append('=') + .append(urlEncode(value)) + } } encodedBaggage.toString() @@ -125,10 +127,10 @@ object SpanContextCodec { baggage } - private def encodeSamplingDecision(samplingDecision: SamplingDecision): String = samplingDecision match { - case SamplingDecision.Sample => "1" - case SamplingDecision.DoNotSample => "0" - case SamplingDecision.Unknown => "" + private def encodeSamplingDecision(samplingDecision: SamplingDecision): Option[String] = samplingDecision match { + case SamplingDecision.Sample => Some("1") + case SamplingDecision.DoNotSample => Some("0") + case SamplingDecision.Unknown => None } private def urlEncode(s: String): String = URLEncoder.encode(s, "UTF-8") diff --git a/kamon-core/src/main/scala/kamon/trace/Tracer.scala b/kamon-core/src/main/scala/kamon/trace/Tracer.scala index 47b633ac..714f0215 100644 --- a/kamon-core/src/main/scala/kamon/trace/Tracer.scala +++ b/kamon-core/src/main/scala/kamon/trace/Tracer.scala @@ -148,7 +148,8 @@ object Tracer { final class SpanBuilder(operationName: String, tracer: Tracer.Default, reporterRegistry: ReporterRegistryImpl) { private var parentContext: SpanContext = _ private var startTimestamp = 0L - private var initialTags = Map.empty[String, Span.TagValue] + private var initialSpanTags = Map.empty[String, Span.TagValue] + private var initialMetricTags = Map.empty[String, String] private var useActiveSpanAsParent = true def asChildOf(parentContext: SpanContext): SpanBuilder = { @@ -159,19 +160,24 @@ object Tracer { def asChildOf(parentSpan: Span): SpanBuilder = asChildOf(parentSpan.context()) + def withMetricTag(key: String, value: String): SpanBuilder = { + this.initialMetricTags = this.initialMetricTags + (key -> value) + this + } + def withSpanTag(key: String, value: String): SpanBuilder = { - this.initialTags = this.initialTags + (key -> TagValue.String(value)) + this.initialSpanTags = this.initialSpanTags + (key -> TagValue.String(value)) this } def withSpanTag(key: String, value: Long): SpanBuilder = { - this.initialTags = this.initialTags + (key -> TagValue.Number(value)) + this.initialSpanTags = this.initialSpanTags + (key -> TagValue.Number(value)) this } def withSpanTag(key: String, value: Boolean): SpanBuilder = { val tagValue = if (value) TagValue.True else TagValue.False - this.initialTags = this.initialTags + (key -> tagValue) + this.initialSpanTags = this.initialSpanTags + (key -> tagValue) this } @@ -195,7 +201,7 @@ object Tracer { val samplingDecision: SamplingDecision = parentSpanContext .map(_.samplingDecision) .filter(_ != SamplingDecision.Unknown) - .getOrElse(tracer.sampler.decide(operationName, initialTags)) + .getOrElse(tracer.sampler.decide(operationName, initialSpanTags)) val spanContext = parentSpanContext match { case Some(parent) => joinParentContext(parent, samplingDecision) @@ -203,7 +209,7 @@ object Tracer { } tracer.tracerMetrics.createdSpans.increment() - Span.Real(spanContext, operationName, initialTags, startTimestampMicros, reporterRegistry, tracer) + Span.Real(spanContext, operationName, initialSpanTags, initialMetricTags, startTimestampMicros, reporterRegistry, tracer) } private def joinParentContext(parent: SpanContext, samplingDecision: SamplingDecision): SpanContext = diff --git a/kamon-core/src/test/scala/kamon/testkit/SpanBuilding.scala b/kamon-core/src/test/scala/kamon/testkit/SpanBuilding.scala new file mode 100644 index 00000000..9b845ac9 --- /dev/null +++ b/kamon-core/src/test/scala/kamon/testkit/SpanBuilding.scala @@ -0,0 +1,20 @@ +package kamon.testkit + +import kamon.trace.SpanContext.{SamplingDecision, Source} +import kamon.trace.{IdentityProvider, SpanContext, SpanContextCodec} + +trait SpanBuilding { + private val identityProvider = IdentityProvider.Default() + private val extendedB3Codec = SpanContextCodec.ExtendedB3(identityProvider) + + def createSpanContext(samplingDecision: SamplingDecision = SamplingDecision.Sample): SpanContext = + SpanContext( + traceID = identityProvider.traceIdentifierGenerator().generate(), + spanID = identityProvider.spanIdentifierGenerator().generate(), + parentID = identityProvider.spanIdentifierGenerator().generate(), + samplingDecision = samplingDecision, + baggage = SpanContext.Baggage(), + source = Source.Local + ) + +} diff --git a/kamon-core/src/test/scala/kamon/testkit/SpanInspector.scala b/kamon-core/src/test/scala/kamon/testkit/SpanInspector.scala index b0bb3e39..3ef1012b 100644 --- a/kamon-core/src/test/scala/kamon/testkit/SpanInspector.scala +++ b/kamon-core/src/test/scala/kamon/testkit/SpanInspector.scala @@ -1,15 +1,61 @@ package kamon.testkit -import kamon.trace.Span +import kamon.trace.{ActiveSpan, Span, SpanContext} import kamon.trace.Span.FinishedSpan +import kamon.util.Clock + +import scala.reflect.ClassTag +import scala.util.Try class SpanInspector(span: Span) { + private val (realSpan, spanData) = { + val realSpan = span match { + case _: Span.Real => span + case a: ActiveSpan => + getField[ActiveSpan.Default, Span](a, "wrappedSpan") + } + + val spanData = invoke[Span.Real, FinishedSpan](realSpan, "toFinishedSpan", classOf[Long] -> Long.box(Clock.microTimestamp())) + (realSpan, spanData) + } + + def nonEmpty: Boolean = + !span.isInstanceOf[Span.Empty] + + def spanTag(key: String): Option[Span.TagValue] = + spanData.tags.get(key) + + def spanTags(): Map[String, Span.TagValue] = + spanData.tags + + def metricTags(): Map[String, String] = + getField[Span.Real, Map[String, String]](realSpan, "customMetricTags") + def startTimestamp(): Long = + getField[Span.Real, Long](realSpan, "startTimestampMicros") - private def getSpanData(): Option[FinishedSpan] = { - + def context(): SpanContext = + spanData.context + + def operationName(): String = + spanData.operationName + + + + + private def getField[T, R](target: Any, fieldName: String)(implicit classTag: ClassTag[T]): R = { + val toFinishedSpanMethod = classTag.runtimeClass.getDeclaredField(fieldName) + toFinishedSpanMethod.setAccessible(true) + toFinishedSpanMethod.get(target).asInstanceOf[R] } + private def invoke[T, R](target: Any, fieldName: String, parameters: (Class[_], AnyRef)*)(implicit classTag: ClassTag[T]): R = { + val parameterClasses = parameters.map(_._1) + val parameterInstances = parameters.map(_._2) + val toFinishedSpanMethod = classTag.runtimeClass.getDeclaredMethod(fieldName, parameterClasses: _*) + toFinishedSpanMethod.setAccessible(true) + toFinishedSpanMethod.invoke(target, parameterInstances: _*).asInstanceOf[R] + } } object SpanInspector { diff --git a/kamon-core/src/test/scala/kamon/trace/ExtendedB3SpanContextCodecSpec.scala b/kamon-core/src/test/scala/kamon/trace/ExtendedB3SpanContextCodecSpec.scala index 9491181f..a11aaa4b 100644 --- a/kamon-core/src/test/scala/kamon/trace/ExtendedB3SpanContextCodecSpec.scala +++ b/kamon-core/src/test/scala/kamon/trace/ExtendedB3SpanContextCodecSpec.scala @@ -16,15 +16,19 @@ package kamon.trace +import kamon.testkit.SpanBuilding import kamon.trace.IdentityProvider.Identifier -import kamon.trace.SpanContext.{SamplingDecision, Source} +import kamon.trace.SpanContext.SamplingDecision import org.scalatest.{Matchers, OptionValues, WordSpecLike} -class ExtendedB3SpanContextCodecSpec extends WordSpecLike with Matchers with OptionValues { +class ExtendedB3SpanContextCodecSpec extends WordSpecLike with Matchers with OptionValues with SpanBuilding { + val identityProvider = IdentityProvider.Default() + val extendedB3Codec = SpanContextCodec.ExtendedB3(identityProvider) + "The ExtendedB3 SpanContextCodec" should { "return a TextMap containing the SpanContext data" in { - val context = createSpanContext() + val context = testSpanContext() context.baggage.add("some", "baggage") context.baggage.add("more", "baggage") @@ -37,7 +41,7 @@ class ExtendedB3SpanContextCodecSpec extends WordSpecLike with Matchers with Opt } "allow to provide the TextMap to be used for encoding" in { - val context = createSpanContext() + val context = testSpanContext() context.baggage.add("some", "baggage") context.baggage.add("more", "baggage") @@ -69,6 +73,36 @@ class ExtendedB3SpanContextCodecSpec extends WordSpecLike with Matchers with Opt ) } + "decode the sampling decision based on the X-B3-Sampled header" in { + val sampledTextMap = TextMap.Default() + sampledTextMap.put("X-B3-TraceId", "1234") + sampledTextMap.put("X-B3-SpanId", "4321") + sampledTextMap.put("X-B3-Sampled", "1") + + val notSampledTextMap = TextMap.Default() + notSampledTextMap.put("X-B3-TraceId", "1234") + notSampledTextMap.put("X-B3-SpanId", "4321") + notSampledTextMap.put("X-B3-Sampled", "0") + + val noSamplingTextMap = TextMap.Default() + noSamplingTextMap.put("X-B3-TraceId", "1234") + noSamplingTextMap.put("X-B3-SpanId", "4321") + + extendedB3Codec.extract(sampledTextMap).value.samplingDecision shouldBe SamplingDecision.Sample + extendedB3Codec.extract(notSampledTextMap).value.samplingDecision shouldBe SamplingDecision.DoNotSample + extendedB3Codec.extract(noSamplingTextMap).value.samplingDecision shouldBe SamplingDecision.Unknown + } + + "not include the X-B3-Sampled header if the sampling decision is unknown" in { + val sampledSpanContext = testSpanContext() + val notSampledSpanContext = testSpanContext().copy(samplingDecision = SamplingDecision.DoNotSample) + val unknownSamplingSpanContext = testSpanContext().copy(samplingDecision = SamplingDecision.Unknown) + + extendedB3Codec.inject(sampledSpanContext).get("X-B3-Sampled").value shouldBe("1") + extendedB3Codec.inject(notSampledSpanContext).get("X-B3-Sampled").value shouldBe("0") + extendedB3Codec.inject(unknownSamplingSpanContext).get("X-B3-Sampled") shouldBe empty + } + "use the Debug flag to override the sampling decision, if provided." in { val textMap = TextMap.Default() textMap.put("X-B3-TraceId", "1234") @@ -103,6 +137,26 @@ class ExtendedB3SpanContextCodecSpec extends WordSpecLike with Matchers with Opt spanContext.baggage.getAll() shouldBe empty } + "do not extract a SpanContext if Trace ID and Span ID are not provided" in { + val onlyTraceID = TextMap.Default() + onlyTraceID.put("X-B3-TraceId", "1234") + onlyTraceID.put("X-B3-Sampled", "0") + onlyTraceID.put("X-B3-Flags", "1") + + val onlySpanID = TextMap.Default() + onlySpanID.put("X-B3-SpanId", "4321") + onlySpanID.put("X-B3-Sampled", "0") + onlySpanID.put("X-B3-Flags", "1") + + val noIds = TextMap.Default() + noIds.put("X-B3-Sampled", "0") + noIds.put("X-B3-Flags", "1") + + extendedB3Codec.extract(onlyTraceID) shouldBe empty + extendedB3Codec.extract(onlySpanID) shouldBe empty + extendedB3Codec.extract(noIds) shouldBe empty + } + "round trip a SpanContext from TextMap -> SpanContext -> TextMap" in { val textMap = TextMap.Default() textMap.put("X-B3-TraceId", "1234") @@ -118,7 +172,7 @@ class ExtendedB3SpanContextCodecSpec extends WordSpecLike with Matchers with Opt } "round trip a baggage that has special characters in there" in { - val spanContext = createSpanContext() + val spanContext = testSpanContext() spanContext.baggage.add("key-with-!specials", "value=with~spec;als") val textMap = extendedB3Codec.inject(spanContext) @@ -126,19 +180,26 @@ class ExtendedB3SpanContextCodecSpec extends WordSpecLike with Matchers with Opt extractedSpanContext.baggage.getAll().values.toSeq should contain theSameElementsAs(spanContext.baggage.getAll().values.toSeq) } + "internally carry the X-B3-Flags value so that it can be injected in outgoing requests" in { + val textMap = TextMap.Default() + textMap.put("X-B3-TraceId", "1234") + textMap.put("X-B3-ParentSpanId", "2222") + textMap.put("X-B3-SpanId", "4321") + textMap.put("X-B3-Sampled", "1") + textMap.put("X-B3-Flags", "1") + textMap.put("X-B3-Extra-Baggage", "some=baggage;more=baggage") - } + val spanContext = extendedB3Codec.extract(textMap).value + val injectTextMap = extendedB3Codec.inject(spanContext) - val identityProvider = IdentityProvider.Default() - val extendedB3Codec = SpanContextCodec.ExtendedB3(identityProvider) + injectTextMap.get("X-B3-Flags").value shouldBe("1") + } + } - def createSpanContext(samplingDecision: SamplingDecision = SamplingDecision.Sample): SpanContext = - SpanContext( + def testSpanContext(): SpanContext = + createSpanContext().copy( traceID = Identifier("1234", Array[Byte](1, 2, 3, 4)), spanID = Identifier("4321", Array[Byte](4, 3, 2, 1)), - parentID = Identifier("2222", Array[Byte](2, 2, 2, 2)), - samplingDecision = samplingDecision, - baggage = SpanContext.Baggage(), - source = Source.Local + parentID = Identifier("2222", Array[Byte](2, 2, 2, 2)) ) } \ No newline at end of file diff --git a/kamon-core/src/test/scala/kamon/trace/TracerSpec.scala b/kamon-core/src/test/scala/kamon/trace/TracerSpec.scala index 686c15d0..3e05adb5 100644 --- a/kamon-core/src/test/scala/kamon/trace/TracerSpec.scala +++ b/kamon-core/src/test/scala/kamon/trace/TracerSpec.scala @@ -1,18 +1,166 @@ package kamon.trace +import com.typesafe.config.ConfigFactory import kamon.Kamon -import org.scalatest.{Matchers, WordSpec} +import kamon.testkit.{SpanBuilding, SpanInspector} +import kamon.trace.Span.TagValue +import kamon.trace.SpanContext.Source +import kamon.trace.SpanContextCodec.Format +import org.scalatest.{Matchers, OptionValues, WordSpec} -class TracerSpec extends WordSpec with Matchers { +class TracerSpec extends WordSpec with Matchers with SpanBuilding with OptionValues { "the Kamon tracer" should { - "build spans that contain all information given to the builder" in { + "construct a minimal Span that only has a operation name" in { + val span = tracer.buildSpan("myOperation").start() + val spanData = inspect(span) + + spanData.operationName() shouldBe "myOperation" + spanData.metricTags() shouldBe empty + spanData.spanTags() shouldBe empty + } + + "pass the operation name and tags to started Span" in { val span = tracer.buildSpan("myOperation") + .withMetricTag("metric-tag", "value") + .withMetricTag("metric-tag", "value") .withSpanTag("hello", "world") + .withSpanTag("kamon", "rulez") + .withSpanTag("number", 123) + .withSpanTag("boolean", true) .start() + + val spanData = inspect(span) + spanData.operationName() shouldBe "myOperation" + spanData.metricTags() should contain only ( + ("metric-tag" -> "value")) + + spanData.spanTags() should contain allOf( + ("hello" -> TagValue.String("world")), + ("kamon" -> TagValue.String("rulez")), + ("number" -> TagValue.Number(123)), + ("boolean" -> TagValue.True)) + } + + "do not interfere with the currently active Span if not requested when starting a Span" in { + val previouslyActiveSpan = tracer.activeSpan() + tracer.buildSpan("myOperation").start() + tracer.activeSpan() should be theSameInstanceAs(previouslyActiveSpan) + } + + "make a span active with started with the .startActive() function and restore the previous Span when deactivated" in { + val previouslyActiveSpan = tracer.activeSpan() + val activeSpan = tracer.buildSpan("myOperation").startActive() + + tracer.activeSpan() shouldNot be theSameInstanceAs(previouslyActiveSpan) + val activeSpanData = inspect(activeSpan) + activeSpanData.operationName() shouldBe "myOperation" + + activeSpan.deactivate() + tracer.activeSpan() should be theSameInstanceAs(previouslyActiveSpan) + } + + "not have any parent Span if there is ActiveSpan and no parent was explicitly given" in { + val span = tracer.buildSpan("myOperation").start() + val spanData = inspect(span) + spanData.context().parentID shouldBe IdentityProvider.NoIdentifier + } + + "use the currently active span as parent" in { + val parent = tracer.buildSpan("myOperation").startActive() + val child = tracer.buildSpan("childOperation").asChildOf(parent).start() + parent.deactivate() + + val parentData = inspect(parent) + val childData = inspect(child) + parentData.context().spanID shouldBe childData.context().parentID + } + + "ignore the currently active span as parent if explicitly requested" in { + val parent = tracer.buildSpan("myOperation").startActive() + val child = tracer.buildSpan("childOperation").ignoreActiveSpan().start() + parent.deactivate() + + val childData = inspect(child) + childData.context().parentID shouldBe IdentityProvider.NoIdentifier + } + + "allow overriding the start timestamp for a Span" in { + val span = tracer.buildSpan("myOperation").withStartTimestamp(100).start() + val spanData = inspect(span) + spanData.startTimestamp() shouldBe 100 } + + "inject and extract a SpanContext from a TextMap carrier" in { + val spanContext = createSpanContext() + val injected = Kamon.inject(spanContext, Format.TextMap) + val extractedSpanContext = Kamon.extract(Format.TextMap, injected).value + + spanContext.traceID shouldBe(extractedSpanContext.traceID) + spanContext.spanID shouldBe(extractedSpanContext.spanID) + spanContext.parentID shouldBe(extractedSpanContext.parentID) + spanContext.baggage.getAll() shouldBe(extractedSpanContext.baggage.getAll()) + } + + "inject and extract a SpanContext from a TextMap carrier supplied by the caller" in { + val spanContext = createSpanContext() + val carrier = TextMap.Default() + Kamon.inject(spanContext, Format.TextMap, carrier) + val extractedSpanContext = Kamon.extract(Format.TextMap, carrier).value + + spanContext.traceID shouldBe(extractedSpanContext.traceID) + spanContext.spanID shouldBe(extractedSpanContext.spanID) + spanContext.parentID shouldBe(extractedSpanContext.parentID) + spanContext.baggage.getAll() shouldBe(extractedSpanContext.baggage.getAll()) + } + + "inject and extract a SpanContext from a HttpHeaders carrier" in { + val spanContext = createSpanContext() + val injected = Kamon.inject(spanContext, Format.HttpHeaders) + val extractedSpanContext = Kamon.extract(Format.HttpHeaders, injected).value + + spanContext.traceID shouldBe(extractedSpanContext.traceID) + spanContext.spanID shouldBe(extractedSpanContext.spanID) + spanContext.parentID shouldBe(extractedSpanContext.parentID) + spanContext.baggage.getAll() shouldBe(extractedSpanContext.baggage.getAll()) + } + + "inject and extract a SpanContext from a HttpHeaders using a TextMap provided by the caller" in { + val spanContext = createSpanContext() + val carrier = TextMap.Default() + Kamon.inject(spanContext, Format.HttpHeaders, carrier) + val extractedSpanContext = Kamon.extract(Format.HttpHeaders, carrier).value + + spanContext.traceID shouldBe(extractedSpanContext.traceID) + spanContext.spanID shouldBe(extractedSpanContext.spanID) + spanContext.parentID shouldBe(extractedSpanContext.parentID) + spanContext.baggage.getAll() shouldBe(extractedSpanContext.baggage.getAll()) + } + + + "preserve the same Span and Parent identifier when creating a Span with a remote parent if join-remote-parents-with-same-span-id is enabled" in { + val previousConfig = Kamon.config() + + Kamon.reconfigure { + ConfigFactory.parseString("kamon.trace.join-remote-parents-with-same-span-id = yes") + .withFallback(Kamon.config()) + } + + val remoteParent = createSpanContext().copy(source = Source.Remote) + val childData = inspect(tracer.buildSpan("local").asChildOf(remoteParent).start()) + + childData.context().traceID shouldBe remoteParent.traceID + childData.context().parentID shouldBe remoteParent.parentID + childData.context().spanID shouldBe remoteParent.spanID + + Kamon.reconfigure(previousConfig) + } + } val tracer: Tracer = Kamon + def inspect(span: Span): SpanInspector = + SpanInspector(span) + } -- cgit v1.2.3