From bfca826392933357046c5f4682cf7b43911b5433 Mon Sep 17 00:00:00 2001 From: Ivan Topolnjak Date: Tue, 18 Jul 2017 13:31:06 +0200 Subject: provide the proper FQCN for default identity providers and codecs --- kamon-core/src/test/scala/kamon/trace/TracerSpec.scala | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 kamon-core/src/test/scala/kamon/trace/TracerSpec.scala (limited to 'kamon-core/src/test/scala/kamon/trace/TracerSpec.scala') diff --git a/kamon-core/src/test/scala/kamon/trace/TracerSpec.scala b/kamon-core/src/test/scala/kamon/trace/TracerSpec.scala new file mode 100644 index 00000000..686c15d0 --- /dev/null +++ b/kamon-core/src/test/scala/kamon/trace/TracerSpec.scala @@ -0,0 +1,18 @@ +package kamon.trace + +import kamon.Kamon +import org.scalatest.{Matchers, WordSpec} + +class TracerSpec extends WordSpec with Matchers { + + "the Kamon tracer" should { + "build spans that contain all information given to the builder" in { + val span = tracer.buildSpan("myOperation") + .withSpanTag("hello", "world") + .start() + } + } + + val tracer: Tracer = Kamon + +} -- cgit v1.2.3 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/test/scala/kamon/trace/TracerSpec.scala') 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 From 0930e36def6ce62c55d30d744b41ef475374a541 Mon Sep 17 00:00:00 2001 From: Ivan Topolnjak Date: Tue, 25 Jul 2017 16:21:13 +0200 Subject: try an alternative approach to active span management --- build.sbt | 1 + kamon-core/src/main/scala/kamon/Kamon.scala | 46 +++---------- .../src/main/scala/kamon/trace/ActiveSpan.scala | 78 ---------------------- .../main/scala/kamon/trace/ActiveSpanSource.scala | 46 +++++++++++++ .../src/main/scala/kamon/trace/Continuation.scala | 39 ----------- kamon-core/src/main/scala/kamon/trace/Span.scala | 14 +--- kamon-core/src/main/scala/kamon/trace/Tracer.scala | 32 +++------ kamon-core/src/main/scala/kamon/util/Mixin.scala | 29 ++++---- .../test/scala/kamon/testkit/SpanInspector.scala | 4 +- .../kamon/trace/ActiveSpanManagementSpec.scala | 40 +++++------ .../src/test/scala/kamon/trace/RealSpanSpec.scala | 10 +-- .../src/test/scala/kamon/trace/TracerSpec.scala | 48 ++++++------- 12 files changed, 131 insertions(+), 256 deletions(-) delete mode 100644 kamon-core/src/main/scala/kamon/trace/ActiveSpan.scala create mode 100644 kamon-core/src/main/scala/kamon/trace/ActiveSpanSource.scala delete mode 100644 kamon-core/src/main/scala/kamon/trace/Continuation.scala (limited to 'kamon-core/src/test/scala/kamon/trace/TracerSpec.scala') diff --git a/build.sbt b/build.sbt index 93331116..37b3abe3 100644 --- a/build.sbt +++ b/build.sbt @@ -16,6 +16,7 @@ scalaVersion := "2.11.8" crossScalaVersions := Seq("2.12.2", "2.11.8", "2.10.6") +concurrentRestrictions in Global += Tags.limit(Tags.Test, 1) lazy val kamon = (project in file(".")) .settings(moduleName := "kamon") diff --git a/kamon-core/src/main/scala/kamon/Kamon.scala b/kamon-core/src/main/scala/kamon/Kamon.scala index fa9e78fe..6de45e25 100644 --- a/kamon-core/src/main/scala/kamon/Kamon.scala +++ b/kamon-core/src/main/scala/kamon/Kamon.scala @@ -17,7 +17,7 @@ package kamon import com.typesafe.config.{Config, ConfigFactory} import kamon.metric._ -import kamon.trace.{ActiveSpan, Span, SpanContext, Tracer, Continuation} +import kamon.trace._ import kamon.util.{Filters, MeasurementUnit, Registration} import scala.concurrent.Future @@ -102,52 +102,28 @@ object Kamon extends MetricLookup with ReporterRegistry with Tracer { override def inject[C](spanContext: SpanContext, format: Format[C]): C = _tracer.inject(spanContext, format) - override def activeSpan(): ActiveSpan = + override def activeSpan(): Span = _tracer.activeSpan() - override def makeActive(span: Span): ActiveSpan = - _tracer.makeActive(span) + override def activate(span: Span): Scope = + _tracer.activate(span) + override def activate(span: Span, finishOnClose: Boolean): Scope = + _tracer.activate(span, finishOnClose) /** * Makes the provided Span active before code is evaluated and deactivates it afterwards. */ - def withSpan[T](span: Span)(code: => T): T = { - val activeSpan = makeActive(span) - val evaluatedCode = code - activeSpan.deactivate() - evaluatedCode - } + def withActiveSpan[T](span: Span)(code: => T): T = { + val scope = activate(span) - /** - * Actives the provided Continuation before code is evaluated and deactivates it afterwards. - */ - def withContinuation[T](continuation: Continuation)(code: => T): T = { - if(continuation == null) + try { code - else { - val activeSpan = continuation.activate() - val evaluatedCode = code - activeSpan.deactivate() - evaluatedCode + } finally { + scope.close() } } - /** - * Captures a continuation from the currently active Span (if any). - */ - def activeSpanContinuation(): Continuation = - activeSpan().capture() - - /** - * Runs the provided closure with the currently active Span (if any). - */ - def onActiveSpan[T](code: ActiveSpan => T): Unit = { - val activeSpan = Kamon.activeSpan() - if(activeSpan != null) - code(activeSpan) - } - override def loadReportersFromConfig(): Unit = _reporters.loadReportersFromConfig() diff --git a/kamon-core/src/main/scala/kamon/trace/ActiveSpan.scala b/kamon-core/src/main/scala/kamon/trace/ActiveSpan.scala deleted file mode 100644 index b6e5d5e9..00000000 --- a/kamon-core/src/main/scala/kamon/trace/ActiveSpan.scala +++ /dev/null @@ -1,78 +0,0 @@ -package kamon.trace - -/** - * Wraps a [[kamon.trace.Span]] that has been activated in the current Thread. By activated we really mean, it is - * stored in a ThreadLocal value inside the tracer until [[kamon.trace.ActiveSpan#deactivate()]] is called. - * - * When a [[kamon.trace.Span]] is activated it will keep a reference to the previously active Span on the current - * Thread, take it's place as the currently active Span and put the original one once this ActiveSpan gets deactivated. - * - */ -trait ActiveSpan extends Span { - - /** - * Sets the currently active Span to whatever Span was active when this Span was activated. - * - */ - def deactivate(): Span -} - -object ActiveSpan { - - final class Default(wrappedSpan: Span, restoreOnDeactivate: ActiveSpan, tl: ThreadLocal[ActiveSpan]) - extends ActiveSpan { - - override def deactivate(): Span = { - tl.set(restoreOnDeactivate) - wrappedSpan - } - - // - // Forward all other members to the wrapped Span. - // - - override def annotate(annotation: Span.Annotation): Span = - wrappedSpan.annotate(annotation) - - override def addSpanTag(key: String, value: String): Span = - wrappedSpan.addSpanTag(key, value) - - override def addSpanTag(key: String, value: Long): Span = - wrappedSpan.addSpanTag(key, value) - - override def addSpanTag(key: String, value: Boolean): Span = - wrappedSpan.addSpanTag(key, value) - - override def addMetricTag(key: String, value: String): Span = - wrappedSpan.addMetricTag(key, value) - - override def addBaggage(key: String, value: String): Span = - wrappedSpan.addBaggage(key, value) - - override def getBaggage(key: String): Option[String] = - wrappedSpan.getBaggage(key) - - override def disableMetricsCollection(): Span = - wrappedSpan.disableMetricsCollection() - - override def context(): SpanContext = - wrappedSpan.context() - - override def setOperationName(operationName: String): Span = - wrappedSpan.setOperationName(operationName) - - override def finish(finishMicros: Long): Unit = - wrappedSpan.finish(finishMicros) - - override def capture(): Continuation = - wrappedSpan.capture() - - override def capture(activeSpanSource: ActiveSpanSource): Continuation = - wrappedSpan.capture(activeSpanSource) - } - - object Default { - def apply(wrappedSpan: Span, restoreOnDeactivate: ActiveSpan, tl: ThreadLocal[ActiveSpan]): Default = - new Default(wrappedSpan, restoreOnDeactivate, tl) - } -} \ No newline at end of file diff --git a/kamon-core/src/main/scala/kamon/trace/ActiveSpanSource.scala b/kamon-core/src/main/scala/kamon/trace/ActiveSpanSource.scala new file mode 100644 index 00000000..f4a363a6 --- /dev/null +++ b/kamon-core/src/main/scala/kamon/trace/ActiveSpanSource.scala @@ -0,0 +1,46 @@ +package kamon.trace + + +trait Scope extends AutoCloseable { + def close(): Unit +} + +trait ActiveSpanSource { + def activeSpan(): Span + + def activate(span: Span): Scope + def activate(span: Span, finishOnClose: Boolean): Scope +} + +object ActiveSpanSource { + + final class ThreadLocalBased extends ActiveSpanSource { + private val emptySpan = Span.Empty(this) + private val storage: ThreadLocal[Span] = new ThreadLocal[Span] { + override def initialValue(): Span = emptySpan + } + + override def activeSpan(): Span = + storage.get() + + override def activate(span: Span): Scope = + activate(span, finishOnClose = false) + + override def activate(span: Span, finishOnClose: Boolean): Scope = { + val previouslyActiveSpan = storage.get() + storage.set(span) + + new Scope { + override def close(): Unit = { + storage.set(previouslyActiveSpan) + if (finishOnClose && span != null) + span.finish() + } + } + } + } + + object ThreadLocalBased { + def apply(): ThreadLocalBased = new ThreadLocalBased() + } +} \ No newline at end of file diff --git a/kamon-core/src/main/scala/kamon/trace/Continuation.scala b/kamon-core/src/main/scala/kamon/trace/Continuation.scala deleted file mode 100644 index 8029b838..00000000 --- a/kamon-core/src/main/scala/kamon/trace/Continuation.scala +++ /dev/null @@ -1,39 +0,0 @@ -/* ========================================================================================= - * Copyright © 2013-2017 the kamon project - * - * 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 kamon.trace - - - -trait Continuation { - def activate(): ActiveSpan -} - -object Continuation { - - /** - * - * @param span - * @param activeSpanSource - */ - final class Default(span: Span, activeSpanSource: ActiveSpanSource) extends Continuation { - override def activate(): ActiveSpan = - activeSpanSource.makeActive(span) - } - - object Default { - def apply(span: Span, activeSpanSource: ActiveSpanSource): Default = new Default(span, activeSpanSource) - } -} diff --git a/kamon-core/src/main/scala/kamon/trace/Span.scala b/kamon-core/src/main/scala/kamon/trace/Span.scala index 113ec3de..6b38ae48 100644 --- a/kamon-core/src/main/scala/kamon/trace/Span.scala +++ b/kamon-core/src/main/scala/kamon/trace/Span.scala @@ -29,10 +29,6 @@ trait BaseSpan { def context(): SpanContext - def capture(): Continuation - - def capture(activeSpanSource: ActiveSpanSource): Continuation - def annotate(annotation: Span.Annotation): Span def addSpanTag(key: String, value: String): Span @@ -77,8 +73,6 @@ object Span { final class Empty(activeSpanSource: ActiveSpanSource) extends Span { override val context: SpanContext = SpanContext.EmptySpanContext - override def capture(): Continuation = Continuation.Default(this, activeSpanSource) - override def capture(activeSpanSource: ActiveSpanSource): Continuation = Continuation.Default(this, activeSpanSource) override def annotate(annotation: Annotation): Span = this override def addSpanTag(key: String, value: String): Span = this @@ -93,7 +87,7 @@ object Span { } object Empty { - def apply(tracer: Tracer): Empty = new Empty(tracer) + def apply(activeSpanSource: ActiveSpanSource): Empty = new Empty(activeSpanSource) } /** @@ -182,12 +176,6 @@ object Span { } } - override def capture(): Continuation = - Continuation.Default(this, activeSpanSource) - - override def capture(activeSpanSource: ActiveSpanSource): Continuation = - Continuation.Default(this, activeSpanSource) - private def toFinishedSpan(endTimestampMicros: Long): Span.FinishedSpan = Span.FinishedSpan(spanContext, operationName, startTimestampMicros, endTimestampMicros, spanTags, annotations) diff --git a/kamon-core/src/main/scala/kamon/trace/Tracer.scala b/kamon-core/src/main/scala/kamon/trace/Tracer.scala index 71201871..737a8b8d 100644 --- a/kamon-core/src/main/scala/kamon/trace/Tracer.scala +++ b/kamon-core/src/main/scala/kamon/trace/Tracer.scala @@ -13,7 +13,6 @@ * ========================================================================================= */ - package kamon.trace import java.nio.ByteBuffer @@ -30,13 +29,7 @@ import org.slf4j.LoggerFactory import scala.collection.immutable import scala.util.Try - -trait ActiveSpanSource { - def activeSpan(): ActiveSpan - def makeActive(span: Span): ActiveSpan -} - -trait Tracer extends ActiveSpanSource{ +trait Tracer extends ActiveSpanSource { def buildSpan(operationName: String): SpanBuilder def extract[C](format: SpanContextCodec.Format[C], carrier: C): Option[SpanContext] @@ -48,10 +41,7 @@ object Tracer { final class Default(metrics: MetricLookup, reporterRegistry: ReporterRegistryImpl, initialConfig: Config) extends Tracer { private val logger = LoggerFactory.getLogger(classOf[Tracer]) - private val emptySpan = Span.Empty(this) - private val activeSpanStorage: ThreadLocal[ActiveSpan] = new ThreadLocal[ActiveSpan] { - override def initialValue(): ActiveSpan = ActiveSpan.Default(emptySpan, null, activeSpanStorage) - } + private val activeSpanSource = ActiveSpanSource.ThreadLocalBased() private[Tracer] val tracerMetrics = new TracerMetrics(metrics) @volatile private[Tracer] var joinRemoteParentsWithSameSpanID: Boolean = true @@ -83,15 +73,14 @@ object Tracer { case SpanContextCodec.Format.Binary => ByteBuffer.allocate(0) // TODO: Implement binary encoding. } - override def activeSpan(): ActiveSpan = - activeSpanStorage.get() + override def activeSpan(): Span = + activeSpanSource.activeSpan() - override def makeActive(span: Span): ActiveSpan = { - val currentlyActiveSpan = activeSpanStorage.get() - val newActiveSpan = ActiveSpan.Default(span, currentlyActiveSpan, activeSpanStorage) - activeSpanStorage.set(newActiveSpan) - newActiveSpan - } + override def activate(span: Span): Scope = + activeSpanSource.activate(span) + + override def activate(span: Span, finishOnClose: Boolean): Scope = + activeSpanSource.activate(span, finishOnClose) def sampler: Sampler = configuredSampler @@ -232,9 +221,6 @@ object Tracer { baggage = SpanContext.Baggage(), source = Source.Local ) - - def startActive(): ActiveSpan = - tracer.makeActive(start()) } private final class TracerMetrics(metricLookup: MetricLookup) { diff --git a/kamon-core/src/main/scala/kamon/util/Mixin.scala b/kamon-core/src/main/scala/kamon/util/Mixin.scala index 318679c1..2fd7be24 100644 --- a/kamon-core/src/main/scala/kamon/util/Mixin.scala +++ b/kamon-core/src/main/scala/kamon/util/Mixin.scala @@ -16,30 +16,31 @@ package kamon package util -import kamon.trace.{ActiveSpan, Continuation} +import kamon.trace.Span /** - * Utility trait that marks objects carrying an ActiveSpan.Continuation. + * Utility trait that marks objects carrying a reference to a Span. + * */ -trait HasContinuation { - def continuation: Continuation +trait HasSpan { + def span: Span } -object HasContinuation { - private class Default(val continuation: Continuation) extends HasContinuation +object HasSpan { + private case class Default(span: Span) extends HasSpan /** - * Construct a HasContinuation instance by capturing a continuation from the provided active span. + * Construct a HasSpan instance that references the provided Span. + * */ - def from(activeSpan: ActiveSpan): HasContinuation = { - val continuation = if(activeSpan == null) null else activeSpan.capture() - new Default(continuation) - } + def from(span: Span): HasSpan = + Default(span) /** - * Constructs a new HasContinuation instance using Kamon's tracer currently active span. + * Construct a HasSpan instance that references the currently ActiveSpan in Kamon's tracer. + * */ - def fromTracerActiveSpan(): HasContinuation = - new Default(Kamon.activeSpanContinuation()) + def fromActiveSpan(): HasSpan = + Default(Kamon.activeSpan()) } diff --git a/kamon-core/src/test/scala/kamon/testkit/SpanInspector.scala b/kamon-core/src/test/scala/kamon/testkit/SpanInspector.scala index e00c8b26..ab58e446 100644 --- a/kamon-core/src/test/scala/kamon/testkit/SpanInspector.scala +++ b/kamon-core/src/test/scala/kamon/testkit/SpanInspector.scala @@ -1,6 +1,6 @@ package kamon.testkit -import kamon.trace.{ActiveSpan, Span, SpanContext} +import kamon.trace.{Span, SpanContext} import kamon.trace.Span.FinishedSpan import kamon.util.Clock @@ -11,8 +11,6 @@ class SpanInspector(span: Span) { private val (realSpan, spanData) = Try { 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())) diff --git a/kamon-core/src/test/scala/kamon/trace/ActiveSpanManagementSpec.scala b/kamon-core/src/test/scala/kamon/trace/ActiveSpanManagementSpec.scala index ebee9f66..a6a7bc3a 100644 --- a/kamon-core/src/test/scala/kamon/trace/ActiveSpanManagementSpec.scala +++ b/kamon-core/src/test/scala/kamon/trace/ActiveSpanManagementSpec.scala @@ -9,16 +9,16 @@ import org.scalatest.{Matchers, WordSpec} class ActiveSpanManagementSpec extends WordSpec with Matchers { "Kamon acting as a ActiveSpanSource" should { - "return a ActiveSpan wrapping a empty span when there is no currently active Span" in { + "return a empty span when there is no currently active Span" in { inspect(Kamon.activeSpan()) shouldBe empty } - "safely operate on a ActiveSpan that wraps a empty Span" in { - val activeSpan = Kamon.activeSpan() + "safely operate on a empty Span" in { + val emptySpan = Kamon.activeSpan() val activeSpanData = inspect(Kamon.activeSpan()) activeSpanData shouldBe empty - activeSpan + emptySpan .setOperationName("test") .addBaggage("key", "value") .addMetricTag("key", "value") @@ -28,39 +28,33 @@ class ActiveSpanManagementSpec extends WordSpec with Matchers { .addSpanTag("boolean-false", false) .annotate(Annotation(Clock.microTimestamp(), "event", Map("k" -> "v"))) - val baggage = activeSpan.context().baggage + val baggage = emptySpan.context().baggage baggage.add("key", "value") baggage.get("key") shouldBe empty baggage.getAll() shouldBe empty - val continuation = activeSpan.capture() - val activatedSpan = continuation.activate() - inspect(Kamon.activeSpan()) shouldBe empty - activatedSpan.deactivate() + Kamon.withActiveSpan(emptySpan) { + inspect(Kamon.activeSpan()) shouldBe empty + } inspect(Kamon.activeSpan()) shouldBe empty } - "set a Span as active when using makeActive" in { + "set a Span as active when using activate" in { val span = Kamon.buildSpan("mySpan").start() - val activeSpan = Kamon.makeActive(span) - Kamon.activeSpan() shouldBe theSameInstanceAs(activeSpan) - activeSpan.deactivate() - } - - "set a Span as active when using startActive" in { - val activeSpan = Kamon.buildSpan("mySpan").startActive() - Kamon.activeSpan() shouldBe theSameInstanceAs(activeSpan) - activeSpan.deactivate() + val scope = Kamon.activate(span) + Kamon.activeSpan() shouldBe theSameInstanceAs(span) + scope.close() } - "restore the previously active Span when a ActiveSpan gets deactivated" in { + "restore the previously active Span when a scope is closed" in { val previouslyActiveSpan = Kamon.activeSpan() inspect(Kamon.activeSpan()) shouldBe empty - val activeSpan = Kamon.buildSpan("mySpan").startActive() - Kamon.activeSpan() shouldBe theSameInstanceAs(activeSpan) - activeSpan.deactivate() + val span = Kamon.buildSpan("mySpan").start() + Kamon.withActiveSpan(span) { + Kamon.activeSpan() shouldBe theSameInstanceAs(span) + } Kamon.activeSpan() shouldBe theSameInstanceAs(previouslyActiveSpan) } diff --git a/kamon-core/src/test/scala/kamon/trace/RealSpanSpec.scala b/kamon-core/src/test/scala/kamon/trace/RealSpanSpec.scala index 150bf4c7..e019e15a 100644 --- a/kamon-core/src/test/scala/kamon/trace/RealSpanSpec.scala +++ b/kamon-core/src/test/scala/kamon/trace/RealSpanSpec.scala @@ -88,16 +88,16 @@ class RealSpanSpec extends WordSpec with Matchers with BeforeAndAfterAll with Ev } } - "pass all the tags, annotations and baggage to the FinishedSpan instance when started active and finished" in { - val activeSpan = Kamon.buildSpan("full-span") + "pass all the tags, annotations and baggage to the FinishedSpan instance when started, activated and finished" in { + val scope = Kamon.activate(Kamon.buildSpan("full-span") .withSpanTag("builder-string-tag", "value") .withSpanTag("builder-boolean-tag-true", true) .withSpanTag("builder-boolean-tag-false", false) .withSpanTag("builder-number-tag", 42) .withStartTimestamp(100) - .startActive() + .start()) - activeSpan + Kamon.activeSpan() .addBaggage("baggage", "value") .addSpanTag("span-string-tag", "value") .addSpanTag("span-boolean-tag-true", true) @@ -110,7 +110,7 @@ class RealSpanSpec extends WordSpec with Matchers with BeforeAndAfterAll with Ev .setOperationName("fully-populated-active-span") .finish(200) - activeSpan.deactivate() + scope.close() eventually(timeout(2 seconds)) { val finishedSpan = reporter.nextSpan().value diff --git a/kamon-core/src/test/scala/kamon/trace/TracerSpec.scala b/kamon-core/src/test/scala/kamon/trace/TracerSpec.scala index 3e05adb5..5abfe723 100644 --- a/kamon-core/src/test/scala/kamon/trace/TracerSpec.scala +++ b/kamon-core/src/test/scala/kamon/trace/TracerSpec.scala @@ -42,23 +42,23 @@ class TracerSpec extends WordSpec with Matchers with SpanBuilding with OptionVal ("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) - } +// "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() @@ -67,9 +67,10 @@ class TracerSpec extends WordSpec with Matchers with SpanBuilding with OptionVal } "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 parent = tracer.buildSpan("myOperation").start() + val child = Kamon.withActiveSpan(parent) { + tracer.buildSpan("childOperation").asChildOf(parent).start() + } val parentData = inspect(parent) val childData = inspect(child) @@ -77,9 +78,10 @@ class TracerSpec extends WordSpec with Matchers with SpanBuilding with OptionVal } "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 parent = tracer.buildSpan("myOperation").start() + val child = Kamon.withActiveSpan(parent) { + tracer.buildSpan("childOperation").ignoreActiveSpan().start() + } val childData = inspect(child) childData.context().parentID shouldBe IdentityProvider.NoIdentifier -- cgit v1.2.3 From 3a8c0fa25f12230b27e943d1fffe07f814c650fe Mon Sep 17 00:00:00 2001 From: Ivan Topolnjak Date: Mon, 14 Aug 2017 17:30:16 +0200 Subject: implement Span propagation on top of Kamon.Context --- kamon-core/src/main/colfer/context.colf | 10 + .../main/java/kamon/context/encoding/Context.java | 359 +++++++++++++++++ .../main/java/kamon/context/encoding/Entry.java | 442 +++++++++++++++++++++ kamon-core/src/main/resources/reference.conf | 13 + kamon-core/src/main/scala/kamon/Kamon.scala | 31 +- .../src/main/scala/kamon/context/Codec.scala | 132 ++++++ .../src/main/scala/kamon/context/Context.scala | 127 +----- .../src/main/scala/kamon/context/Mixin.scala | 43 ++ .../src/main/scala/kamon/context/Storage.scala | 39 ++ .../main/scala/kamon/trace/ActiveSpanStorage.scala | 74 ---- kamon-core/src/main/scala/kamon/trace/Span.scala | 78 ++-- .../src/main/scala/kamon/trace/SpanContext.scala | 15 +- .../main/scala/kamon/trace/SpanContextCodec.scala | 105 +---- kamon-core/src/main/scala/kamon/trace/Tracer.scala | 95 +---- kamon-core/src/main/scala/kamon/util/Mixin.scala | 46 --- .../scala/kamon/context/ContextCodecSpec.scala | 18 + .../test/scala/kamon/testkit/SpanBuilding.scala | 6 +- .../test/scala/kamon/testkit/SpanInspector.scala | 8 +- .../kamon/trace/ActiveSpanManagementSpec.scala | 65 --- .../trace/ExtendedB3SpanContextCodecSpec.scala | 103 ++--- .../src/test/scala/kamon/trace/LocalSpanSpec.scala | 100 +++++ .../src/test/scala/kamon/trace/RealSpanSpec.scala | 180 --------- .../src/test/scala/kamon/trace/TracerSpec.scala | 83 +--- 23 files changed, 1314 insertions(+), 858 deletions(-) create mode 100644 kamon-core/src/main/colfer/context.colf create mode 100644 kamon-core/src/main/java/kamon/context/encoding/Context.java create mode 100644 kamon-core/src/main/java/kamon/context/encoding/Entry.java create mode 100644 kamon-core/src/main/scala/kamon/context/Codec.scala create mode 100644 kamon-core/src/main/scala/kamon/context/Mixin.scala create mode 100644 kamon-core/src/main/scala/kamon/context/Storage.scala delete mode 100644 kamon-core/src/main/scala/kamon/trace/ActiveSpanStorage.scala delete mode 100644 kamon-core/src/main/scala/kamon/util/Mixin.scala create mode 100644 kamon-core/src/test/scala/kamon/context/ContextCodecSpec.scala delete mode 100644 kamon-core/src/test/scala/kamon/trace/ActiveSpanManagementSpec.scala create mode 100644 kamon-core/src/test/scala/kamon/trace/LocalSpanSpec.scala delete mode 100644 kamon-core/src/test/scala/kamon/trace/RealSpanSpec.scala (limited to 'kamon-core/src/test/scala/kamon/trace/TracerSpec.scala') diff --git a/kamon-core/src/main/colfer/context.colf b/kamon-core/src/main/colfer/context.colf new file mode 100644 index 00000000..26421cba --- /dev/null +++ b/kamon-core/src/main/colfer/context.colf @@ -0,0 +1,10 @@ +package kamon + +type Entry struct { + name text + content binary +} + +type Context struct { + entries []Entry +} \ No newline at end of file diff --git a/kamon-core/src/main/java/kamon/context/encoding/Context.java b/kamon-core/src/main/java/kamon/context/encoding/Context.java new file mode 100644 index 00000000..db6ed7a9 --- /dev/null +++ b/kamon-core/src/main/java/kamon/context/encoding/Context.java @@ -0,0 +1,359 @@ +package kamon.context.encoding; + + +// Code generated by colf(1); DO NOT EDIT. + + +import static java.lang.String.format; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamException; +import java.io.OutputStream; +import java.io.Serializable; +import java.util.InputMismatchException; +import java.nio.BufferOverflowException; +import java.nio.BufferUnderflowException; + + +/** + * Data bean with built-in serialization support. + + * @author generated by colf(1) + * @see Colfer's home + */ +@javax.annotation.Generated(value="colf(1)", comments="Colfer from schema file context.colf") +public class Context implements Serializable { + + /** The upper limit for serial byte sizes. */ + public static int colferSizeMax = 16 * 1024 * 1024; + + /** The upper limit for the number of elements in a list. */ + public static int colferListMax = 64 * 1024; + + + + + public Entry[] entries; + + + /** Default constructor */ + public Context() { + init(); + } + + private static final Entry[] _zeroEntries = new Entry[0]; + + /** Colfer zero values. */ + private void init() { + entries = _zeroEntries; + } + + /** + * {@link #reset(InputStream) Reusable} deserialization of Colfer streams. + */ + public static class Unmarshaller { + + /** The data source. */ + protected InputStream in; + + /** The read buffer. */ + public byte[] buf; + + /** The {@link #buf buffer}'s data start index, inclusive. */ + protected int offset; + + /** The {@link #buf buffer}'s data end index, exclusive. */ + protected int i; + + + /** + * @param in the data source or {@code null}. + * @param buf the initial buffer or {@code null}. + */ + public Unmarshaller(InputStream in, byte[] buf) { + // TODO: better size estimation + if (buf == null || buf.length == 0) + buf = new byte[Math.min(Context.colferSizeMax, 2048)]; + this.buf = buf; + reset(in); + } + + /** + * Reuses the marshaller. + * @param in the data source or {@code null}. + * @throws IllegalStateException on pending data. + */ + public void reset(InputStream in) { + if (this.i != this.offset) throw new IllegalStateException("colfer: pending data"); + this.in = in; + this.offset = 0; + this.i = 0; + } + + /** + * Deserializes the following object. + * @return the result or {@code null} when EOF. + * @throws IOException from the input stream. + * @throws SecurityException on an upper limit breach defined by either {@link #colferSizeMax} or {@link #colferListMax}. + * @throws InputMismatchException when the data does not match this object's schema. + */ + public Context next() throws IOException { + if (in == null) return null; + + while (true) { + if (this.i > this.offset) { + try { + Context o = new Context(); + this.offset = o.unmarshal(this.buf, this.offset, this.i); + return o; + } catch (BufferUnderflowException e) { + } + } + // not enough data + + if (this.i <= this.offset) { + this.offset = 0; + this.i = 0; + } else if (i == buf.length) { + byte[] src = this.buf; + // TODO: better size estimation + if (offset == 0) this.buf = new byte[Math.min(Context.colferSizeMax, this.buf.length * 4)]; + System.arraycopy(src, this.offset, this.buf, 0, this.i - this.offset); + this.i -= this.offset; + this.offset = 0; + } + assert this.i < this.buf.length; + + int n = in.read(buf, i, buf.length - i); + if (n < 0) { + if (this.i > this.offset) + throw new InputMismatchException("colfer: pending data with EOF"); + return null; + } + assert n > 0; + i += n; + } + } + + } + + + /** + * Serializes the object. + * All {@code null} elements in {@link #entries} will be replaced with a {@code new} value. + * @param out the data destination. + * @param buf the initial buffer or {@code null}. + * @return the final buffer. When the serial fits into {@code buf} then the return is {@code buf}. + * Otherwise the return is a new buffer, large enough to hold the whole serial. + * @throws IOException from {@code out}. + * @throws IllegalStateException on an upper limit breach defined by either {@link #colferSizeMax} or {@link #colferListMax}. + */ + public byte[] marshal(OutputStream out, byte[] buf) throws IOException { + // TODO: better size estimation + if (buf == null || buf.length == 0) + buf = new byte[Math.min(Context.colferSizeMax, 2048)]; + + while (true) { + int i; + try { + i = marshal(buf, 0); + } catch (BufferOverflowException e) { + buf = new byte[Math.min(Context.colferSizeMax, buf.length * 4)]; + continue; + } + + out.write(buf, 0, i); + return buf; + } + } + + /** + * Serializes the object. + * All {@code null} elements in {@link #entries} will be replaced with a {@code new} value. + * @param buf the data destination. + * @param offset the initial index for {@code buf}, inclusive. + * @return the final index for {@code buf}, exclusive. + * @throws BufferOverflowException when {@code buf} is too small. + * @throws IllegalStateException on an upper limit breach defined by either {@link #colferSizeMax} or {@link #colferListMax}. + */ + public int marshal(byte[] buf, int offset) { + int i = offset; + + try { + if (this.entries.length != 0) { + buf[i++] = (byte) 0; + Entry[] a = this.entries; + + int x = a.length; + if (x > Context.colferListMax) + throw new IllegalStateException(format("colfer: kamon/context/kamon.Context.entries length %d exceeds %d elements", x, Context.colferListMax)); + while (x > 0x7f) { + buf[i++] = (byte) (x | 0x80); + x >>>= 7; + } + buf[i++] = (byte) x; + + for (int ai = 0; ai < a.length; ai++) { + Entry o = a[ai]; + if (o == null) { + o = new Entry(); + a[ai] = o; + } + i = o.marshal(buf, i); + } + } + + buf[i++] = (byte) 0x7f; + return i; + } catch (ArrayIndexOutOfBoundsException e) { + if (i - offset > Context.colferSizeMax) + throw new IllegalStateException(format("colfer: kamon/context/kamon.Context exceeds %d bytes", Context.colferSizeMax)); + if (i > buf.length) throw new BufferOverflowException(); + throw e; + } + } + + /** + * Deserializes the object. + * @param buf the data source. + * @param offset the initial index for {@code buf}, inclusive. + * @return the final index for {@code buf}, exclusive. + * @throws BufferUnderflowException when {@code buf} is incomplete. (EOF) + * @throws SecurityException on an upper limit breach defined by either {@link #colferSizeMax} or {@link #colferListMax}. + * @throws InputMismatchException when the data does not match this object's schema. + */ + public int unmarshal(byte[] buf, int offset) { + return unmarshal(buf, offset, buf.length); + } + + /** + * Deserializes the object. + * @param buf the data source. + * @param offset the initial index for {@code buf}, inclusive. + * @param end the index limit for {@code buf}, exclusive. + * @return the final index for {@code buf}, exclusive. + * @throws BufferUnderflowException when {@code buf} is incomplete. (EOF) + * @throws SecurityException on an upper limit breach defined by either {@link #colferSizeMax} or {@link #colferListMax}. + * @throws InputMismatchException when the data does not match this object's schema. + */ + public int unmarshal(byte[] buf, int offset, int end) { + if (end > buf.length) end = buf.length; + int i = offset; + + try { + byte header = buf[i++]; + + if (header == (byte) 0) { + int length = 0; + for (int shift = 0; true; shift += 7) { + byte b = buf[i++]; + length |= (b & 0x7f) << shift; + if (shift == 28 || b >= 0) break; + } + if (length < 0 || length > Context.colferListMax) + throw new SecurityException(format("colfer: kamon/context/kamon.Context.entries length %d exceeds %d elements", length, Context.colferListMax)); + + Entry[] a = new Entry[length]; + for (int ai = 0; ai < length; ai++) { + Entry o = new Entry(); + i = o.unmarshal(buf, i, end); + a[ai] = o; + } + this.entries = a; + header = buf[i++]; + } + + if (header != (byte) 0x7f) + throw new InputMismatchException(format("colfer: unknown header at byte %d", i - 1)); + } finally { + if (i > end && end - offset < Context.colferSizeMax) throw new BufferUnderflowException(); + if (i < 0 || i - offset > Context.colferSizeMax) + throw new SecurityException(format("colfer: kamon/context/kamon.Context exceeds %d bytes", Context.colferSizeMax)); + if (i > end) throw new BufferUnderflowException(); + } + + return i; + } + + // {@link Serializable} version number. + private static final long serialVersionUID = 1L; + + // {@link Serializable} Colfer extension. + private void writeObject(ObjectOutputStream out) throws IOException { + // TODO: better size estimation + byte[] buf = new byte[1024]; + int n; + while (true) try { + n = marshal(buf, 0); + break; + } catch (BufferUnderflowException e) { + buf = new byte[4 * buf.length]; + } + + out.writeInt(n); + out.write(buf, 0, n); + } + + // {@link Serializable} Colfer extension. + private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException { + init(); + + int n = in.readInt(); + byte[] buf = new byte[n]; + in.readFully(buf); + unmarshal(buf, 0); + } + + // {@link Serializable} Colfer extension. + private void readObjectNoData() throws ObjectStreamException { + init(); + } + + /** + * Gets kamon/context/kamon.Context.entries. + * @return the value. + */ + public Entry[] getEntries() { + return this.entries; + } + + /** + * Sets kamon/context/kamon.Context.entries. + * @param value the replacement. + */ + public void setEntries(Entry[] value) { + this.entries = value; + } + + /** + * Sets kamon/context/kamon.Context.entries. + * @param value the replacement. + * @return {link this}. + */ + public Context withEntries(Entry[] value) { + this.entries = value; + return this; + } + + @Override + public final int hashCode() { + int h = 1; + for (Entry o : this.entries) h = 31 * h + (o == null ? 0 : o.hashCode()); + return h; + } + + @Override + public final boolean equals(Object o) { + return o instanceof Context && equals((Context) o); + } + + public final boolean equals(Context o) { + if (o == null) return false; + if (o == this) return true; + return o.getClass() == Context.class + && java.util.Arrays.equals(this.entries, o.entries); + } + +} diff --git a/kamon-core/src/main/java/kamon/context/encoding/Entry.java b/kamon-core/src/main/java/kamon/context/encoding/Entry.java new file mode 100644 index 00000000..d7734c13 --- /dev/null +++ b/kamon-core/src/main/java/kamon/context/encoding/Entry.java @@ -0,0 +1,442 @@ +package kamon.context.encoding; + + +// Code generated by colf(1); DO NOT EDIT. + + +import static java.lang.String.format; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamException; +import java.io.OutputStream; +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import java.util.InputMismatchException; +import java.nio.BufferOverflowException; +import java.nio.BufferUnderflowException; + + +/** + * Data bean with built-in serialization support. + + * @author generated by colf(1) + * @see Colfer's home + */ +@javax.annotation.Generated(value="colf(1)", comments="Colfer from schema file context.colf") +public class Entry implements Serializable { + + /** The upper limit for serial byte sizes. */ + public static int colferSizeMax = 16 * 1024 * 1024; + + + + + public String name; + + public byte[] content; + + + /** Default constructor */ + public Entry() { + init(); + } + + private static final byte[] _zeroBytes = new byte[0]; + + /** Colfer zero values. */ + private void init() { + name = ""; + content = _zeroBytes; + } + + /** + * {@link #reset(InputStream) Reusable} deserialization of Colfer streams. + */ + public static class Unmarshaller { + + /** The data source. */ + protected InputStream in; + + /** The read buffer. */ + public byte[] buf; + + /** The {@link #buf buffer}'s data start index, inclusive. */ + protected int offset; + + /** The {@link #buf buffer}'s data end index, exclusive. */ + protected int i; + + + /** + * @param in the data source or {@code null}. + * @param buf the initial buffer or {@code null}. + */ + public Unmarshaller(InputStream in, byte[] buf) { + // TODO: better size estimation + if (buf == null || buf.length == 0) + buf = new byte[Math.min(Entry.colferSizeMax, 2048)]; + this.buf = buf; + reset(in); + } + + /** + * Reuses the marshaller. + * @param in the data source or {@code null}. + * @throws IllegalStateException on pending data. + */ + public void reset(InputStream in) { + if (this.i != this.offset) throw new IllegalStateException("colfer: pending data"); + this.in = in; + this.offset = 0; + this.i = 0; + } + + /** + * Deserializes the following object. + * @return the result or {@code null} when EOF. + * @throws IOException from the input stream. + * @throws SecurityException on an upper limit breach defined by {@link #colferSizeMax}. + * @throws InputMismatchException when the data does not match this object's schema. + */ + public Entry next() throws IOException { + if (in == null) return null; + + while (true) { + if (this.i > this.offset) { + try { + Entry o = new Entry(); + this.offset = o.unmarshal(this.buf, this.offset, this.i); + return o; + } catch (BufferUnderflowException e) { + } + } + // not enough data + + if (this.i <= this.offset) { + this.offset = 0; + this.i = 0; + } else if (i == buf.length) { + byte[] src = this.buf; + // TODO: better size estimation + if (offset == 0) this.buf = new byte[Math.min(Entry.colferSizeMax, this.buf.length * 4)]; + System.arraycopy(src, this.offset, this.buf, 0, this.i - this.offset); + this.i -= this.offset; + this.offset = 0; + } + assert this.i < this.buf.length; + + int n = in.read(buf, i, buf.length - i); + if (n < 0) { + if (this.i > this.offset) + throw new InputMismatchException("colfer: pending data with EOF"); + return null; + } + assert n > 0; + i += n; + } + } + + } + + + /** + * Serializes the object. + * @param out the data destination. + * @param buf the initial buffer or {@code null}. + * @return the final buffer. When the serial fits into {@code buf} then the return is {@code buf}. + * Otherwise the return is a new buffer, large enough to hold the whole serial. + * @throws IOException from {@code out}. + * @throws IllegalStateException on an upper limit breach defined by {@link #colferSizeMax}. + */ + public byte[] marshal(OutputStream out, byte[] buf) throws IOException { + // TODO: better size estimation + if (buf == null || buf.length == 0) + buf = new byte[Math.min(Entry.colferSizeMax, 2048)]; + + while (true) { + int i; + try { + i = marshal(buf, 0); + } catch (BufferOverflowException e) { + buf = new byte[Math.min(Entry.colferSizeMax, buf.length * 4)]; + continue; + } + + out.write(buf, 0, i); + return buf; + } + } + + /** + * Serializes the object. + * @param buf the data destination. + * @param offset the initial index for {@code buf}, inclusive. + * @return the final index for {@code buf}, exclusive. + * @throws BufferOverflowException when {@code buf} is too small. + * @throws IllegalStateException on an upper limit breach defined by {@link #colferSizeMax}. + */ + public int marshal(byte[] buf, int offset) { + int i = offset; + + try { + if (! this.name.isEmpty()) { + buf[i++] = (byte) 0; + int start = ++i; + + String s = this.name; + for (int sIndex = 0, sLength = s.length(); sIndex < sLength; sIndex++) { + char c = s.charAt(sIndex); + if (c < '\u0080') { + buf[i++] = (byte) c; + } else if (c < '\u0800') { + buf[i++] = (byte) (192 | c >>> 6); + buf[i++] = (byte) (128 | c & 63); + } else if (c < '\ud800' || c > '\udfff') { + buf[i++] = (byte) (224 | c >>> 12); + buf[i++] = (byte) (128 | c >>> 6 & 63); + buf[i++] = (byte) (128 | c & 63); + } else { + int cp = 0; + if (++sIndex < sLength) cp = Character.toCodePoint(c, s.charAt(sIndex)); + if ((cp >= 1 << 16) && (cp < 1 << 21)) { + buf[i++] = (byte) (240 | cp >>> 18); + buf[i++] = (byte) (128 | cp >>> 12 & 63); + buf[i++] = (byte) (128 | cp >>> 6 & 63); + buf[i++] = (byte) (128 | cp & 63); + } else + buf[i++] = (byte) '?'; + } + } + int size = i - start; + if (size > Entry.colferSizeMax) + throw new IllegalStateException(format("colfer: kamon/context/kamon.Entry.name size %d exceeds %d UTF-8 bytes", size, Entry.colferSizeMax)); + + int ii = start - 1; + if (size > 0x7f) { + i++; + for (int x = size; x >= 1 << 14; x >>>= 7) i++; + System.arraycopy(buf, start, buf, i - size, size); + + do { + buf[ii++] = (byte) (size | 0x80); + size >>>= 7; + } while (size > 0x7f); + } + buf[ii] = (byte) size; + } + + if (this.content.length != 0) { + buf[i++] = (byte) 1; + + int size = this.content.length; + if (size > Entry.colferSizeMax) + throw new IllegalStateException(format("colfer: kamon/context/kamon.Entry.content size %d exceeds %d bytes", size, Entry.colferSizeMax)); + + int x = size; + while (x > 0x7f) { + buf[i++] = (byte) (x | 0x80); + x >>>= 7; + } + buf[i++] = (byte) x; + + int start = i; + i += size; + System.arraycopy(this.content, 0, buf, start, size); + } + + buf[i++] = (byte) 0x7f; + return i; + } catch (ArrayIndexOutOfBoundsException e) { + if (i - offset > Entry.colferSizeMax) + throw new IllegalStateException(format("colfer: kamon/context/kamon.Entry exceeds %d bytes", Entry.colferSizeMax)); + if (i > buf.length) throw new BufferOverflowException(); + throw e; + } + } + + /** + * Deserializes the object. + * @param buf the data source. + * @param offset the initial index for {@code buf}, inclusive. + * @return the final index for {@code buf}, exclusive. + * @throws BufferUnderflowException when {@code buf} is incomplete. (EOF) + * @throws SecurityException on an upper limit breach defined by {@link #colferSizeMax}. + * @throws InputMismatchException when the data does not match this object's schema. + */ + public int unmarshal(byte[] buf, int offset) { + return unmarshal(buf, offset, buf.length); + } + + /** + * Deserializes the object. + * @param buf the data source. + * @param offset the initial index for {@code buf}, inclusive. + * @param end the index limit for {@code buf}, exclusive. + * @return the final index for {@code buf}, exclusive. + * @throws BufferUnderflowException when {@code buf} is incomplete. (EOF) + * @throws SecurityException on an upper limit breach defined by {@link #colferSizeMax}. + * @throws InputMismatchException when the data does not match this object's schema. + */ + public int unmarshal(byte[] buf, int offset, int end) { + if (end > buf.length) end = buf.length; + int i = offset; + + try { + byte header = buf[i++]; + + if (header == (byte) 0) { + int size = 0; + for (int shift = 0; true; shift += 7) { + byte b = buf[i++]; + size |= (b & 0x7f) << shift; + if (shift == 28 || b >= 0) break; + } + if (size < 0 || size > Entry.colferSizeMax) + throw new SecurityException(format("colfer: kamon/context/kamon.Entry.name size %d exceeds %d UTF-8 bytes", size, Entry.colferSizeMax)); + + int start = i; + i += size; + this.name = new String(buf, start, size, StandardCharsets.UTF_8); + header = buf[i++]; + } + + if (header == (byte) 1) { + int size = 0; + for (int shift = 0; true; shift += 7) { + byte b = buf[i++]; + size |= (b & 0x7f) << shift; + if (shift == 28 || b >= 0) break; + } + if (size < 0 || size > Entry.colferSizeMax) + throw new SecurityException(format("colfer: kamon/context/kamon.Entry.content size %d exceeds %d bytes", size, Entry.colferSizeMax)); + + this.content = new byte[size]; + int start = i; + i += size; + System.arraycopy(buf, start, this.content, 0, size); + + header = buf[i++]; + } + + if (header != (byte) 0x7f) + throw new InputMismatchException(format("colfer: unknown header at byte %d", i - 1)); + } finally { + if (i > end && end - offset < Entry.colferSizeMax) throw new BufferUnderflowException(); + if (i < 0 || i - offset > Entry.colferSizeMax) + throw new SecurityException(format("colfer: kamon/context/kamon.Entry exceeds %d bytes", Entry.colferSizeMax)); + if (i > end) throw new BufferUnderflowException(); + } + + return i; + } + + // {@link Serializable} version number. + private static final long serialVersionUID = 2L; + + // {@link Serializable} Colfer extension. + private void writeObject(ObjectOutputStream out) throws IOException { + // TODO: better size estimation + byte[] buf = new byte[1024]; + int n; + while (true) try { + n = marshal(buf, 0); + break; + } catch (BufferUnderflowException e) { + buf = new byte[4 * buf.length]; + } + + out.writeInt(n); + out.write(buf, 0, n); + } + + // {@link Serializable} Colfer extension. + private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException { + init(); + + int n = in.readInt(); + byte[] buf = new byte[n]; + in.readFully(buf); + unmarshal(buf, 0); + } + + // {@link Serializable} Colfer extension. + private void readObjectNoData() throws ObjectStreamException { + init(); + } + + /** + * Gets kamon/context/kamon.Entry.name. + * @return the value. + */ + public String getName() { + return this.name; + } + + /** + * Sets kamon/context/kamon.Entry.name. + * @param value the replacement. + */ + public void setName(String value) { + this.name = value; + } + + /** + * Sets kamon/context/kamon.Entry.name. + * @param value the replacement. + * @return {link this}. + */ + public Entry withName(String value) { + this.name = value; + return this; + } + + /** + * Gets kamon/context/kamon.Entry.content. + * @return the value. + */ + public byte[] getContent() { + return this.content; + } + + /** + * Sets kamon/context/kamon.Entry.content. + * @param value the replacement. + */ + public void setContent(byte[] value) { + this.content = value; + } + + /** + * Sets kamon/context/kamon.Entry.content. + * @param value the replacement. + * @return {link this}. + */ + public Entry withContent(byte[] value) { + this.content = value; + return this; + } + + @Override + public final int hashCode() { + int h = 1; + if (this.name != null) h = 31 * h + this.name.hashCode(); + for (byte b : this.content) h = 31 * h + b; + return h; + } + + @Override + public final boolean equals(Object o) { + return o instanceof Entry && equals((Entry) o); + } + + public final boolean equals(Entry o) { + if (o == null) return false; + if (o == this) return true; + return o.getClass() == Entry.class + && (this.name == null ? o.name == null : this.name.equals(o.name)) + && java.util.Arrays.equals(this.content, o.content); + } + +} diff --git a/kamon-core/src/main/resources/reference.conf b/kamon-core/src/main/resources/reference.conf index dd42ab03..665cfc08 100644 --- a/kamon-core/src/main/resources/reference.conf +++ b/kamon-core/src/main/resources/reference.conf @@ -140,6 +140,19 @@ kamon { } + context { + encoding { + + http-headers { + span = "kamon.trace.propagation.B3" + } + + binary { + # span = "kamon.trace.propagation.Binary" + } + } + } + util { filters { diff --git a/kamon-core/src/main/scala/kamon/Kamon.scala b/kamon-core/src/main/scala/kamon/Kamon.scala index 7c3beb84..5c33b3b5 100644 --- a/kamon-core/src/main/scala/kamon/Kamon.scala +++ b/kamon-core/src/main/scala/kamon/Kamon.scala @@ -24,7 +24,7 @@ import scala.concurrent.Future import java.time.Duration import java.util.concurrent.{Executors, ScheduledExecutorService, ScheduledThreadPoolExecutor} -import kamon.trace.SpanContextCodec.Format +import kamon.context.{Context, Storage} import org.slf4j.LoggerFactory import scala.util.Try @@ -41,6 +41,7 @@ object Kamon extends MetricLookup with ReporterRegistry with Tracer { private val _metrics = new MetricRegistry(_config, _scheduler) private val _reporters = new ReporterRegistryImpl(_metrics, _config) private val _tracer = Tracer.Default(Kamon, _reporters, _config) + private val _contextStorage = Storage.ThreadLocal() private var _onReconfigureHooks = Seq.empty[OnReconfigureHook] def environment: Environment = @@ -93,30 +94,16 @@ object Kamon extends MetricLookup with ReporterRegistry with Tracer { override def buildSpan(operationName: String): Tracer.SpanBuilder = _tracer.buildSpan(operationName) - override def extract[C](format: Format[C], carrier: C): Option[SpanContext] = - _tracer.extract(format, carrier) + def currentContext(): Context = + _contextStorage.current() - override def inject[C](spanContext: SpanContext, format: Format[C], carrier: C): C = - _tracer.inject(spanContext, format, carrier) - - override def inject[C](spanContext: SpanContext, format: Format[C]): C = - _tracer.inject(spanContext, format) - - override def activeSpan(): Span = - _tracer.activeSpan() - - override def activate(span: Span): Scope = - _tracer.activate(span) - - - /** - * Makes the provided Span active before code is evaluated and deactivates it afterwards. - */ - def withActiveSpan[T](span: Span)(code: => T): T = { - val scope = activate(span) + def storeContext(context: Context): Storage.Scope = + _contextStorage.store(context) + def withContext[T](context: Context)(f: => T): T = { + val scope = _contextStorage.store(context) try { - code + f } finally { scope.close() } diff --git a/kamon-core/src/main/scala/kamon/context/Codec.scala b/kamon-core/src/main/scala/kamon/context/Codec.scala new file mode 100644 index 00000000..957c3e26 --- /dev/null +++ b/kamon-core/src/main/scala/kamon/context/Codec.scala @@ -0,0 +1,132 @@ +package kamon +package context + +import com.typesafe.config.Config +import kamon.util.DynamicAccess +import org.slf4j.LoggerFactory +import scala.collection.mutable + +class Codec(initialConfig: Config) { + private val log = LoggerFactory.getLogger(classOf[Codec]) + + @volatile private var httpHeaders: Codec.ForContext[TextMap] = new Codec.HttpHeaders(Map.empty) + //val Binary: Codec.ForContext[ByteBuffer] = _ + reconfigure(initialConfig) + + + def HttpHeaders: Codec.ForContext[TextMap] = + httpHeaders + + def reconfigure(config: Config): Unit = { + httpHeaders = new Codec.HttpHeaders(readEntryCodecs("kamon.context.encoding.http-headers", config)) + + + // Kamon.contextCodec.httpHeaderExport(current) + // Kamon.exportContext(HTTP, context) + // Kamon.importContext(HTTP, textMap) + // Kamon.currentContext() + // Kamon.storeContext(context) + + } + + private def readEntryCodecs[T](rootKey: String, config: Config): Map[String, Codec.ForEntry[T]] = { + val rootConfig = config.getConfig(rootKey) + val dynamic = new DynamicAccess(getClass.getClassLoader) + val entries = Map.newBuilder[String, Codec.ForEntry[T]] + + rootConfig.topLevelKeys.foreach(key => { + try { + val fqcn = rootConfig.getString(key) + entries += ((key, dynamic.createInstanceFor[Codec.ForEntry[T]](fqcn, Nil).get)) + } catch { + case e: Throwable => + log.error(s"Failed to initialize codec for key [$key]", e) + } + }) + + entries.result() + } +} + +object Codec { + + trait ForContext[T] { + def encode(context: Context): T + def decode(carrier: T): Context + } + + trait ForEntry[T] { + def encode(context: Context): T + def decode(carrier: T, context: Context): Context + } + + final class HttpHeaders(entryCodecs: Map[String, Codec.ForEntry[TextMap]]) extends Codec.ForContext[TextMap] { + private val log = LoggerFactory.getLogger(classOf[HttpHeaders]) + + override def encode(context: Context): TextMap = { + val encoded = TextMap.Default() + + context.entries.foreach { + case (key, _) if key.broadcast => + entryCodecs.get(key.name) match { + case Some(codec) => + try { + codec.encode(context).values.foreach(pair => encoded.put(pair._1, pair._2)) + } catch { + case e: Throwable => log.error(s"Failed to encode key [${key.name}]", e) + } + + case None => + log.error("Context key [{}] should be encoded in HttpHeaders but no codec was found for it.", key.name) + } + } + + encoded + } + + override def decode(carrier: TextMap): Context = { + var context: Context = Context.Empty + + try { + context = entryCodecs.foldLeft(context)((ctx, codecEntry) => { + val (_, codec) = codecEntry + codec.decode(carrier, ctx) + }) + + } catch { + case e: Throwable => + log.error("Failed to decode context from HttpHeaders", e) + } + + context + } + } + +} + + +trait TextMap { + def get(key: String): Option[String] + + def put(key: String, value: String): Unit + + def values: Iterator[(String, String)] +} + +object TextMap { + + class Default extends TextMap { + private val storage = mutable.Map.empty[String, String] + + override def get(key: String): Option[String] = storage.get(key) + + override def put(key: String, value: String): Unit = storage.put(key, value) + + override def values: Iterator[(String, String)] = storage.toIterator + } + + object Default { + def apply(): Default = new Default() + } + +} \ No newline at end of file diff --git a/kamon-core/src/main/scala/kamon/context/Context.scala b/kamon-core/src/main/scala/kamon/context/Context.scala index f7c78388..f8a4662f 100644 --- a/kamon-core/src/main/scala/kamon/context/Context.scala +++ b/kamon-core/src/main/scala/kamon/context/Context.scala @@ -1,18 +1,27 @@ package kamon.context -class Context private (private val keys: Map[Key[_], Any]) { +class Context private (private[context] val entries: Map[Key[_], Any]) { def get[T](key: Key[T]): T = - keys.get(key).getOrElse(key.emptyValue).asInstanceOf[T] + entries.get(key).getOrElse(key.emptyValue).asInstanceOf[T] def withKey[T](key: Key[T], value: T): Context = - new Context(keys.updated(key, value)) + new Context(entries.updated(key, value)) } object Context { val Empty = new Context(Map.empty) - def apply(): Context = Empty - def create(): Context = Empty + def apply(): Context = + Empty + + def create(): Context = + Empty + + def apply[T](key: Key[T], value: T): Context = + new Context(Map(key -> value)) + + def create[T](key: Key[T], value: T): Context = + apply(key, value) } @@ -38,110 +47,4 @@ object Key { override def equals(that: Any): Boolean = that.isInstanceOf[Default[_]] && that.asInstanceOf[Default[_]].name == this.name } -} - -trait Storage { - def current(): Context - def store(context: Context): Scope - - trait Scope { - def context: Context - def close(): Unit - } -} - -object Storage { - - class ThreadLocal extends Storage { - private val tls = new java.lang.ThreadLocal[Context]() { - override def initialValue(): Context = Context.Empty - } - - override def current(): Context = - tls.get() - - override def store(context: Context): Scope = { - val newContext = context - val previousContext = tls.get() - tls.set(newContext) - - new Scope { - override def context: Context = newContext - override def close(): Unit = tls.set(previousContext) - } - } - } -} - -trait KeyCodec[T] { - def encode(context: Context): T - def decode(carrier: T, context: Context): Context -} - -/* -object Example { - // this is defined somewhere statically, only once. - val User = Key.local[Option[User]]("user", None) - val Client = Key.local[Option[User]]("client", null) - val Span = Key.broadcast[Span]("span", EmptySpan) - val storage = Kamon.contextStorage // or something similar. - - storage.get(Span) // returns a Span instance or EmptySpan. - storage.get(User) // Returns Option[User] or None if not set. - storage.get(Client) // Returns Option[Client] or null if not set. - - // Context Propagation works the very same way as before. - - val scope = storage.store(context) - // do something here - scope.close() - - // Configuration for codecs would be handled sort of like this: - - // kamon.context.propagation { - // http-header-codecs { - // "span" = kamon.trace.propagation.B3 - // } - // - // binary-codecs { - // "span" = kamon.trace.propagation.Binary - // } - // } - - - - - -}*/ - - - -/* - - - - - -class Context(private val keys: Map[Key[_], Any]) { - - - - - -} - -object Context { - - -} - -sealed trait Key[T] { - def name: String -} - -object Key { - - def local[T](name: String): Key[T] = Local(name) - - case class Local[T](name: String) extends Key[T] -}*/ +} \ No newline at end of file diff --git a/kamon-core/src/main/scala/kamon/context/Mixin.scala b/kamon-core/src/main/scala/kamon/context/Mixin.scala new file mode 100644 index 00000000..52c97e84 --- /dev/null +++ b/kamon-core/src/main/scala/kamon/context/Mixin.scala @@ -0,0 +1,43 @@ +/* ========================================================================================= + * Copyright © 2013-2017 the kamon project + * + * 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 kamon.context + + +/** + * Utility trait that marks objects carrying a reference to a Span. + * + */ +trait HasContext { + def context: Context +} + +object HasContext { + private case class Default(context: Context) extends HasContext + + /** + * Construct a HasSpan instance that references the provided Span. + * + */ + def from(context: Context): HasContext = + Default(context) + + /** + * Construct a HasSpan instance that references the currently ActiveSpan in Kamon's tracer. + * + */ +// def fromActiveSpan(): HasContext = +// Default(Kamon.activeSpan()) +} diff --git a/kamon-core/src/main/scala/kamon/context/Storage.scala b/kamon-core/src/main/scala/kamon/context/Storage.scala new file mode 100644 index 00000000..6b92ff85 --- /dev/null +++ b/kamon-core/src/main/scala/kamon/context/Storage.scala @@ -0,0 +1,39 @@ +package kamon.context + +trait Storage { + def current(): Context + def store(context: Context): Storage.Scope +} + +object Storage { + + trait Scope { + def context: Context + def close(): Unit + } + + + class ThreadLocal extends Storage { + private val tls = new java.lang.ThreadLocal[Context]() { + override def initialValue(): Context = Context.Empty + } + + override def current(): Context = + tls.get() + + override def store(context: Context): Scope = { + val newContext = context + val previousContext = tls.get() + tls.set(newContext) + + new Scope { + override def context: Context = newContext + override def close(): Unit = tls.set(previousContext) + } + } + } + + object ThreadLocal { + def apply(): ThreadLocal = new ThreadLocal() + } +} \ No newline at end of file diff --git a/kamon-core/src/main/scala/kamon/trace/ActiveSpanStorage.scala b/kamon-core/src/main/scala/kamon/trace/ActiveSpanStorage.scala deleted file mode 100644 index 85e94ef2..00000000 --- a/kamon-core/src/main/scala/kamon/trace/ActiveSpanStorage.scala +++ /dev/null @@ -1,74 +0,0 @@ -package kamon.trace - -/** - * A means of storing and retrieving the currently active Span. The application code execution is always considered to - * contribute to the completion of the operation represented by the currently active Span. - * - * The activation of a Span is of temporary nature and users of this API must ensure that all Scopes created via calls - * to `activate(span)` are timely closed; failing to do so might lead to unexpected behavior. Typically, the same block - * of code designating a Span as currently active will close the created Scope after finishing execution. - * - */ -trait ActiveSpanStorage { - - /** - * @return the currently active Span. - */ - def activeSpan(): Span - - /** - * Sets - * @param span the Span to be set as currently active. - * @return a [[Scope]] that will finish the designation of the given Span as active once it's closed. - */ - def activate(span: Span): Scope - -} - -/** - * Encapsulates the state (if any) required to handle the removal of a Span from it's currently active designation. - * - * Typically a Scope will enclose the previously active Span and return the previously active Span when closed, - * although no assumptions are made. - * - */ -trait Scope extends AutoCloseable { - - /** - * Removes the currently active Span from the ActiveSpanStorage. - * - */ - def close(): Unit -} - -object ActiveSpanStorage { - - /** - * A ActiveSpanStorage that uses a [[java.lang.ThreadLocal]] as the underlying storage. - * - */ - final class ThreadLocal extends ActiveSpanStorage { - private val emptySpan = Span.Empty(this) - private val storage: java.lang.ThreadLocal[Span] = new java.lang.ThreadLocal[Span] { - override def initialValue(): Span = emptySpan - } - - override def activeSpan(): Span = - storage.get() - - override def activate(span: Span): Scope = { - val previouslyActiveSpan = storage.get() - storage.set(span) - - new Scope { - override def close(): Unit = { - storage.set(previouslyActiveSpan) - } - } - } - } - - object ThreadLocal { - def apply(): ThreadLocal = new ThreadLocal() - } -} \ No newline at end of file diff --git a/kamon-core/src/main/scala/kamon/trace/Span.scala b/kamon-core/src/main/scala/kamon/trace/Span.scala index 84cc5625..161042d5 100644 --- a/kamon-core/src/main/scala/kamon/trace/Span.scala +++ b/kamon-core/src/main/scala/kamon/trace/Span.scala @@ -17,15 +17,18 @@ package kamon package trace import kamon.ReporterRegistry.SpanSink +import kamon.context.Key import kamon.trace.SpanContext.SamplingDecision - import kamon.util.{Clock, MeasurementUnit} -/** - * Minimum set of capabilities that should be provided by a Span, all additional sugar is provided by extensions - * in the Span trait bellow. - */ -trait BaseSpan { + +trait Span { + + def isEmpty(): Boolean + def isLocal(): Boolean + + def nonEmpty(): Boolean = !isEmpty() + def isRemote(): Boolean = !isLocal() def context(): SpanContext @@ -39,21 +42,11 @@ trait BaseSpan { def addMetricTag(key: String, value: String): Span - def addBaggage(key: String, value: String): Span - - def getBaggage(key: String): Option[String] - def setOperationName(name: String): Span def disableMetricsCollection(): Span def finish(finishTimestampMicros: Long): Unit -} - -/** - * - */ -trait Span extends BaseSpan { def finish(): Unit = finish(Clock.microTimestamp()) @@ -71,25 +64,22 @@ trait Span extends BaseSpan { object Span { - final class Empty(activeSpanSource: ActiveSpanStorage) extends Span { - override val context: SpanContext = SpanContext.EmptySpanContext + val ContextKey = Key.broadcast[Span]("span", Span.Empty) + object Empty extends Span { + override val context: SpanContext = SpanContext.EmptySpanContext + override def isEmpty(): Boolean = true + override def isLocal(): Boolean = true override def annotate(annotation: Annotation): Span = this override def addSpanTag(key: String, value: String): Span = this override def addSpanTag(key: String, value: Long): Span = this override def addSpanTag(key: String, value: Boolean): Span = this override def addMetricTag(key: String, value: String): Span = this - override def addBaggage(key: String, value: String): Span = this - override def getBaggage(key: String): Option[String] = None override def setOperationName(name: String): Span = this override def disableMetricsCollection(): Span = this override def finish(finishTimestampMicros: Long): Unit = {} } - object Empty { - def apply(activeSpanSource: ActiveSpanStorage): Empty = new Empty(activeSpanSource) - } - /** * * @param spanContext @@ -98,8 +88,8 @@ object Span { * @param startTimestampMicros * @param spanSink */ - final class Real(spanContext: SpanContext, initialOperationName: String, initialSpanTags: Map[String, Span.TagValue], - initialMetricTags: Map[String, String], startTimestampMicros: Long, spanSink: SpanSink, activeSpanSource: ActiveSpanStorage) extends Span { + final class Local(spanContext: SpanContext, initialOperationName: String, initialSpanTags: Map[String, Span.TagValue], + initialMetricTags: Map[String, String], startTimestampMicros: Long, spanSink: SpanSink) extends Span { private var collectMetrics: Boolean = true private var open: Boolean = true @@ -110,6 +100,9 @@ object Span { private var customMetricTags = initialMetricTags private var annotations = List.empty[Span.Annotation] + override def isEmpty(): Boolean = false + override def isLocal(): Boolean = true + def annotate(annotation: Annotation): Span = synchronized { if(sampled && open) annotations = annotation :: annotations @@ -142,14 +135,6 @@ object Span { this } - override def addBaggage(key: String, value: String): Span = { - spanContext.baggage.add(key, value) - this - } - - override def getBaggage(key: String): Option[String] = - spanContext.baggage.get(key) - override def disableMetricsCollection(): Span = synchronized { collectMetrics = false this @@ -194,10 +179,29 @@ object Span { } } - object Real { + object Local { 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) + initialMetricTags: Map[String, String], startTimestampMicros: Long, reporterRegistry: ReporterRegistryImpl): Local = + new Local(spanContext, initialOperationName, initialSpanTags, initialMetricTags, startTimestampMicros, reporterRegistry) + } + + + final class Remote(val context: SpanContext) extends Span { + override def isEmpty(): Boolean = false + override def isLocal(): Boolean = false + override def annotate(annotation: Annotation): Span = this + override def addSpanTag(key: String, value: String): Span = this + override def addSpanTag(key: String, value: Long): Span = this + override def addSpanTag(key: String, value: Boolean): Span = this + override def addMetricTag(key: String, value: String): Span = this + override def setOperationName(name: String): Span = this + override def disableMetricsCollection(): Span = this + override def finish(finishTimestampMicros: Long): Unit = {} + } + + object Remote { + def apply(spanContext: SpanContext): Remote = + new Remote(spanContext) } sealed trait TagValue diff --git a/kamon-core/src/main/scala/kamon/trace/SpanContext.scala b/kamon-core/src/main/scala/kamon/trace/SpanContext.scala index ae92f46d..e8b239ba 100644 --- a/kamon-core/src/main/scala/kamon/trace/SpanContext.scala +++ b/kamon-core/src/main/scala/kamon/trace/SpanContext.scala @@ -16,7 +16,7 @@ package kamon.trace import kamon.trace.IdentityProvider.Identifier -import kamon.trace.SpanContext.{Baggage, SamplingDecision, Source} +import kamon.trace.SpanContext.SamplingDecision /** * @@ -24,9 +24,8 @@ import kamon.trace.SpanContext.{Baggage, SamplingDecision, Source} * @param spanID * @param parentID * @param samplingDecision - * @param baggage */ -case class SpanContext(traceID: Identifier, spanID: Identifier, parentID: Identifier, samplingDecision: SamplingDecision, baggage: Baggage, source: Source) { +case class SpanContext(traceID: Identifier, spanID: Identifier, parentID: Identifier, samplingDecision: SamplingDecision) { def createChild(childSpanID: Identifier, samplingDecision: SamplingDecision): SpanContext = this.copy(parentID = this.spanID, spanID = childSpanID) @@ -34,19 +33,11 @@ case class SpanContext(traceID: Identifier, spanID: Identifier, parentID: Identi object SpanContext { - sealed trait Source - object Source { - case object Local extends Source - case object Remote extends Source - } - val EmptySpanContext = SpanContext( traceID = IdentityProvider.NoIdentifier, spanID = IdentityProvider.NoIdentifier, parentID = IdentityProvider.NoIdentifier, - samplingDecision = SamplingDecision.DoNotSample, - baggage = Baggage.EmptyBaggage, - source = Source.Local + samplingDecision = SamplingDecision.DoNotSample ) diff --git a/kamon-core/src/main/scala/kamon/trace/SpanContextCodec.scala b/kamon-core/src/main/scala/kamon/trace/SpanContextCodec.scala index 43b5e8e4..1db55694 100644 --- a/kamon-core/src/main/scala/kamon/trace/SpanContextCodec.scala +++ b/kamon-core/src/main/scala/kamon/trace/SpanContextCodec.scala @@ -15,53 +15,36 @@ package kamon.trace -import java.lang.StringBuilder import java.net.{URLDecoder, URLEncoder} -import java.nio.ByteBuffer -import kamon.trace.SpanContext.{Baggage, SamplingDecision, Source} -import scala.collection.mutable -trait SpanContextCodec[T] { - def inject(spanContext: SpanContext, carrier: T): T - def inject(spanContext: SpanContext): T - def extract(carrier: T): Option[SpanContext] -} +import kamon.context.{Codec, Context, TextMap} +import kamon.trace.SpanContext.SamplingDecision -object SpanContextCodec { - sealed trait Format[C] - object Format { - case object TextMap extends Format[TextMap] - case object HttpHeaders extends Format[TextMap] - case object Binary extends Format[ByteBuffer] - } +object SpanContextCodec { - class ExtendedB3(identityProvider: IdentityProvider) extends SpanContextCodec[TextMap] { + class ExtendedB3(identityProvider: IdentityProvider) extends Codec.ForEntry[TextMap] { import ExtendedB3.Headers - override def inject(spanContext: SpanContext, carrier: TextMap): TextMap = { - if(spanContext != SpanContext.EmptySpanContext) { + override def encode(context: Context): TextMap = { + val span = context.get(Span.ContextKey) + val carrier = TextMap.Default() + + if(span.nonEmpty()) { + val spanContext = span.context 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.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) - } } carrier } - override def inject(spanContext: SpanContext): TextMap = - inject(spanContext, TextMap.Default()) - - override def extract(carrier: TextMap): Option[SpanContext] = { + override def decode(carrier: TextMap, context: Context): Context = { val traceID = carrier.get(Headers.TraceIdentifier) .map(id => identityProvider.traceIdentifierGenerator().from(urlDecode(id))) .getOrElse(IdentityProvider.NoIdentifier) @@ -75,56 +58,17 @@ object SpanContextCodec { .map(id => identityProvider.spanIdentifierGenerator().from(urlDecode(id))) .getOrElse(IdentityProvider.NoIdentifier) - val baggage = decodeBaggage(carrier.get(Headers.Baggage)) val flags = carrier.get(Headers.Flags) - flags.foreach { flags => - baggage.add(Headers.Flags, flags) - } - val samplingDecision = flags.orElse(carrier.get(Headers.Sampled)) match { case Some(sampled) if sampled == "1" => SamplingDecision.Sample case Some(sampled) if sampled == "0" => SamplingDecision.DoNotSample case _ => SamplingDecision.Unknown } - Some(SpanContext(traceID, spanID, parentID, samplingDecision, baggage, Source.Remote)) - - } else None - } - - private def encodeBaggage(baggage: Baggage): String = { - 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)) - } - } - - encodedBaggage.toString() - } else "" - } + context.withKey(Span.ContextKey, Span.Remote(SpanContext(traceID, spanID, parentID, samplingDecision))) - private def decodeBaggage(encodedBaggage: Option[String]): Baggage = { - val baggage = Baggage() - encodedBaggage.foreach { baggageString => - baggageString.split(";").foreach { group => - val pair = group.split("=") - if(pair.length >= 2 && pair(0).nonEmpty) { - baggage.add(urlDecode(pair(0)), urlDecode(pair(1))) - } - } - } - - baggage + } else context } private def encodeSamplingDecision(samplingDecision: SamplingDecision): Option[String] = samplingDecision match { @@ -135,7 +79,6 @@ object SpanContextCodec { private def urlEncode(s: String): String = URLEncoder.encode(s, "UTF-8") private def urlDecode(s: String): String = URLDecoder.decode(s, "UTF-8") - } object ExtendedB3 { @@ -149,26 +92,6 @@ object SpanContextCodec { val SpanIdentifier = "X-B3-SpanId" val Sampled = "X-B3-Sampled" val Flags = "X-B3-Flags" - val Baggage = "X-B3-Extra-Baggage" } } -} - -trait TextMap { - def get(key: String): Option[String] - def put(key: String, value: String): Unit - def values: Iterator[(String, String)] -} - -object TextMap { - class Default extends TextMap { - private val storage = mutable.Map.empty[String, String] - override def get(key: String): Option[String] = storage.get(key) - override def put(key: String, value: String): Unit = storage.put(key, value) - override def values: Iterator[(String, String)] = storage.toIterator - } - - object Default { - def apply(): Default = new Default() - } -} +} \ No newline at end of file diff --git a/kamon-core/src/main/scala/kamon/trace/Tracer.scala b/kamon-core/src/main/scala/kamon/trace/Tracer.scala index bfdd561d..65307b95 100644 --- a/kamon-core/src/main/scala/kamon/trace/Tracer.scala +++ b/kamon-core/src/main/scala/kamon/trace/Tracer.scala @@ -15,13 +15,11 @@ package kamon.trace -import java.nio.ByteBuffer - import com.typesafe.config.Config -import kamon.ReporterRegistryImpl +import kamon.{Kamon, ReporterRegistryImpl} import kamon.metric.MetricLookup import kamon.trace.Span.TagValue -import kamon.trace.SpanContext.{SamplingDecision, Source} +import kamon.trace.SpanContext.SamplingDecision import kamon.trace.Tracer.SpanBuilder import kamon.util.{Clock, DynamicAccess} import org.slf4j.LoggerFactory @@ -29,56 +27,25 @@ import org.slf4j.LoggerFactory import scala.collection.immutable import scala.util.Try -trait Tracer extends ActiveSpanStorage { +trait Tracer { def buildSpan(operationName: String): SpanBuilder - - def extract[C](format: SpanContextCodec.Format[C], carrier: C): Option[SpanContext] - def inject[C](spanContext: SpanContext, format: SpanContextCodec.Format[C], carrier: C): C - def inject[C](spanContext: SpanContext, format: SpanContextCodec.Format[C]): C } object Tracer { final class Default(metrics: MetricLookup, reporterRegistry: ReporterRegistryImpl, initialConfig: Config) extends Tracer { private val logger = LoggerFactory.getLogger(classOf[Tracer]) - private val activeSpanSource = ActiveSpanStorage.ThreadLocal() private[Tracer] val tracerMetrics = new TracerMetrics(metrics) @volatile private[Tracer] var joinRemoteParentsWithSameSpanID: Boolean = true @volatile private[Tracer] var configuredSampler: Sampler = Sampler.Never @volatile private[Tracer] var identityProvider: IdentityProvider = IdentityProvider.Default() - @volatile private[Tracer] var textMapSpanContextCodec: SpanContextCodec[TextMap] = SpanContextCodec.ExtendedB3(identityProvider) - @volatile private[Tracer] var httpHeaderSpanContextCodec: SpanContextCodec[TextMap] = SpanContextCodec.ExtendedB3(identityProvider) reconfigure(initialConfig) override def buildSpan(operationName: String): SpanBuilder = new SpanBuilder(operationName, this, reporterRegistry) - override def extract[C](format: SpanContextCodec.Format[C], carrier: C): Option[SpanContext] = format match { - case SpanContextCodec.Format.HttpHeaders => httpHeaderSpanContextCodec.extract(carrier.asInstanceOf[TextMap]) - case SpanContextCodec.Format.TextMap => textMapSpanContextCodec.extract(carrier.asInstanceOf[TextMap]) - case SpanContextCodec.Format.Binary => None - } - - override def inject[C](spanContext: SpanContext, format: SpanContextCodec.Format[C], carrier: C): C = format match { - case SpanContextCodec.Format.HttpHeaders => httpHeaderSpanContextCodec.inject(spanContext, carrier.asInstanceOf[TextMap]) - case SpanContextCodec.Format.TextMap => textMapSpanContextCodec.inject(spanContext, carrier.asInstanceOf[TextMap]) - case SpanContextCodec.Format.Binary => carrier - } - - override def inject[C](spanContext: SpanContext, format: SpanContextCodec.Format[C]): C = format match { - case SpanContextCodec.Format.HttpHeaders => httpHeaderSpanContextCodec.inject(spanContext) - case SpanContextCodec.Format.TextMap => textMapSpanContextCodec.inject(spanContext) - case SpanContextCodec.Format.Binary => ByteBuffer.allocate(0) // TODO: Implement binary encoding. - } - - override def activeSpan(): Span = - activeSpanSource.activeSpan() - - override def activate(span: Span): Scope = - activeSpanSource.activate(span) - def sampler: Sampler = configuredSampler @@ -100,25 +67,9 @@ object Tracer { traceConfig.getString("identity-provider"), immutable.Seq.empty[(Class[_], AnyRef)] ).get - val spanContextCodecs = traceConfig.getConfig("span-context-codec") - val newTextMapSpanContextCodec = dynamic.createInstanceFor[SpanContextCodec[TextMap]]( - spanContextCodecs.getString("text-map"), immutable.Seq((classOf[IdentityProvider], newIdentityProvider)) - ).get - - val newHttpHeadersSpanContextCodec = dynamic.createInstanceFor[SpanContextCodec[TextMap]]( - spanContextCodecs.getString("http-headers"), immutable.Seq((classOf[IdentityProvider], newIdentityProvider)) - ).get - -// val newBinarySpanContextCodec = dynamic.createInstanceFor[SpanContextCodec[TextMap]]( -// spanContextCodecs.getString("binary"), immutable.Seq((classOf[IdentityProvider], newIdentityProvider)) -// ).get // TODO: Make it happen! - - configuredSampler = newSampler joinRemoteParentsWithSameSpanID = newJoinRemoteParentsWithSameSpanID identityProvider = newIdentityProvider - textMapSpanContextCodec = newTextMapSpanContextCodec - httpHeaderSpanContextCodec = newHttpHeadersSpanContextCodec }.failed.foreach { ex => logger.error("Unable to reconfigure Kamon Tracer", ex) @@ -132,25 +83,17 @@ object Tracer { } final class SpanBuilder(operationName: String, tracer: Tracer.Default, reporterRegistry: ReporterRegistryImpl) { - private var parentContext: SpanContext = _ + private var parentSpan: Span = _ private var startTimestamp = 0L private var initialSpanTags = Map.empty[String, Span.TagValue] private var initialMetricTags = Map.empty[String, String] private var useActiveSpanAsParent = true - def asChildOf(parentContext: SpanContext): SpanBuilder = { - this.parentContext = parentContext + def asChildOf(parent: Span): SpanBuilder = { + if(parent != Span.Empty) this.parentSpan = parent this } - def asChildOf(parentContext: Option[SpanContext]): SpanBuilder = { - parentContext.foreach(asChildOf) - this - } - - def asChildOf(parentSpan: Span): SpanBuilder = - asChildOf(parentSpan.context()) - def withMetricTag(key: String, value: String): SpanBuilder = { this.initialMetricTags = this.initialMetricTags + (key -> value) this @@ -185,38 +128,36 @@ object Tracer { def start(): Span = { val startTimestampMicros = if(startTimestamp != 0L) startTimestamp else Clock.microTimestamp() - val parentSpanContext: Option[SpanContext] = Option(parentContext) - .orElse(if(useActiveSpanAsParent) Some(tracer.activeSpan().context()) else None) - .filter(spanContext => spanContext != SpanContext.EmptySpanContext) + val parentSpan: Option[Span] = Option(this.parentSpan) + .orElse(if(useActiveSpanAsParent) Some(Kamon.currentContext().get(Span.ContextKey)) else None) + .filter(span => span != Span.Empty) - val samplingDecision: SamplingDecision = parentSpanContext - .map(_.samplingDecision) + val samplingDecision: SamplingDecision = parentSpan + .map(_.context.samplingDecision) .filter(_ != SamplingDecision.Unknown) .getOrElse(tracer.sampler.decide(operationName, initialSpanTags)) - val spanContext = parentSpanContext match { + val spanContext = parentSpan match { case Some(parent) => joinParentContext(parent, samplingDecision) case None => newSpanContext(samplingDecision) } tracer.tracerMetrics.createdSpans.increment() - Span.Real(spanContext, operationName, initialSpanTags, initialMetricTags, startTimestampMicros, reporterRegistry, tracer) + Span.Local(spanContext, operationName, initialSpanTags, initialMetricTags, startTimestampMicros, reporterRegistry) } - private def joinParentContext(parent: SpanContext, samplingDecision: SamplingDecision): SpanContext = - if(parent.source == Source.Remote && tracer.joinRemoteParentsWithSameSpanID) - parent.copy(samplingDecision = samplingDecision) + private def joinParentContext(parent: Span, samplingDecision: SamplingDecision): SpanContext = + if(parent.isRemote() && tracer.joinRemoteParentsWithSameSpanID) + parent.context().copy(samplingDecision = samplingDecision) else - parent.createChild(tracer.identityProvider.spanIdentifierGenerator().generate(), samplingDecision) + parent.context().createChild(tracer.identityProvider.spanIdentifierGenerator().generate(), samplingDecision) private def newSpanContext(samplingDecision: SamplingDecision): SpanContext = SpanContext( traceID = tracer.identityProvider.traceIdentifierGenerator().generate(), spanID = tracer.identityProvider.spanIdentifierGenerator().generate(), parentID = IdentityProvider.NoIdentifier, - samplingDecision = samplingDecision, - baggage = SpanContext.Baggage(), - source = Source.Local + samplingDecision = samplingDecision ) } diff --git a/kamon-core/src/main/scala/kamon/util/Mixin.scala b/kamon-core/src/main/scala/kamon/util/Mixin.scala deleted file mode 100644 index 2fd7be24..00000000 --- a/kamon-core/src/main/scala/kamon/util/Mixin.scala +++ /dev/null @@ -1,46 +0,0 @@ -/* ========================================================================================= - * Copyright © 2013-2017 the kamon project - * - * 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 kamon -package util - -import kamon.trace.Span - - -/** - * Utility trait that marks objects carrying a reference to a Span. - * - */ -trait HasSpan { - def span: Span -} - -object HasSpan { - private case class Default(span: Span) extends HasSpan - - /** - * Construct a HasSpan instance that references the provided Span. - * - */ - def from(span: Span): HasSpan = - Default(span) - - /** - * Construct a HasSpan instance that references the currently ActiveSpan in Kamon's tracer. - * - */ - def fromActiveSpan(): HasSpan = - Default(Kamon.activeSpan()) -} diff --git a/kamon-core/src/test/scala/kamon/context/ContextCodecSpec.scala b/kamon-core/src/test/scala/kamon/context/ContextCodecSpec.scala new file mode 100644 index 00000000..242c3345 --- /dev/null +++ b/kamon-core/src/test/scala/kamon/context/ContextCodecSpec.scala @@ -0,0 +1,18 @@ +package kamon.context + +import kamon.Kamon +import org.scalatest.{Matchers, WordSpec} + +class ContextCodecSpec extends WordSpec with Matchers { + "the Context Codec" when { + "encoding/decoding to HttpHeaders" should { + "encode stuff" in { + + + + } + } + } + + val ContextCodec = new Codec(Kamon.config()) +} diff --git a/kamon-core/src/test/scala/kamon/testkit/SpanBuilding.scala b/kamon-core/src/test/scala/kamon/testkit/SpanBuilding.scala index 9b845ac9..29678b34 100644 --- a/kamon-core/src/test/scala/kamon/testkit/SpanBuilding.scala +++ b/kamon-core/src/test/scala/kamon/testkit/SpanBuilding.scala @@ -1,6 +1,6 @@ package kamon.testkit -import kamon.trace.SpanContext.{SamplingDecision, Source} +import kamon.trace.SpanContext.SamplingDecision import kamon.trace.{IdentityProvider, SpanContext, SpanContextCodec} trait SpanBuilding { @@ -12,9 +12,7 @@ trait SpanBuilding { traceID = identityProvider.traceIdentifierGenerator().generate(), spanID = identityProvider.spanIdentifierGenerator().generate(), parentID = identityProvider.spanIdentifierGenerator().generate(), - samplingDecision = samplingDecision, - baggage = SpanContext.Baggage(), - source = Source.Local + samplingDecision = samplingDecision ) } diff --git a/kamon-core/src/test/scala/kamon/testkit/SpanInspector.scala b/kamon-core/src/test/scala/kamon/testkit/SpanInspector.scala index ab58e446..f23fba98 100644 --- a/kamon-core/src/test/scala/kamon/testkit/SpanInspector.scala +++ b/kamon-core/src/test/scala/kamon/testkit/SpanInspector.scala @@ -10,10 +10,10 @@ import scala.util.Try class SpanInspector(span: Span) { private val (realSpan, spanData) = Try { val realSpan = span match { - case _: Span.Real => span + case _: Span.Local => span } - val spanData = invoke[Span.Real, FinishedSpan](realSpan, "toFinishedSpan", classOf[Long] -> Long.box(Clock.microTimestamp())) + val spanData = invoke[Span.Local, FinishedSpan](realSpan, "toFinishedSpan", classOf[Long] -> Long.box(Clock.microTimestamp())) (realSpan, spanData) }.getOrElse((null, null)) @@ -27,10 +27,10 @@ class SpanInspector(span: Span) { spanData.tags def metricTags(): Map[String, String] = - getField[Span.Real, Map[String, String]](realSpan, "customMetricTags") + getField[Span.Local, Map[String, String]](realSpan, "customMetricTags") def startTimestamp(): Long = - getField[Span.Real, Long](realSpan, "startTimestampMicros") + getField[Span.Local, Long](realSpan, "startTimestampMicros") def context(): SpanContext = spanData.context diff --git a/kamon-core/src/test/scala/kamon/trace/ActiveSpanManagementSpec.scala b/kamon-core/src/test/scala/kamon/trace/ActiveSpanManagementSpec.scala deleted file mode 100644 index a6a7bc3a..00000000 --- a/kamon-core/src/test/scala/kamon/trace/ActiveSpanManagementSpec.scala +++ /dev/null @@ -1,65 +0,0 @@ -package kamon.trace - -import kamon.Kamon -import kamon.testkit.SpanInspector -import kamon.trace.Span.Annotation -import kamon.util.Clock -import org.scalatest.{Matchers, WordSpec} - -class ActiveSpanManagementSpec extends WordSpec with Matchers { - - "Kamon acting as a ActiveSpanSource" should { - "return a empty span when there is no currently active Span" in { - inspect(Kamon.activeSpan()) shouldBe empty - } - - "safely operate on a empty Span" in { - val emptySpan = Kamon.activeSpan() - val activeSpanData = inspect(Kamon.activeSpan()) - activeSpanData shouldBe empty - - emptySpan - .setOperationName("test") - .addBaggage("key", "value") - .addMetricTag("key", "value") - .addSpanTag("string", "string") - .addSpanTag("number", 42) - .addSpanTag("boolean-true", true) - .addSpanTag("boolean-false", false) - .annotate(Annotation(Clock.microTimestamp(), "event", Map("k" -> "v"))) - - val baggage = emptySpan.context().baggage - baggage.add("key", "value") - baggage.get("key") shouldBe empty - baggage.getAll() shouldBe empty - - Kamon.withActiveSpan(emptySpan) { - inspect(Kamon.activeSpan()) shouldBe empty - } - - inspect(Kamon.activeSpan()) shouldBe empty - } - - "set a Span as active when using activate" in { - val span = Kamon.buildSpan("mySpan").start() - val scope = Kamon.activate(span) - Kamon.activeSpan() shouldBe theSameInstanceAs(span) - scope.close() - } - - "restore the previously active Span when a scope is closed" in { - val previouslyActiveSpan = Kamon.activeSpan() - inspect(Kamon.activeSpan()) shouldBe empty - - val span = Kamon.buildSpan("mySpan").start() - Kamon.withActiveSpan(span) { - Kamon.activeSpan() shouldBe theSameInstanceAs(span) - } - - Kamon.activeSpan() shouldBe theSameInstanceAs(previouslyActiveSpan) - } - } - - def inspect(span: Span): SpanInspector = - SpanInspector(span) -} diff --git a/kamon-core/src/test/scala/kamon/trace/ExtendedB3SpanContextCodecSpec.scala b/kamon-core/src/test/scala/kamon/trace/ExtendedB3SpanContextCodecSpec.scala index 24cb7ef5..cc886bd9 100644 --- a/kamon-core/src/test/scala/kamon/trace/ExtendedB3SpanContextCodecSpec.scala +++ b/kamon-core/src/test/scala/kamon/trace/ExtendedB3SpanContextCodecSpec.scala @@ -16,6 +16,7 @@ package kamon.trace +import kamon.context.{Context, TextMap} import kamon.testkit.SpanBuilding import kamon.trace.IdentityProvider.Identifier import kamon.trace.SpanContext.SamplingDecision @@ -28,38 +29,22 @@ class ExtendedB3SpanContextCodecSpec extends WordSpecLike with Matchers with Opt "The ExtendedB3 SpanContextCodec" should { "return a TextMap containing the SpanContext data" in { - val context = testSpanContext() - context.baggage.add("some", "baggage") - context.baggage.add("more", "baggage") + val context = testContext() - val textMap = extendedB3Codec.inject(context) + val textMap = extendedB3Codec.encode(context) textMap.get("X-B3-TraceId").value shouldBe "1234" textMap.get("X-B3-ParentSpanId").value shouldBe "2222" textMap.get("X-B3-SpanId").value shouldBe "4321" textMap.get("X-B3-Sampled").value shouldBe "1" - textMap.get("X-B3-Extra-Baggage").value shouldBe "some=baggage;more=baggage" } - "allow to provide the TextMap to be used for encoding" in { - val context = testSpanContext() - context.baggage.add("some", "baggage") - context.baggage.add("more", "baggage") - val textMap = TextMap.Default() - extendedB3Codec.inject(context, textMap) - textMap.get("X-B3-TraceId").value shouldBe "1234" - textMap.get("X-B3-ParentSpanId").value shouldBe "2222" - textMap.get("X-B3-SpanId").value shouldBe "4321" - textMap.get("X-B3-Sampled").value shouldBe "1" - textMap.get("X-B3-Extra-Baggage").value shouldBe "some=baggage;more=baggage" - } - - "not inject anything if the SpanContext is empty" in { - val textMap = extendedB3Codec.inject(SpanContext.EmptySpanContext) + "not inject anything if there is no Span in the Context" in { + val textMap = extendedB3Codec.encode(Context.Empty) textMap.values shouldBe empty } - "extract a SpanContext from a TextMap when all fields are set" in { + "extract a RemoteSpan from a TextMap when all fields are set" in { val textMap = TextMap.Default() textMap.put("X-B3-TraceId", "1234") textMap.put("X-B3-ParentSpanId", "2222") @@ -67,15 +52,11 @@ class ExtendedB3SpanContextCodecSpec extends WordSpecLike with Matchers with Opt textMap.put("X-B3-Sampled", "1") textMap.put("X-B3-Extra-Baggage", "some=baggage;more=baggage") - val spanContext = extendedB3Codec.extract(textMap).value + val spanContext = extendedB3Codec.decode(textMap, Context.Empty).get(Span.ContextKey).context() spanContext.traceID.string shouldBe "1234" spanContext.spanID.string shouldBe "4321" spanContext.parentID.string shouldBe "2222" spanContext.samplingDecision shouldBe SamplingDecision.Sample - spanContext.baggage.getAll() should contain allOf( - "some" -> "baggage", - "more" -> "baggage" - ) } "decode the sampling decision based on the X-B3-Sampled header" in { @@ -93,19 +74,27 @@ class ExtendedB3SpanContextCodecSpec extends WordSpecLike with Matchers with Opt 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 + extendedB3Codec.decode(sampledTextMap, Context.Empty) + .get(Span.ContextKey).context().samplingDecision shouldBe SamplingDecision.Sample + + extendedB3Codec.decode(notSampledTextMap, Context.Empty) + .get(Span.ContextKey).context().samplingDecision shouldBe SamplingDecision.DoNotSample + + extendedB3Codec.decode(noSamplingTextMap, Context.Empty) + .get(Span.ContextKey).context().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 + val context = testContext() + val sampledSpanContext = context.get(Span.ContextKey).context() + val notSampledSpanContext = Context.Empty.withKey(Span.ContextKey, + Span.Remote(sampledSpanContext.copy(samplingDecision = SamplingDecision.DoNotSample))) + val unknownSamplingSpanContext = Context.Empty.withKey(Span.ContextKey, + Span.Remote(sampledSpanContext.copy(samplingDecision = SamplingDecision.Unknown))) + + extendedB3Codec.encode(context).get("X-B3-Sampled").value shouldBe("1") + extendedB3Codec.encode(notSampledSpanContext).get("X-B3-Sampled").value shouldBe("0") + extendedB3Codec.encode(unknownSamplingSpanContext).get("X-B3-Sampled") shouldBe empty } "use the Debug flag to override the sampling decision, if provided." in { @@ -115,7 +104,7 @@ class ExtendedB3SpanContextCodecSpec extends WordSpecLike with Matchers with Opt textMap.put("X-B3-Sampled", "0") textMap.put("X-B3-Flags", "1") - val spanContext = extendedB3Codec.extract(textMap).value + val spanContext = extendedB3Codec.decode(textMap, Context.Empty).get(Span.ContextKey).context() spanContext.samplingDecision shouldBe SamplingDecision.Sample } @@ -125,7 +114,7 @@ class ExtendedB3SpanContextCodecSpec extends WordSpecLike with Matchers with Opt textMap.put("X-B3-SpanId", "4321") textMap.put("X-B3-Flags", "1") - val spanContext = extendedB3Codec.extract(textMap).value + val spanContext = extendedB3Codec.decode(textMap, Context.Empty).get(Span.ContextKey).context() spanContext.samplingDecision shouldBe SamplingDecision.Sample } @@ -134,12 +123,11 @@ class ExtendedB3SpanContextCodecSpec extends WordSpecLike with Matchers with Opt textMap.put("X-B3-TraceId", "1234") textMap.put("X-B3-SpanId", "4321") - val spanContext = extendedB3Codec.extract(textMap).value + val spanContext = extendedB3Codec.decode(textMap, Context.Empty).get(Span.ContextKey).context() spanContext.traceID.string shouldBe "1234" spanContext.spanID.string shouldBe "4321" spanContext.parentID shouldBe IdentityProvider.NoIdentifier spanContext.samplingDecision shouldBe SamplingDecision.Unknown - spanContext.baggage.getAll() shouldBe empty } "do not extract a SpanContext if Trace ID and Span ID are not provided" in { @@ -157,34 +145,26 @@ class ExtendedB3SpanContextCodecSpec extends WordSpecLike with Matchers with Opt 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 + extendedB3Codec.decode(onlyTraceID, Context.Empty).get(Span.ContextKey) shouldBe Span.Empty + extendedB3Codec.decode(onlySpanID, Context.Empty).get(Span.ContextKey) shouldBe Span.Empty + extendedB3Codec.decode(noIds, Context.Empty).get(Span.ContextKey) shouldBe Span.Empty } - "round trip a SpanContext from TextMap -> SpanContext -> TextMap" in { + "round trip a Span from TextMap -> Context -> TextMap" 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-Extra-Baggage", "some=baggage;more=baggage") - val spanContext = extendedB3Codec.extract(textMap).value - val injectTextMap = extendedB3Codec.inject(spanContext) + val context = extendedB3Codec.decode(textMap, Context.Empty) + val injectTextMap = extendedB3Codec.encode(context) textMap.values.toSeq should contain theSameElementsAs(injectTextMap.values.toSeq) } - "round trip a baggage that has special characters in there" in { - val spanContext = testSpanContext() - spanContext.baggage.add("key-with-!specials", "value=with~spec;als") - - val textMap = extendedB3Codec.inject(spanContext) - val extractedSpanContext = extendedB3Codec.extract(textMap).value - extractedSpanContext.baggage.getAll().values.toSeq should contain theSameElementsAs(spanContext.baggage.getAll().values.toSeq) - } - + /* + // TODO: Should we be supporting this use case? maybe even have the concept of Debug requests ourselves? "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") @@ -192,19 +172,22 @@ class ExtendedB3SpanContextCodecSpec extends WordSpecLike with Matchers with Opt 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) injectTextMap.get("X-B3-Flags").value shouldBe("1") - } + }*/ } - def testSpanContext(): SpanContext = - createSpanContext().copy( + def testContext(): Context = { + val 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)) ) + + Context.create().withKey(Span.ContextKey, Span.Remote(spanContext)) + } + } \ No newline at end of file diff --git a/kamon-core/src/test/scala/kamon/trace/LocalSpanSpec.scala b/kamon-core/src/test/scala/kamon/trace/LocalSpanSpec.scala new file mode 100644 index 00000000..e24f8727 --- /dev/null +++ b/kamon-core/src/test/scala/kamon/trace/LocalSpanSpec.scala @@ -0,0 +1,100 @@ +package kamon.trace + +import kamon.testkit.{MetricInspection, Reconfigure, TestSpanReporter} +import kamon.util.Registration +import kamon.Kamon +import kamon.trace.Span.{Annotation, TagValue} +import org.scalatest.concurrent.Eventually +import org.scalatest.{BeforeAndAfterAll, Matchers, OptionValues, WordSpec} +import org.scalatest.time.SpanSugar._ + +class LocalSpanSpec extends WordSpec with Matchers with BeforeAndAfterAll with Eventually with OptionValues + with Reconfigure with MetricInspection { + + "a real span" when { + "sampled and finished" should { + "be sent to the Span reporters" in { + Kamon.buildSpan("test-span") + .withSpanTag("test", "value") + .withStartTimestamp(100) + .start() + .finish(200) + + eventually(timeout(2 seconds)) { + val finishedSpan = reporter.nextSpan().value + finishedSpan.operationName shouldBe("test-span") + finishedSpan.startTimestampMicros shouldBe 100 + finishedSpan.endTimestampMicros shouldBe 200 + finishedSpan.tags should contain("test" -> TagValue.String("value")) + } + } + + "pass all the tags, annotations and baggage to the FinishedSpan instance when started and finished" in { + Kamon.buildSpan("full-span") + .withSpanTag("builder-string-tag", "value") + .withSpanTag("builder-boolean-tag-true", true) + .withSpanTag("builder-boolean-tag-false", false) + .withSpanTag("builder-number-tag", 42) + .withStartTimestamp(100) + .start() + .addSpanTag("span-string-tag", "value") + .addSpanTag("span-boolean-tag-true", true) + .addSpanTag("span-boolean-tag-false", false) + .addSpanTag("span-number-tag", 42) + .annotate("simple-annotation") + .annotate("regular-annotation", Map("data" -> "something")) + .annotate(4200, "custom-annotation-1", Map("custom" -> "yes-1")) + .annotate(Annotation(4201, "custom-annotation-2", Map("custom" -> "yes-2"))) + .setOperationName("fully-populated-span") + .finish(200) + + eventually(timeout(2 seconds)) { + val finishedSpan = reporter.nextSpan().value + finishedSpan.operationName shouldBe ("fully-populated-span") + finishedSpan.startTimestampMicros shouldBe 100 + finishedSpan.endTimestampMicros shouldBe 200 + finishedSpan.tags should contain allOf( + "builder-string-tag" -> TagValue.String("value"), + "builder-boolean-tag-true" -> TagValue.True, + "builder-boolean-tag-false" -> TagValue.False, + "builder-number-tag" -> TagValue.Number(42), + "span-string-tag" -> TagValue.String("value"), + "span-boolean-tag-true" -> TagValue.True, + "span-boolean-tag-false" -> TagValue.False, + "span-number-tag" -> TagValue.Number(42) + ) + + finishedSpan.annotations.length shouldBe (4) + val annotations = finishedSpan.annotations.groupBy(_.name) + annotations.keys should contain allOf( + "simple-annotation", + "regular-annotation", + "custom-annotation-1", + "custom-annotation-2" + ) + + val customAnnotationOne = annotations("custom-annotation-1").head + customAnnotationOne.timestampMicros shouldBe (4200) + customAnnotationOne.fields shouldBe (Map("custom" -> "yes-1")) + + val customAnnotationTwo = annotations("custom-annotation-2").head + customAnnotationTwo.timestampMicros shouldBe (4201) + customAnnotationTwo.fields shouldBe (Map("custom" -> "yes-2")) + } + } + } + } + + @volatile var registration: Registration = _ + val reporter = new TestSpanReporter() + + override protected def beforeAll(): Unit = { + enableFastSpanFlushing() + sampleAlways() + registration = Kamon.addReporter(reporter) + } + + override protected def afterAll(): Unit = { + registration.cancel() + } +} diff --git a/kamon-core/src/test/scala/kamon/trace/RealSpanSpec.scala b/kamon-core/src/test/scala/kamon/trace/RealSpanSpec.scala deleted file mode 100644 index e019e15a..00000000 --- a/kamon-core/src/test/scala/kamon/trace/RealSpanSpec.scala +++ /dev/null @@ -1,180 +0,0 @@ -package kamon.trace - -import kamon.testkit.{MetricInspection, Reconfigure, TestSpanReporter} -import kamon.util.Registration -import kamon.Kamon -import kamon.trace.Span.{Annotation, TagValue} -import org.scalatest.concurrent.Eventually -import org.scalatest.{BeforeAndAfterAll, Matchers, OptionValues, WordSpec} -import org.scalatest.time.SpanSugar._ - -class RealSpanSpec extends WordSpec with Matchers with BeforeAndAfterAll with Eventually with OptionValues - with Reconfigure with MetricInspection { - - "a real span" when { - "sampled and finished" should { - "be sent to the Span reporters" in { - Kamon.buildSpan("test-span") - .withSpanTag("test", "value") - .withStartTimestamp(100) - .start() - .finish(200) - - eventually(timeout(2 seconds)) { - val finishedSpan = reporter.nextSpan().value - finishedSpan.operationName shouldBe("test-span") - finishedSpan.startTimestampMicros shouldBe 100 - finishedSpan.endTimestampMicros shouldBe 200 - finishedSpan.tags should contain("test" -> TagValue.String("value")) - } - } - - "pass all the tags, annotations and baggage to the FinishedSpan instance when started and finished" in { - Kamon.buildSpan("full-span") - .withSpanTag("builder-string-tag", "value") - .withSpanTag("builder-boolean-tag-true", true) - .withSpanTag("builder-boolean-tag-false", false) - .withSpanTag("builder-number-tag", 42) - .withStartTimestamp(100) - .start() - .addBaggage("baggage", "value") - .addSpanTag("span-string-tag", "value") - .addSpanTag("span-boolean-tag-true", true) - .addSpanTag("span-boolean-tag-false", false) - .addSpanTag("span-number-tag", 42) - .annotate("simple-annotation") - .annotate("regular-annotation", Map("data" -> "something")) - .annotate(4200, "custom-annotation-1", Map("custom" -> "yes-1")) - .annotate(Annotation(4201, "custom-annotation-2", Map("custom" -> "yes-2"))) - .setOperationName("fully-populated-span") - .finish(200) - - eventually(timeout(2 seconds)) { - val finishedSpan = reporter.nextSpan().value - finishedSpan.operationName shouldBe ("fully-populated-span") - finishedSpan.startTimestampMicros shouldBe 100 - finishedSpan.endTimestampMicros shouldBe 200 - finishedSpan.tags should contain allOf( - "builder-string-tag" -> TagValue.String("value"), - "builder-boolean-tag-true" -> TagValue.True, - "builder-boolean-tag-false" -> TagValue.False, - "builder-number-tag" -> TagValue.Number(42), - "span-string-tag" -> TagValue.String("value"), - "span-boolean-tag-true" -> TagValue.True, - "span-boolean-tag-false" -> TagValue.False, - "span-number-tag" -> TagValue.Number(42) - ) - - finishedSpan.annotations.length shouldBe (4) - val annotations = finishedSpan.annotations.groupBy(_.name) - annotations.keys should contain allOf( - "simple-annotation", - "regular-annotation", - "custom-annotation-1", - "custom-annotation-2" - ) - - val customAnnotationOne = annotations("custom-annotation-1").head - customAnnotationOne.timestampMicros shouldBe (4200) - customAnnotationOne.fields shouldBe (Map("custom" -> "yes-1")) - - val customAnnotationTwo = annotations("custom-annotation-2").head - customAnnotationTwo.timestampMicros shouldBe (4201) - customAnnotationTwo.fields shouldBe (Map("custom" -> "yes-2")) - - finishedSpan.context.baggage.getAll() should contain( - "baggage" -> "value" - ) - } - } - - "pass all the tags, annotations and baggage to the FinishedSpan instance when started, activated and finished" in { - val scope = Kamon.activate(Kamon.buildSpan("full-span") - .withSpanTag("builder-string-tag", "value") - .withSpanTag("builder-boolean-tag-true", true) - .withSpanTag("builder-boolean-tag-false", false) - .withSpanTag("builder-number-tag", 42) - .withStartTimestamp(100) - .start()) - - Kamon.activeSpan() - .addBaggage("baggage", "value") - .addSpanTag("span-string-tag", "value") - .addSpanTag("span-boolean-tag-true", true) - .addSpanTag("span-boolean-tag-false", false) - .addSpanTag("span-number-tag", 42) - .annotate("simple-annotation") - .annotate("regular-annotation", Map("data" -> "something")) - .annotate(4200, "custom-annotation-1", Map("custom" -> "yes-1")) - .annotate(Annotation(4201, "custom-annotation-2", Map("custom" -> "yes-2"))) - .setOperationName("fully-populated-active-span") - .finish(200) - - scope.close() - - eventually(timeout(2 seconds)) { - val finishedSpan = reporter.nextSpan().value - finishedSpan.operationName shouldBe ("fully-populated-active-span") - finishedSpan.startTimestampMicros shouldBe 100 - finishedSpan.endTimestampMicros shouldBe 200 - finishedSpan.tags should contain allOf( - "builder-string-tag" -> TagValue.String("value"), - "builder-boolean-tag-true" -> TagValue.True, - "builder-boolean-tag-false" -> TagValue.False, - "builder-number-tag" -> TagValue.Number(42), - "span-string-tag" -> TagValue.String("value"), - "span-boolean-tag-true" -> TagValue.True, - "span-boolean-tag-false" -> TagValue.False, - "span-number-tag" -> TagValue.Number(42) - ) - - finishedSpan.annotations.length shouldBe (4) - val annotations = finishedSpan.annotations.groupBy(_.name) - annotations.keys should contain allOf( - "simple-annotation", - "regular-annotation", - "custom-annotation-1", - "custom-annotation-2" - ) - - val customAnnotationOne = annotations("custom-annotation-1").head - customAnnotationOne.timestampMicros shouldBe (4200) - customAnnotationOne.fields shouldBe (Map("custom" -> "yes-1")) - - val customAnnotationTwo = annotations("custom-annotation-2").head - customAnnotationTwo.timestampMicros shouldBe (4201) - customAnnotationTwo.fields shouldBe (Map("custom" -> "yes-2")) - - finishedSpan.context.baggage.getAll() should contain( - "baggage" -> "value" - ) - } - } - - "allow storing and retrieving baggage items" in { - val span = Kamon.buildSpan("span-with-baggage").start() - span.addBaggage("my-baggage", "value-1") - span.addBaggage("my-baggage", "value-2") - span.addBaggage("my-other-baggage", "value-3") - - span.context().baggage.getAll() should contain only( - "my-baggage" -> "value-2", - "my-other-baggage" -> "value-3" - ) - } - } - } - - @volatile var registration: Registration = _ - val reporter = new TestSpanReporter() - - override protected def beforeAll(): Unit = { - enableFastSpanFlushing() - sampleAlways() - registration = Kamon.addReporter(reporter) - } - - override protected def afterAll(): Unit = { - registration.cancel() - } -} diff --git a/kamon-core/src/test/scala/kamon/trace/TracerSpec.scala b/kamon-core/src/test/scala/kamon/trace/TracerSpec.scala index 5abfe723..fb5bb313 100644 --- a/kamon-core/src/test/scala/kamon/trace/TracerSpec.scala +++ b/kamon-core/src/test/scala/kamon/trace/TracerSpec.scala @@ -2,10 +2,9 @@ package kamon.trace import com.typesafe.config.ConfigFactory import kamon.Kamon +import kamon.context.Context 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 with SpanBuilding with OptionValues { @@ -42,33 +41,16 @@ class TracerSpec extends WordSpec with Matchers with SpanBuilding with OptionVal ("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 { + + "automatically take the Span from the current Context as parent" in { val parent = tracer.buildSpan("myOperation").start() - val child = Kamon.withActiveSpan(parent) { + val child = Kamon.withContext(Context.create(Span.ContextKey, parent)) { tracer.buildSpan("childOperation").asChildOf(parent).start() } @@ -79,7 +61,7 @@ class TracerSpec extends WordSpec with Matchers with SpanBuilding with OptionVal "ignore the currently active span as parent if explicitly requested" in { val parent = tracer.buildSpan("myOperation").start() - val child = Kamon.withActiveSpan(parent) { + val child = Kamon.withContext(Context.create(Span.ContextKey, parent)) { tracer.buildSpan("childOperation").ignoreActiveSpan().start() } @@ -93,53 +75,6 @@ class TracerSpec extends WordSpec with Matchers with SpanBuilding with OptionVal 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() @@ -148,12 +83,12 @@ class TracerSpec extends WordSpec with Matchers with SpanBuilding with OptionVal .withFallback(Kamon.config()) } - val remoteParent = createSpanContext().copy(source = Source.Remote) + val remoteParent = Span.Remote(createSpanContext()) 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 + childData.context().traceID shouldBe remoteParent.context.traceID + childData.context().parentID shouldBe remoteParent.context.parentID + childData.context().spanID shouldBe remoteParent.context.spanID Kamon.reconfigure(previousConfig) } -- cgit v1.2.3