1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
|
<?php
namespace Wikimedia\Telemetry;
use Wikimedia\Assert\Assert;
/**
* @since 1.43
* @internal
*/
class Tracer implements TracerInterface {
/**
* The length of a trace ID in bytes, as specified by the OTEL specification.
*/
private const TRACE_ID_BYTES_LENGTH = 16;
/**
* The length of a span ID in bytes, as specified by the OTEL specification.
*/
private const SPAN_ID_BYTES_LENGTH = 8;
private Clock $clock;
private SamplerInterface $sampler;
private ExporterInterface $exporter;
private TracerState $tracerState;
/**
* Whether tracing has been explicitly ended by calling shutdown() on this instance.
* @var bool
*/
private bool $wasShutdown = false;
public function __construct(
Clock $clock,
SamplerInterface $sampler,
ExporterInterface $exporter,
TracerState $tracerState
) {
$this->clock = $clock;
$this->sampler = $sampler;
$this->exporter = $exporter;
$this->tracerState = $tracerState;
}
/** @inheritDoc */
public function createSpan( string $spanName ): SpanInterface {
$activeSpanContext = $this->tracerState->getActiveSpanContext();
// Gracefully handle attempts to instrument code after shutdown() was called.
if ( !$this->wasShutdown ) {
Assert::precondition(
$activeSpanContext !== null,
'Attempted to create a span with the currently active span as the implicit parent, ' .
'but no span was active. Use createRootSpan() to create a span with no parent (i.e. a root span).'
);
}
return $this->newSpan( $spanName, $activeSpanContext );
}
/** @inheritDoc */
public function createRootSpan( string $spanName ): SpanInterface {
return $this->newSpan( $spanName, null );
}
/** @inheritDoc */
public function createSpanWithParent( string $spanName, SpanContext $parentSpanContext ): SpanInterface {
return $this->newSpan( $spanName, $parentSpanContext );
}
private function newSpan( string $spanName, ?SpanContext $parentSpanContext ): SpanInterface {
$traceId = $parentSpanContext !== null ?
$parentSpanContext->getTraceId() : $this->generateId( self::TRACE_ID_BYTES_LENGTH );
$spanId = $this->generateId( self::SPAN_ID_BYTES_LENGTH );
$sampled = $this->sampler->shouldSample( $parentSpanContext );
$spanContext = new SpanContext(
$traceId,
$spanId,
$parentSpanContext !== null ? $parentSpanContext->getSpanId() : null,
$spanName,
$sampled
);
if ( $this->wasShutdown || !$sampled ) {
return new NoopSpan( $spanContext );
}
return new Span(
$this->clock,
$this->tracerState,
$spanContext
);
}
/** @inheritDoc */
public function shutdown(): void {
$this->wasShutdown = true;
$this->exporter->export( $this->tracerState );
}
/**
* Generate a valid hexadecimal string for use as a trace or span ID, with the given length in bytes.
*
* @param int $bytesLength The byte length of the ID
* @return string The ID as a hexadecimal string
*/
private function generateId( int $bytesLength ): string {
return bin2hex( random_bytes( $bytesLength ) );
}
}
|