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 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