Jaeger exporter (#70)
* make zipkin-exporter implementation simpler * add Jaeger exporter and fix zipkin exporter timestamps * move zipkin converter logic outside of exporters * start containers before running examples * fix open-telemetry badge codecov.io url * fix phpunit whitelist after api and sdk splitting
This commit is contained in:
parent
783ccdde06
commit
e5eda43ad9
5
Makefile
5
Makefile
|
@ -5,11 +5,14 @@ install:
|
||||||
update:
|
update:
|
||||||
$(DC_RUN_PHP) composer update
|
$(DC_RUN_PHP) composer update
|
||||||
test:
|
test:
|
||||||
$(DC_RUN_PHP) php ./vendor/bin/phpunit --colors=always
|
$(DC_RUN_PHP) php ./vendor/bin/phpunit --colors=always --coverage-text
|
||||||
phan:
|
phan:
|
||||||
$(DC_RUN_PHP) env PHAN_DISABLE_XDEBUG_WARN=1 php ./vendor/bin/phan
|
$(DC_RUN_PHP) env PHAN_DISABLE_XDEBUG_WARN=1 php ./vendor/bin/phan
|
||||||
examples: FORCE
|
examples: FORCE
|
||||||
|
docker-compose up -d
|
||||||
$(DC_RUN_PHP) php ./examples/AlwaysOnTraceExample.php
|
$(DC_RUN_PHP) php ./examples/AlwaysOnTraceExample.php
|
||||||
|
$(DC_RUN_PHP) php ./examples/AlwaysOffTraceExample.php
|
||||||
|
$(DC_RUN_PHP) php ./examples/JaegerExporterExample.php
|
||||||
bash:
|
bash:
|
||||||
$(DC_RUN_PHP) bash
|
$(DC_RUN_PHP) bash
|
||||||
style:
|
style:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# OpenTelemetry php library
|
# OpenTelemetry php library
|
||||||
[](https://gitter.im/open-telemetry/opentelemetry-php?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
[](https://gitter.im/open-telemetry/opentelemetry-php?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||||
[](https://travis-ci.org/open-telemetry/opentelemetry-php)
|
[](https://travis-ci.org/open-telemetry/opentelemetry-php)
|
||||||
[](https://codecov.io/gh/opentelemety/opentelemetry-php)
|
[](https://codecov.io/gh/open-telemetry/opentelemetry-php)
|
||||||
|
|
||||||
## Communication
|
## Communication
|
||||||
Most of our communication is done on gitter.im in the [opentelemetry-php](https://gitter.im/open-telemetry/opentelemetry-php) channel.
|
Most of our communication is done on gitter.im in the [opentelemetry-php](https://gitter.im/open-telemetry/opentelemetry-php) channel.
|
||||||
|
|
|
@ -10,3 +10,10 @@ services:
|
||||||
image: openzipkin/zipkin-slim
|
image: openzipkin/zipkin-slim
|
||||||
ports:
|
ports:
|
||||||
- 9411:9411
|
- 9411:9411
|
||||||
|
jaeger:
|
||||||
|
image: jaegertracing/all-in-one
|
||||||
|
environment:
|
||||||
|
COLLECTOR_ZIPKIN_HTTP_PORT: 9412
|
||||||
|
ports:
|
||||||
|
- 9412:9412
|
||||||
|
- 16686:16686
|
||||||
|
|
|
@ -36,5 +36,7 @@ if (SamplingResult::RECORD_AND_SAMPLED === $samplingResult) {
|
||||||
$span->end(); // pass status as an optional argument
|
$span->end(); // pass status as an optional argument
|
||||||
print_r($span); // print the span as a resulting output
|
print_r($span); // print the span as a resulting output
|
||||||
} else {
|
} else {
|
||||||
echo 'Sampling is not enabled';
|
echo PHP_EOL . 'Sampling is not enabled';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
echo PHP_EOL;
|
||||||
|
|
|
@ -29,9 +29,12 @@ if (SamplingResult::RECORD_AND_SAMPLED === $samplingResult->getDecision()) {
|
||||||
[new SimpleSpanProcessor($zipkinExporter)]
|
[new SimpleSpanProcessor($zipkinExporter)]
|
||||||
))
|
))
|
||||||
->getTracer('io.opentelemetry.contrib.php');
|
->getTracer('io.opentelemetry.contrib.php');
|
||||||
|
|
||||||
|
echo PHP_EOL . sprintf('Trace with id %s started ', $tracer->getActiveSpan()->getContext()->getTraceId());
|
||||||
|
|
||||||
for ($i = 0; $i < 5; $i++) {
|
for ($i = 0; $i < 5; $i++) {
|
||||||
// start a span, register some events
|
// start a span, register some events
|
||||||
$span = $tracer->startAndActivateSpan('session.generate.span' . $i);
|
$span = $tracer->startAndActivateSpan('session.generate.span.' . time());
|
||||||
$tracer->setActiveSpan($span);
|
$tracer->setActiveSpan($span);
|
||||||
|
|
||||||
$span->setAttribute('remote_ip', '1.2.3.4')
|
$span->setAttribute('remote_ip', '1.2.3.4')
|
||||||
|
@ -47,7 +50,9 @@ if (SamplingResult::RECORD_AND_SAMPLED === $samplingResult->getDecision()) {
|
||||||
|
|
||||||
$tracer->endActiveSpan();
|
$tracer->endActiveSpan();
|
||||||
}
|
}
|
||||||
echo 'AlwaysOnTraceExample complete! See the results at http://localhost:9411/';
|
echo PHP_EOL . 'AlwaysOnTraceExample complete! See the results at http://localhost:9411/';
|
||||||
} else {
|
} else {
|
||||||
echo 'Sampling is not enabled';
|
echo PHP_EOL . 'Sampling is not enabled';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
echo PHP_EOL;
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
require __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
|
use OpenTelemetry\Sdk\Trace\AlwaysOnSampler;
|
||||||
|
use OpenTelemetry\Sdk\Trace\Attributes;
|
||||||
|
use OpenTelemetry\Sdk\Trace\JaegerExporter;
|
||||||
|
use OpenTelemetry\Sdk\Trace\SimpleSpanProcessor;
|
||||||
|
use OpenTelemetry\Sdk\Trace\TracerProvider;
|
||||||
|
|
||||||
|
$sampler = (new AlwaysOnSampler())->shouldSample();
|
||||||
|
|
||||||
|
$exporter = new JaegerExporter(
|
||||||
|
'jaegerExporterExample',
|
||||||
|
'http://jaeger:9412/api/v2/spans'
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($sampler) {
|
||||||
|
echo 'Starting JaegerExporterExample';
|
||||||
|
$tracer = (TracerProvider::getInstance(
|
||||||
|
[new SimpleSpanProcessor($exporter)]
|
||||||
|
))
|
||||||
|
->getTracer('io.opentelemetry.contrib.php');
|
||||||
|
|
||||||
|
echo PHP_EOL . sprintf('Trace with id %s started ', $tracer->getActiveSpan()->getContext()->getTraceId());
|
||||||
|
|
||||||
|
for ($i = 0; $i < 5; $i++) {
|
||||||
|
// start a span, register some events
|
||||||
|
$span = $tracer->startAndActivateSpan('session.generate.span' . time());
|
||||||
|
$tracer->setActiveSpan($span);
|
||||||
|
|
||||||
|
$span->setAttribute('remote_ip', '1.2.3.4')
|
||||||
|
->setAttribute('country', 'USA');
|
||||||
|
|
||||||
|
$span->addEvent('found_login' . $i, new Attributes([
|
||||||
|
'id' => $i,
|
||||||
|
'username' => 'otuser' . $i,
|
||||||
|
]));
|
||||||
|
$span->addEvent('generated_session', new Attributes([
|
||||||
|
'id' => md5((string) microtime(true)),
|
||||||
|
]));
|
||||||
|
|
||||||
|
$tracer->endActiveSpan();
|
||||||
|
}
|
||||||
|
echo PHP_EOL . 'JaegerExporterExample complete! See the results at http://localhost:16686/';
|
||||||
|
} else {
|
||||||
|
echo PHP_EOL . 'Sampling is not enabled';
|
||||||
|
}
|
||||||
|
|
||||||
|
echo PHP_EOL;
|
|
@ -32,7 +32,10 @@
|
||||||
|
|
||||||
<filter>
|
<filter>
|
||||||
<whitelist>
|
<whitelist>
|
||||||
<directory>src</directory>
|
<directory>sdk</directory>
|
||||||
|
<exclude>
|
||||||
|
<directory>sdk/Tests</directory>
|
||||||
|
</exclude>
|
||||||
</whitelist>
|
</whitelist>
|
||||||
</filter>
|
</filter>
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,7 @@ use OpenTelemetry\Sdk\Trace\Attributes;
|
||||||
use OpenTelemetry\Sdk\Trace\SpanContext;
|
use OpenTelemetry\Sdk\Trace\SpanContext;
|
||||||
use OpenTelemetry\Sdk\Trace\SpanStatus;
|
use OpenTelemetry\Sdk\Trace\SpanStatus;
|
||||||
use OpenTelemetry\Sdk\Trace\Tracer;
|
use OpenTelemetry\Sdk\Trace\Tracer;
|
||||||
use OpenTelemetry\Sdk\Trace\ZipkinExporter;
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use ReflectionMethod;
|
|
||||||
|
|
||||||
class TracingTest extends TestCase
|
class TracingTest extends TestCase
|
||||||
{
|
{
|
||||||
|
@ -222,34 +220,4 @@ class TracingTest extends TestCase
|
||||||
$this->assertNull($global->getParent());
|
$this->assertNull($global->getParent());
|
||||||
$this->assertNotNull($request->getParent());
|
$this->assertNotNull($request->getParent());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testZipkinConverter()
|
|
||||||
{
|
|
||||||
$tracer = new Tracer();
|
|
||||||
$span = $tracer->startAndActivateSpan('guard.validate');
|
|
||||||
$span->setAttribute('service', 'guard');
|
|
||||||
$span->addEvent('validators.list', new Attributes(['job' => 'stage.updateTime']));
|
|
||||||
$span->end();
|
|
||||||
|
|
||||||
$method = new ReflectionMethod(ZipkinExporter::class, 'convertSpan');
|
|
||||||
$method->setAccessible(true);
|
|
||||||
|
|
||||||
$exporter = new ZipkinExporter(
|
|
||||||
'test.name',
|
|
||||||
'http://host:123/path'
|
|
||||||
);
|
|
||||||
|
|
||||||
$row = $method->invokeArgs($exporter, ['span' => $span]);
|
|
||||||
$this->assertSame($row['name'], $span->getSpanName());
|
|
||||||
|
|
||||||
self::assertCount(1, $row['tags']);
|
|
||||||
self::assertEquals($span->getAttribute('service')->getValue(), $row['tags']['service']);
|
|
||||||
|
|
||||||
self::assertCount(1, $row['annotations']);
|
|
||||||
[$annotation] = $row['annotations'];
|
|
||||||
self::assertEquals('validators.list', $annotation['value']);
|
|
||||||
|
|
||||||
[$event] = \iterator_to_array($span->getEvents());
|
|
||||||
self::assertEquals($event->getTimestamp(), $annotation['timestamp']);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace OpenTelemetry\Sdk\Trace;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use OpenTelemetry\Sdk\Trace\Zipkin\SpanConverter;
|
||||||
|
use OpenTelemetry\Trace as API;
|
||||||
|
|
||||||
|
class JaegerExporter implements Exporter
|
||||||
|
{
|
||||||
|
const IMPLEMENTED_FORMATS = [
|
||||||
|
'/api/v1/spans',
|
||||||
|
'/api/v2/spans',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $endpointUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var SpanConverter
|
||||||
|
*/
|
||||||
|
private $spanConverter;
|
||||||
|
|
||||||
|
public function __construct($name, string $endpointUrl, SpanConverter $spanConverter = null)
|
||||||
|
{
|
||||||
|
$url = parse_url($endpointUrl);
|
||||||
|
|
||||||
|
if (!is_array($url)) {
|
||||||
|
throw new InvalidArgumentException('Unable to parse provided DSN');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($url['scheme'])
|
||||||
|
|| !isset($url['host'])
|
||||||
|
|| !isset($url['port'])
|
||||||
|
|| !isset($url['path'])
|
||||||
|
) {
|
||||||
|
throw new InvalidArgumentException('Endpoint should have scheme, host, port and path');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_array($url['path'], self::IMPLEMENTED_FORMATS)) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
sprintf("Current implementation supports only '%s' format", implode(' or ', self::IMPLEMENTED_FORMATS))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->endpointUrl = $endpointUrl;
|
||||||
|
|
||||||
|
$this->spanConverter = $spanConverter ?? new SpanConverter($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports the provided Span data via the Zipkin protocol
|
||||||
|
*
|
||||||
|
* @param iterable<API\Span> $spans Array of Spans
|
||||||
|
* @return int return code, defined on the Exporter interface
|
||||||
|
*/
|
||||||
|
public function export(iterable $spans) : int
|
||||||
|
{
|
||||||
|
if (empty($spans)) {
|
||||||
|
return Exporter::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
$convertedSpans = [];
|
||||||
|
foreach ($spans as &$span) {
|
||||||
|
array_push($convertedSpans, $this->spanConverter->convert($span));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$json = json_encode($convertedSpans);
|
||||||
|
$contextOptions = [
|
||||||
|
'http' => [
|
||||||
|
'method' => 'POST',
|
||||||
|
'header' => 'Content-Type: application/json',
|
||||||
|
'content' => $json,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
$context = stream_context_create($contextOptions);
|
||||||
|
@file_get_contents($this->endpointUrl, false, $context);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return Exporter::FAILED_RETRYABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Exporter::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function shutdown(): void
|
||||||
|
{
|
||||||
|
// TODO: Implement shutdown() method.
|
||||||
|
}
|
||||||
|
}
|
|
@ -100,14 +100,18 @@ class Tracer implements API\Tracer
|
||||||
|
|
||||||
public function endActiveSpan(?string $timestamp = null)
|
public function endActiveSpan(?string $timestamp = null)
|
||||||
{
|
{
|
||||||
// todo: should processors be called before or after end()?
|
/**
|
||||||
if ($this->getActiveSpan()->isRecording()) {
|
* a span should be ended before is sent to exporters, because the exporters need's span duration.
|
||||||
foreach ($this->spanProcessors as $spanProcessor) {
|
*/
|
||||||
$spanProcessor->onEnd($this->getActiveSpan());
|
$span = $this->getActiveSpan();
|
||||||
}
|
$wasRecording = $span->isRecording();
|
||||||
}
|
$span->end();
|
||||||
|
|
||||||
$this->getActiveSpan()->end($timestamp);
|
if ($wasRecording) {
|
||||||
|
foreach ($this->spanProcessors as $spanProcessor) {
|
||||||
|
$spanProcessor->onEnd($span);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generateSpanInstance($name, API\SpanContext $context): Span
|
private function generateSpanInstance($name, API\SpanContext $context): Span
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace OpenTelemetry\Sdk\Trace\Zipkin;
|
||||||
|
|
||||||
|
use OpenTelemetry\Trace\Span;
|
||||||
|
|
||||||
|
class SpanConverter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $serviceName;
|
||||||
|
|
||||||
|
public function __construct(string $serviceName)
|
||||||
|
{
|
||||||
|
$this->serviceName = $serviceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function convert(Span $span)
|
||||||
|
{
|
||||||
|
$row = [
|
||||||
|
'id' => $span->getContext()->getSpanId(),
|
||||||
|
'traceId' => $span->getContext()->getTraceId(),
|
||||||
|
'parentId' => $span->getParent() ? $span->getParent()->getSpanId() : null,
|
||||||
|
'localEndpoint' => [
|
||||||
|
'serviceName' => $this->serviceName,
|
||||||
|
],
|
||||||
|
'name' => $span->getSpanName(),
|
||||||
|
'timestamp' => (int) ((float) $span->getStartTimestamp() * 1000),
|
||||||
|
'duration' => (int) ((float) $span->getEndTimestamp() * 1000 - (float) $span->getStartTimestamp() * 1000),
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($span->getAttributes() as $k => $v) {
|
||||||
|
if (!array_key_exists('tags', $row)) {
|
||||||
|
$row['tags'] = [];
|
||||||
|
}
|
||||||
|
$v = $v->getValue();
|
||||||
|
if (is_bool($v)) {
|
||||||
|
$v = (string) $v;
|
||||||
|
}
|
||||||
|
$row['tags'][$k] = $v;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($span->getEvents() as $event) {
|
||||||
|
if (!array_key_exists('annotations', $row)) {
|
||||||
|
$row['annotations'] = [];
|
||||||
|
}
|
||||||
|
$row['annotations'][] = [
|
||||||
|
'timestamp' => (int) round((float) $event->getTimestamp() * 1000),
|
||||||
|
'value' => $event->getName(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $row;
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ namespace OpenTelemetry\Sdk\Trace;
|
||||||
use Exception;
|
use Exception;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
use OpenTelemetry\Sdk\Trace\Zipkin\SpanConverter;
|
||||||
use OpenTelemetry\Trace as API;
|
use OpenTelemetry\Trace as API;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,23 +16,35 @@ use OpenTelemetry\Trace as API;
|
||||||
*/
|
*/
|
||||||
class ZipkinExporter implements Exporter
|
class ZipkinExporter implements Exporter
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $endpointUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var $endpoint array to send Spans to
|
* @var SpanConverter
|
||||||
*/
|
*/
|
||||||
private $endpoint;
|
private $spanConverter;
|
||||||
private $name;
|
|
||||||
|
|
||||||
public function __construct($name, string $endpointDsn)
|
public function __construct($name, string $endpointUrl, SpanConverter $spanConverter = null)
|
||||||
{
|
{
|
||||||
$parsedDsn = parse_url($endpointDsn);
|
$parsedDsn = parse_url($endpointUrl);
|
||||||
|
|
||||||
if (!is_array($parsedDsn)) {
|
if (!is_array($parsedDsn)) {
|
||||||
throw new InvalidArgumentException('Unable to parse provided DSN');
|
throw new InvalidArgumentException('Unable to parse provided DSN');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->setEndpoint($parsedDsn);
|
if (!isset($parsedDsn['scheme'])
|
||||||
$this->name = $name;
|
|| !isset($parsedDsn['host'])
|
||||||
|
|| !isset($parsedDsn['port'])
|
||||||
|
|| !isset($parsedDsn['path'])
|
||||||
|
) {
|
||||||
|
throw new InvalidArgumentException('Endpoint should have scheme, host, port and path');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->endpointUrl = $endpointUrl;
|
||||||
|
|
||||||
|
$this->spanConverter = $spanConverter ?? new SpanConverter($name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,7 +61,7 @@ class ZipkinExporter implements Exporter
|
||||||
|
|
||||||
$convertedSpans = [];
|
$convertedSpans = [];
|
||||||
foreach ($spans as &$span) {
|
foreach ($spans as &$span) {
|
||||||
array_push($convertedSpans, $this->convertSpan($span));
|
array_push($convertedSpans, $this->spanConverter->convert($span));
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -61,7 +74,7 @@ class ZipkinExporter implements Exporter
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
$context = stream_context_create($contextOptions);
|
$context = stream_context_create($contextOptions);
|
||||||
file_get_contents($this->getEndpointUrl(), false, $context);
|
@file_get_contents($this->endpointUrl, false, $context);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
return Exporter::FAILED_RETRYABLE;
|
return Exporter::FAILED_RETRYABLE;
|
||||||
}
|
}
|
||||||
|
@ -69,93 +82,6 @@ class ZipkinExporter implements Exporter
|
||||||
return Exporter::SUCCESS;
|
return Exporter::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts spans to Zipkin format for export
|
|
||||||
*
|
|
||||||
* @param API\Span $span
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
private function convertSpan(API\Span $span) : array
|
|
||||||
{
|
|
||||||
$row = [
|
|
||||||
'id' => $span->getContext()->getSpanId(),
|
|
||||||
'traceId' => $span->getContext()->getTraceId(),
|
|
||||||
'parentId' => $span->getParent() ? $span->getParent()->getSpanId() : null,
|
|
||||||
'localEndpoint' => [
|
|
||||||
'serviceName' => $this->name,
|
|
||||||
'port' => $this->getEndpoint()['port'] ?? 0,
|
|
||||||
],
|
|
||||||
'name' => $span->getSpanName(),
|
|
||||||
'timestamp' => (int) round((float) $span->getStartTimestamp()),
|
|
||||||
'duration' => (int) round((float) $span->getEndTimestamp()) - round((float) $span->getStartTimestamp()),
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($span->getAttributes() as $k => $v) {
|
|
||||||
if (!array_key_exists('tags', $row)) {
|
|
||||||
$row['tags'] = [];
|
|
||||||
}
|
|
||||||
$v = $v->getValue();
|
|
||||||
if (is_bool($v)) {
|
|
||||||
$v = (string) $v;
|
|
||||||
}
|
|
||||||
$row['tags'][$k] = $v;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($span->getEvents() as $event) {
|
|
||||||
if (!array_key_exists('annotations', $row)) {
|
|
||||||
$row['annotations'] = [];
|
|
||||||
}
|
|
||||||
$row['annotations'][] = [
|
|
||||||
'timestamp' => $event->getTimestamp(),
|
|
||||||
'value' => $event->getName(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $row;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the configured endpoint for the Zipkin exporter
|
|
||||||
*
|
|
||||||
* @return array |null
|
|
||||||
*/
|
|
||||||
public function getEndpoint(): ?array
|
|
||||||
{
|
|
||||||
return $this->endpoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the configured endpoint for the zipkin exportedr
|
|
||||||
*
|
|
||||||
* @param array $endpoint
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setEndpoint(array $endpoint) : self
|
|
||||||
{
|
|
||||||
if (!isset($endpoint['scheme'])
|
|
||||||
|| !isset($endpoint['host'])
|
|
||||||
|| !isset($endpoint['port'])
|
|
||||||
|| !isset($endpoint['path'])
|
|
||||||
) {
|
|
||||||
throw new InvalidArgumentException('Endpoint should have scheme, host, port and path');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->endpoint = $endpoint;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getEndpointUrl(): string
|
|
||||||
{
|
|
||||||
return sprintf(
|
|
||||||
'%s://%s:%s%s',
|
|
||||||
$this->endpoint['scheme'],
|
|
||||||
$this->endpoint['host'],
|
|
||||||
$this->endpoint['port'],
|
|
||||||
$this->endpoint['path']
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function shutdown(): void
|
public function shutdown(): void
|
||||||
{
|
{
|
||||||
// TODO: Implement shutdown() method.
|
// TODO: Implement shutdown() method.
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace OpenTelemetry\Tests\Unit\Exporter;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use OpenTelemetry\Sdk\Trace\JaegerExporter;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class JaegerExporterTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @dataProvider invalidDsnDataProvider
|
||||||
|
*/
|
||||||
|
public function shouldThrowExceptionIfInvalidDsnIsPassed($invalidDsn)
|
||||||
|
{
|
||||||
|
$this->expectException(InvalidArgumentException::class);
|
||||||
|
|
||||||
|
new JaegerExporter('test.zipkin', $invalidDsn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function invalidDsnDataProvider()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'missing scheme' => ['host:123/path'],
|
||||||
|
'missing host' => ['scheme://123/path'],
|
||||||
|
'missing port' => ['scheme://host/path'],
|
||||||
|
'missing path' => ['scheme://host:123'],
|
||||||
|
'invalid port' => ['scheme://host:port/path'],
|
||||||
|
'invalid scheme' => ['1234://host:port/path'],
|
||||||
|
'invalid host' => ['scheme:///end:1234/path'],
|
||||||
|
'unimplemented path' => ['scheme:///host:1234/api/v1/spans'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,19 +10,6 @@ use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
class ZipkinExporterTest extends TestCase
|
class ZipkinExporterTest extends TestCase
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function shouldParseAnValidDsn()
|
|
||||||
{
|
|
||||||
$exporter = new ZipkinExporter('test.zipkin', 'scheme://host:1234/path');
|
|
||||||
|
|
||||||
$this->assertArrayHasKey('scheme', $exporter->getEndpoint());
|
|
||||||
$this->assertArrayHasKey('host', $exporter->getEndpoint());
|
|
||||||
$this->assertArrayHasKey('port', $exporter->getEndpoint());
|
|
||||||
$this->assertArrayHasKey('path', $exporter->getEndpoint());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @test
|
* @test
|
||||||
* @dataProvider invalidDsnDataProvider
|
* @dataProvider invalidDsnDataProvider
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace unit\Exporter;
|
||||||
|
|
||||||
|
use OpenTelemetry\Sdk\Trace\Attributes;
|
||||||
|
use OpenTelemetry\Sdk\Trace\Tracer;
|
||||||
|
use OpenTelemetry\Sdk\Trace\Zipkin\SpanConverter;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class ZipkinSpanConverterTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function shouldConvertASpanToAPayloadForZipkin()
|
||||||
|
{
|
||||||
|
$tracer = new Tracer();
|
||||||
|
$span = $tracer->startAndActivateSpan('guard.validate');
|
||||||
|
$span->setAttribute('service', 'guard');
|
||||||
|
$span->addEvent('validators.list', new Attributes(['job' => 'stage.updateTime']));
|
||||||
|
$span->end();
|
||||||
|
|
||||||
|
$converter = new SpanConverter('test.name');
|
||||||
|
$row = $converter->convert($span);
|
||||||
|
|
||||||
|
$this->assertSame($span->getContext()->getSpanId(), $row['id']);
|
||||||
|
$this->assertSame($span->getContext()->getTraceId(), $row['traceId']);
|
||||||
|
|
||||||
|
$this->assertSame('test.name', $row['localEndpoint']['serviceName']);
|
||||||
|
$this->assertSame($span->getSpanName(), $row['name']);
|
||||||
|
|
||||||
|
$this->assertIsInt($row['timestamp']);
|
||||||
|
// timestamp should be in microseconds
|
||||||
|
$this->assertGreaterThan(1e15, $row['timestamp']);
|
||||||
|
|
||||||
|
$this->assertIsInt($row['duration']);
|
||||||
|
$this->assertGreaterThan(0, $row['duration']);
|
||||||
|
|
||||||
|
$this->assertCount(1, $row['tags']);
|
||||||
|
$this->assertEquals($span->getAttribute('service')->getValue(), $row['tags']['service']);
|
||||||
|
|
||||||
|
$this->assertCount(1, $row['annotations']);
|
||||||
|
[$annotation] = $row['annotations'];
|
||||||
|
$this->assertEquals('validators.list', $annotation['value']);
|
||||||
|
|
||||||
|
[$event] = \iterator_to_array($span->getEvents());
|
||||||
|
$this->assertIsInt($annotation['timestamp']);
|
||||||
|
|
||||||
|
// timestamp should be in microseconds
|
||||||
|
$this->assertGreaterThan(1e15, $annotation['timestamp']);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue