diff --git a/src/Zipkin/DefaultTracing.php b/src/Zipkin/DefaultTracing.php index 204675ab..a2d0dd27 100644 --- a/src/Zipkin/DefaultTracing.php +++ b/src/Zipkin/DefaultTracing.php @@ -42,7 +42,8 @@ public function __construct( CurrentTraceContext $currentTraceContext, bool $isNoop, Propagation $propagation, - bool $supportsJoin + bool $supportsJoin, + bool $alwaysReportSpans = false ) { $this->tracer = new Tracer( $localEndpoint, @@ -51,7 +52,8 @@ public function __construct( $usesTraceId128bits, $currentTraceContext, $isNoop, - $supportsJoin + $supportsJoin, + $alwaysReportSpans ); $this->propagation = $propagation; diff --git a/src/Zipkin/Recording/Span.php b/src/Zipkin/Recording/Span.php index 875725d3..64955e13 100644 --- a/src/Zipkin/Recording/Span.php +++ b/src/Zipkin/Recording/Span.php @@ -127,13 +127,19 @@ final class Span */ private $localEndpoint; + /** + * @var bool|null + */ + private $isSampled; + private function __construct( string $traceId, ?string $parentId, string $spanId, bool $debug, bool $shared, - Endpoint $localEndpoint + Endpoint $localEndpoint, + ?bool $isSampled = false ) { $this->traceId = $traceId; $this->parentId = $parentId; @@ -141,6 +147,7 @@ private function __construct( $this->debug = $debug; $this->shared = $shared; $this->localEndpoint = $localEndpoint; + $this->isSampled = $isSampled; } /** @@ -156,7 +163,8 @@ public static function createFromContext(TraceContext $context, Endpoint $localE $context->getSpanId(), $context->isDebug(), $context->isShared(), - $localEndpoint + $localEndpoint, + $context->isSampled() ); } @@ -225,6 +233,14 @@ public function setRemoteEndpoint(Endpoint $remoteEndpoint): void $this->remoteEndpoint = $remoteEndpoint; } + /** + * @return bool + */ + public function isSampled(): bool + { + return $this->isSampled === true; + } + /** * Completes and reports the span * diff --git a/src/Zipkin/Tracer.php b/src/Zipkin/Tracer.php index ac8a8c2d..96f9af86 100644 --- a/src/Zipkin/Tracer.php +++ b/src/Zipkin/Tracer.php @@ -48,6 +48,11 @@ final class Tracer */ private $supportsJoin; + /** + * @var bool + */ + private $alwaysReportSpans; + /** * @param Endpoint $localEndpoint * @param Reporter $reporter @@ -55,6 +60,7 @@ final class Tracer * @param bool $usesTraceId128bits * @param CurrentTraceContext $currentTraceContext * @param bool $isNoop + * @param bool $alwaysReportSpans */ public function __construct( Endpoint $localEndpoint, @@ -63,7 +69,8 @@ public function __construct( bool $usesTraceId128bits, CurrentTraceContext $currentTraceContext, bool $isNoop, - bool $supportsJoin = true + bool $supportsJoin = true, + bool $alwaysReportSpans = false ) { $this->recorder = new Recorder($localEndpoint, $reporter, $isNoop); $this->sampler = $sampler; @@ -71,6 +78,7 @@ public function __construct( $this->currentTraceContext = $currentTraceContext; $this->isNoop = $isNoop; $this->supportsJoin = $supportsJoin; + $this->alwaysReportSpans = $alwaysReportSpans; } /** @@ -393,7 +401,7 @@ private function ensureSampled(TraceContext $context): Span */ private function toSpan(TraceContext $context): Span { - if (!$this->isNoop && $context->isSampled()) { + if (!$this->isNoop && ($context->isSampled() || $this->alwaysReportSpans)) { return new RealSpan($context, $this->recorder); } diff --git a/src/Zipkin/TracingBuilder.php b/src/Zipkin/TracingBuilder.php index 6bdce466..64844af3 100644 --- a/src/Zipkin/TracingBuilder.php +++ b/src/Zipkin/TracingBuilder.php @@ -58,6 +58,11 @@ class TracingBuilder */ private $propagation = null; + /** + * @var bool + */ + private $alwaysReportSpans = false; + public static function create(): self { return new self(); @@ -183,6 +188,23 @@ public function supportingJoin(bool $supportsJoin): self return $this; } + /** + * True means that spans will always be recorded, even if the current trace is not sampled. + * Defaults to False. + * + * This has the side effect that your reporter will receive all spans, irrespective of the + * sampling decision. Use this if you want to have some custom smart logic in the reporter + * that needs to have access to both sampled and unsampled traces. + * + * @param bool $alwaysReportSpans + * @return $this + */ + public function alwaysReportingSpans(bool $alwaysReportSpans): self + { + $this->alwaysReportSpans = $alwaysReportSpans; + return $this; + } + /** * @return DefaultTracing */ @@ -209,7 +231,8 @@ public function build(): Tracing $currentTraceContext, $this->isNoop, $propagation, - $this->supportsJoin && $propagation->supportsJoin() + $this->supportsJoin && $propagation->supportsJoin(), + $this->alwaysReportSpans ); } } diff --git a/tests/Unit/TracingBuilderTest.php b/tests/Unit/TracingBuilderTest.php index 75da0252..7fec2db0 100644 --- a/tests/Unit/TracingBuilderTest.php +++ b/tests/Unit/TracingBuilderTest.php @@ -5,6 +5,7 @@ use Zipkin\Tracing; use Zipkin\Endpoint; use Zipkin\Reporters\Noop; +use Zipkin\Reporters\InMemory; use Zipkin\TracingBuilder; use Zipkin\Propagation\Getter; use Zipkin\Propagation\Setter; @@ -85,4 +86,58 @@ private function randomBool() { return (bool) mt_rand(0, 1); } + + public function testAlwaysEmitSpans() + { + // If `alwaysReportingSpans(true)` is called, we should be emitting the + // spans even if the trace isn't sampled + $endpoint = Endpoint::createAsEmpty(); + $reporter = new InMemory(); + $sampler = BinarySampler::createAsNeverSample(); + + $tracing = TracingBuilder::create() + ->havingLocalServiceName(self::SERVICE_NAME) + ->havingLocalEndpoint($endpoint) + ->havingReporter($reporter) + ->havingSampler($sampler) + ->alwaysReportingSpans(true) + ->build(); + $tracer = $tracing->getTracer(); + + $span = $tracer->newTrace(); + $span->setName('test'); + $span->start(); + $span->finish(); + + $tracer->flush(); + $spans = $reporter->flush(); + $this->assertCount(1, $spans); + $this->assertFalse($spans[0]->isSampled()); + } + + public function testDontEmitByDefault() + { + // By default, let's verify that we don't emit any span if the + // trace isn't sampled. + $endpoint = Endpoint::createAsEmpty(); + $reporter = new InMemory(); + $sampler = BinarySampler::createAsNeverSample(); + + $tracing = TracingBuilder::create() + ->havingLocalServiceName(self::SERVICE_NAME) + ->havingLocalEndpoint($endpoint) + ->havingReporter($reporter) + ->havingSampler($sampler) + ->build(); + $tracer = $tracing->getTracer(); + + $span = $tracer->newTrace(); + $span->setName('test'); + $span->start(); + $span->finish(); + + $tracer->flush(); + $spans = $reporter->flush(); + $this->assertCount(0, $spans); + } }