477 lines
9.9 KiB
PHP
477 lines
9.9 KiB
PHP
<?php
|
|
|
|
namespace Jaeger;
|
|
|
|
use Jaeger\Thrift\Agent\Zipkin\AnnotationType;
|
|
use Jaeger\Thrift\Agent\Zipkin\BinaryAnnotation;
|
|
use OpenTracing\Span as OTSpan;
|
|
use DateTime;
|
|
use DateTimeInterface;
|
|
use OpenTracing\SpanContext as OTSpanContext;
|
|
use const OpenTracing\Tags\COMPONENT;
|
|
use const OpenTracing\Tags\PEER_HOST_IPV4;
|
|
use const OpenTracing\Tags\PEER_PORT;
|
|
use const OpenTracing\Tags\PEER_SERVICE;
|
|
use const OpenTracing\Tags\SPAN_KIND;
|
|
use const OpenTracing\Tags\SPAN_KIND_MESSAGE_BUS_CONSUMER;
|
|
use const OpenTracing\Tags\SPAN_KIND_MESSAGE_BUS_PRODUCER;
|
|
use const OpenTracing\Tags\SPAN_KIND_RPC_CLIENT;
|
|
use const OpenTracing\Tags\SPAN_KIND_RPC_SERVER;
|
|
|
|
class Span implements OTSpan
|
|
{
|
|
/**
|
|
* @var Tracer
|
|
*/
|
|
private $tracer;
|
|
|
|
/**
|
|
* @var SpanContext
|
|
*/
|
|
private $context;
|
|
|
|
/**
|
|
* @var string
|
|
*/
|
|
private $operationName;
|
|
|
|
/**
|
|
* @var int|float|DateTime|null
|
|
*/
|
|
private $startTime;
|
|
|
|
/**
|
|
* @var int|float|DateTime|null
|
|
*/
|
|
private $endTime;
|
|
|
|
/**
|
|
* SPAN_RPC_CLIENT
|
|
* @var null|string
|
|
*/
|
|
private $kind;
|
|
|
|
/**
|
|
* @var array|null
|
|
*/
|
|
public $peer;
|
|
|
|
/**
|
|
* @var string|null
|
|
*/
|
|
private $component;
|
|
|
|
/**
|
|
* @var array
|
|
*/
|
|
private $logs = [];
|
|
|
|
/**
|
|
* @var BinaryAnnotation[]
|
|
*/
|
|
public $tags = [];
|
|
|
|
/**
|
|
* @var bool
|
|
*/
|
|
private $debug = false;
|
|
|
|
/**
|
|
* Span constructor.
|
|
* @param SpanContext $context
|
|
* @param Tracer $tracer
|
|
* @param string $operationName
|
|
* @param array $tags
|
|
* @param int|float|DateTime|null $startTime
|
|
*/
|
|
public function __construct(
|
|
SpanContext $context,
|
|
Tracer $tracer,
|
|
string $operationName,
|
|
array $tags = [],
|
|
$startTime = null
|
|
) {
|
|
$this->context = $context;
|
|
$this->tracer = $tracer;
|
|
|
|
$this->operationName = $operationName;
|
|
$this->startTime = $this->microTime($startTime);
|
|
$this->endTime = null;
|
|
$this->kind = null;
|
|
$this->peer = null;
|
|
$this->component = null;
|
|
|
|
foreach ($tags as $key => $value) {
|
|
$this->setTag($key, $value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Converts time to microtime int
|
|
* - int represents microseconds
|
|
* - float represents seconds
|
|
*
|
|
* @param int|float|DateTime|null $time
|
|
* @return int
|
|
*/
|
|
protected function microTime($time): int
|
|
{
|
|
if ($time === null) {
|
|
return $this->timestampMicro();
|
|
}
|
|
|
|
if ($time instanceof \DateTimeInterface) {
|
|
return (int)round($time->format('U.u') * 1000000, 0);
|
|
}
|
|
|
|
if (is_int($time)) {
|
|
return $time;
|
|
}
|
|
|
|
if (is_float($time)) {
|
|
return (int)round($time * 1000000, 0);
|
|
}
|
|
|
|
throw new \InvalidArgumentException(sprintf(
|
|
'Time should be one of the types int|float|DateTime|null, got %s.',
|
|
gettype($time)
|
|
));
|
|
}
|
|
|
|
/**
|
|
* @return Tracer
|
|
*/
|
|
public function getTracer(): Tracer
|
|
{
|
|
return $this->tracer;
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function isDebug(): bool
|
|
{
|
|
return $this->debug;
|
|
}
|
|
|
|
/**
|
|
* @return int
|
|
*/
|
|
public function getStartTime(): int
|
|
{
|
|
return $this->startTime;
|
|
}
|
|
|
|
/**
|
|
* @return int|null
|
|
*/
|
|
public function getEndTime()
|
|
{
|
|
return $this->endTime;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getOperationName(): string
|
|
{
|
|
return $this->operationName;
|
|
}
|
|
|
|
/**
|
|
* @return mixed
|
|
*/
|
|
public function getComponent()
|
|
{
|
|
// TODO
|
|
return $this->component;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*
|
|
* @return SpanContext
|
|
*/
|
|
public function getContext(): OTSpanContext
|
|
{
|
|
return $this->context;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function finish($finishTime = null, array $logRecords = []): void
|
|
{
|
|
if (!$this->isSampled()) {
|
|
return;
|
|
}
|
|
|
|
foreach ($logRecords as $logRecord) {
|
|
$this->log($logRecord);
|
|
}
|
|
|
|
$this->endTime = $this->microTime($finishTime);
|
|
$this->tracer->reportSpan($this);
|
|
}
|
|
|
|
/**
|
|
* Returns true if the trace should be measured.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function isSampled(): bool
|
|
{
|
|
$context = $this->getContext();
|
|
|
|
return ($context->getFlags() & SAMPLED_FLAG) == SAMPLED_FLAG;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function overwriteOperationName(string $newOperationName): void
|
|
{
|
|
// TODO log warning
|
|
$this->operationName = $newOperationName;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*
|
|
* @param array $tags
|
|
* @return void
|
|
*/
|
|
public function setTags($tags)
|
|
{
|
|
foreach ($tags as $key => $value) {
|
|
$this->setTag($key, $value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function setTag(string $key, $value): void
|
|
{
|
|
if ($this->isSampled()) {
|
|
$special = self::SPECIAL_TAGS[$key] ?? null;
|
|
$handled = false;
|
|
|
|
if ($special !== null && is_callable([$this, $special])) {
|
|
$handled = $this->$special($value);
|
|
}
|
|
|
|
if (!$handled) {
|
|
$tag = $this->makeTag($key, $value);
|
|
$this->tags[$key] = $tag;
|
|
}
|
|
}
|
|
}
|
|
|
|
const SPECIAL_TAGS = [
|
|
PEER_SERVICE => 'setPeerService',
|
|
PEER_HOST_IPV4 => 'setPeerHostIpv4',
|
|
PEER_PORT => 'setPeerPort',
|
|
SPAN_KIND => 'setSpanKind',
|
|
COMPONENT => 'setComponent',
|
|
];
|
|
|
|
/**
|
|
* Sets a low-cardinality identifier of the module, library,
|
|
* or package that is generating a span.
|
|
*
|
|
* @see Span::setTag()
|
|
*
|
|
* @param string $value
|
|
* @return bool
|
|
*/
|
|
private function setComponent($value): bool
|
|
{
|
|
$this->component = $value;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
private function setSpanKind($value): bool
|
|
{
|
|
$validSpanKinds = [
|
|
SPAN_KIND_RPC_CLIENT,
|
|
SPAN_KIND_RPC_SERVER,
|
|
SPAN_KIND_MESSAGE_BUS_CONSUMER,
|
|
SPAN_KIND_MESSAGE_BUS_PRODUCER,
|
|
];
|
|
|
|
if ($value === null || in_array($value, $validSpanKinds, true)) {
|
|
$this->kind = $value;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @return string|null
|
|
*/
|
|
public function getKind(): ?string
|
|
{
|
|
return $this->kind;
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
private function setPeerPort($value): bool
|
|
{
|
|
if ($this->peer === null) {
|
|
$this->peer = ['port' => $value];
|
|
} else {
|
|
$this->peer['port'] = $value;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
private function setPeerHostIpv4($value): bool
|
|
{
|
|
if ($this->peer === null) {
|
|
$this->peer = ['ipv4' => $value];
|
|
} else {
|
|
$this->peer['ipv4'] = $value;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
private function setPeerService($value): bool
|
|
{
|
|
if ($this->peer === null) {
|
|
$this->peer = ['service_name' => $value];
|
|
} else {
|
|
$this->peer['service_name'] = $value;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function isRpc(): bool
|
|
{
|
|
return $this->kind == SPAN_KIND_RPC_CLIENT || $this->kind == SPAN_KIND_RPC_SERVER;
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function isRpcClient(): bool
|
|
{
|
|
return $this->kind == SPAN_KIND_RPC_CLIENT;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function log(array $fields = [], $timestamp = null): void
|
|
{
|
|
$timestamp = $this->microTime($timestamp);
|
|
if ($timestamp < $this->getStartTime()) {
|
|
$timestamp = $this->timestampMicro();
|
|
}
|
|
|
|
$this->logs[] = [
|
|
'fields' => $fields,
|
|
'timestamp' => $timestamp,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Returns the logs.
|
|
*
|
|
* [
|
|
* [
|
|
* 'timestamp' => timestamp in microsecond,
|
|
* 'fields' => [
|
|
* 'error' => 'message',
|
|
* ]
|
|
* ]
|
|
* ]
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getLogs(): array
|
|
{
|
|
return $this->logs;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function addBaggageItem(string $key, string $value): void
|
|
{
|
|
$this->context = $this->context->withBaggageItem($key, $value);
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function getBaggageItem(string $key): ?string
|
|
{
|
|
return $this->context->getBaggageItem($key);
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function getTags(): array
|
|
{
|
|
return $this->tags;
|
|
}
|
|
|
|
/**
|
|
* @return int
|
|
*/
|
|
private function timestampMicro(): int
|
|
{
|
|
return round(microtime(true) * 1000000);
|
|
}
|
|
|
|
/**
|
|
* @param string $key
|
|
* @param mixed $value
|
|
* @return BinaryAnnotation
|
|
*/
|
|
private function makeTag(string $key, $value): BinaryAnnotation
|
|
{
|
|
$valueType = gettype($value);
|
|
$annotationType = null;
|
|
switch ($valueType) {
|
|
case "boolean":
|
|
$annotationType = AnnotationType::BOOL;
|
|
break;
|
|
case "integer":
|
|
$annotationType = AnnotationType::I64;
|
|
break;
|
|
case "double":
|
|
$annotationType = AnnotationType::DOUBLE;
|
|
break;
|
|
default:
|
|
$annotationType = AnnotationType::STRING;
|
|
$value = (string)$value;
|
|
if (strlen($value) > 1024) {
|
|
$value = substr($value, 0, 1024);
|
|
}
|
|
}
|
|
|
|
return new BinaryAnnotation([
|
|
'key' => $key,
|
|
'value' => $value,
|
|
'annotation_type' => $annotationType,
|
|
]);
|
|
}
|
|
}
|