ttrss/vendor/jonahgeorge/jaeger-client-php/src/Jaeger/Span.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,
]);
}
}