Skip to content

Commit

Permalink
Added OTLP/HTTP JSON Exporter (#210)
Browse files Browse the repository at this point in the history
* Added OTLP/HTTP JSON Exporter

Signed-off-by:Ritick Gautam<[email protected]>

* Updated SpanConverter

* Fix vendor/bin/psalm

* Updated TestCase

* Updated OtlpExportertest accordingly #208
Updated TestCase

* Updated OTLPSpanConverterTest

* Updated OTLPExportertest

* BugFixed

* bug fixed

* Updated: otlpExporter
Updated: testcase

* Updated namespace with StuldyCase

* Renamed the namespace

* Updated php-cs-fixer
  • Loading branch information
riticksingh authored Nov 10, 2020
1 parent 16c3f97 commit 094333d
Show file tree
Hide file tree
Showing 5 changed files with 535 additions and 0 deletions.
159 changes: 159 additions & 0 deletions contrib/Otlp/Exporter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Contrib\Otlp;

use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\Psr7\Request;
use Http\Adapter\Guzzle6\Client;
use OpenTelemetry\Sdk\Trace;
use OpenTelemetry\Trace as API;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Client\NetworkExceptionInterface;
use Psr\Http\Client\RequestExceptionInterface;

class Exporter implements Trace\Exporter
{
/**
* @var string
*/
private $endpointURL;

/**
* @var string
*/
private $protocol;

/**
* @var string
*/
private $insecure;

/**
* @var string
*/
private $certificateFile;

/**
* @var array
*/
private $headers;

/**
* @var string
*/
private $compression;

/**
* @var int
*/
private $timeout;
/**
* @var SpanConverter
*/
private $spanConverter;

/**
* @var bool
*/
private $running = true;

/**
* @var ClientInterface
*/

private $client;

/**
* Exporter constructor.
* @param string $serviceName
*/
public function __construct(
$serviceName,
ClientInterface $client=null
) {

// Set default values based on presence of env variable
$this->endpointURL = getenv('OTEL_EXPORTER_OTLP_ENDPOINT') ?: 'localhost:55680';
$this->protocol = getenv('OTEL_EXPORTER_OTLP_PROTOCOL') ?: 'json';
$this->insecure = getenv('OTEL_EXPORTER_OTLP_INSECURE') ?: 'false';
$this->certificateFile = getenv('OTEL_EXPORTER_OTLP_CERTIFICATE') ?: 'none';
$this->headers[] = getenv('OTEL_EXPORTER_OTLP_HEADERS') ?: 'none';
$this->compression = getenv('OTEL_EXPORTER_OTLP_COMPRESSION') ?: 'none';
$this->timeout =(int) getenv('OTEL_EXPORTER_OTLP_TIMEOUT') ?: 10;

$this->client = $client ?? $this->createDefaultClient();
$this->spanConverter = new SpanConverter($serviceName);
}

/**
* Exports the provided Span data via the OTLP 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 (!$this->running) {
return Exporter::FAILED_NOT_RETRYABLE;
}

if (empty($spans)) {
return Trace\Exporter::SUCCESS;
}

$convertedSpans = [];
foreach ($spans as $span) {
array_push($convertedSpans, $this->spanConverter->convert($span));
}

try {
$json = json_encode($convertedSpans);

$this->headers[] = '';

if ($this->protocol == 'json') {
$headers = ['content-type' => 'application/json', 'Content-Encoding' => 'gzip'];
}

$request = new Request('POST', $this->endpointURL, $this->headers, $json);
$response = $this->client->sendRequest($request);
} catch (RequestExceptionInterface $e) {
return Trace\Exporter::FAILED_NOT_RETRYABLE;
} catch (NetworkExceptionInterface | ClientExceptionInterface $e) {
return Trace\Exporter::FAILED_RETRYABLE;
}

if ($response->getStatusCode() >= 400 && $response->getStatusCode() < 500) {
return Trace\Exporter::FAILED_NOT_RETRYABLE;
}

if ($response->getStatusCode() >= 500 && $response->getStatusCode() < 600) {
return Trace\Exporter::FAILED_RETRYABLE;
}

return Trace\Exporter::SUCCESS;
}

public function shutdown(): void
{
$this->running = false;
}

protected function createDefaultClient(): ClientInterface
{
$container = [];
$history = Middleware::history($container);
$stack = HandlerStack::create();
// Add the history middleware to the handler stack.
$stack->push($history);

return Client::createWithConfig([
'handler' => $stack,
'timeout' => 30,
]);
}
}
77 changes: 77 additions & 0 deletions contrib/Otlp/SpanConverter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Contrib\Otlp;

use OpenTelemetry\Trace\Span;

class SpanConverter
{
/**
* @var string
*/
private $serviceName;

public function __construct(string $serviceName)
{
$this->serviceName = $serviceName;
}

private function sanitiseTagValue($value)
{
// Casting false to string makes an empty string
if (is_bool($value)) {
return $value ? 'true' : 'false';
}

// OTLP tags must be strings, but opentelemetry
// accepts strings, booleans, numbers, and lists of each.
if (is_array($value)) {
return join(',', array_map([$this, 'sanitiseTagValue'], $value));
}

// Floats will lose precision if their string representation
// is >=14 or >=17 digits, depending on PHP settings.
// Can also throw E_RECOVERABLE_ERROR if $value is an object
// without a __toString() method.
// This is possible because OpenTelemetry\Trace\Span does not verify
// setAttribute() $value input.
return (string) $value;
}

public function convert(Span $span)
{
$spanParent = $span->getParent();
$row = [
'id' => $span->getContext()->getSpanId(),
'traceId' => $span->getContext()->getTraceId(),
'parentId' => $spanParent ? $spanParent->getSpanId() : null,
'localEndpoint' => [
'serviceName' => $this->serviceName,
],
'name' => $span->getSpanName(),
'timestamp' => (int) ($span->getStartEpochTimestamp() / 1e3), // RealtimeClock in microseconds
'duration' => (int) (($span->getEnd() - $span->getStart()) / 1e3), // Diff in microseconds
];

foreach ($span->getAttributes() as $k => $v) {
if (!array_key_exists('tags', $row)) {
$row['tags'] = [];
}
$row['tags'][$k] = $this->sanitiseTagValue($v->getValue());
}

foreach ($span->getEvents() as $event) {
if (!array_key_exists('annotations', $row)) {
$row['annotations'] = [];
}
$row['annotations'][] = [
'timestamp' => (int) ($event->getTimestamp() / 1e3), // RealtimeClock in microseconds
'value' => $event->getName(),
];
}

return $row;
}
}
64 changes: 64 additions & 0 deletions examples/AlwaysOnOTLPExample.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);
require __DIR__ . '/../vendor/autoload.php';

use OpenTelemetry\Contrib\Otlp\Exporter as OTLPExporter;
use OpenTelemetry\Sdk\Trace\Attributes;
use OpenTelemetry\Sdk\Trace\Clock;
use OpenTelemetry\Sdk\Trace\Sampler\AlwaysOnSampler;
use OpenTelemetry\Sdk\Trace\SamplingResult;
use OpenTelemetry\Sdk\Trace\SpanProcessor\SimpleSpanProcessor;
use OpenTelemetry\Sdk\Trace\TracerProvider;
use OpenTelemetry\Trace as API;

$sampler = new AlwaysOnSampler();
$samplingResult = $sampler->shouldSample(
null,
md5((string) microtime(true)),
substr(md5((string) microtime(true)), 16),
'io.opentelemetry.example',
API\SpanKind::KIND_INTERNAL
);
$Exporter = new OTLPExporter(
'OTLP Example Service'
);

if (SamplingResult::RECORD_AND_SAMPLED === $samplingResult->getDecision()) {
echo 'Starting OTLPExample';
$tracer = (new TracerProvider())
->addSpanProcessor(new SimpleSpanProcessor($Exporter))
->getTracer('io.opentelemetry.contrib.php');

for ($i = 0; $i < 5; $i++) {
// start a span, register some events
$timestamp = Clock::get()->timestamp();
$span = $tracer->startAndActivateSpan('session.generate.span.' . microtime(true));

$spanParent = $span->getParent();
echo sprintf(
PHP_EOL . 'Exporting Trace: %s, Parent: %s, Span: %s',
$span->getContext()->getTraceId(),
$spanParent ? $spanParent->getSpanId() : 'None',
$span->getContext()->getSpanId()
);

$span->setAttribute('remote_ip', '1.2.3.4')
->setAttribute('country', 'USA');

$span->addEvent('found_login' . $i, $timestamp, new Attributes([
'id' => $i,
'username' => 'otuser' . $i,
]));
$span->addEvent('generated_session', $timestamp, new Attributes([
'id' => md5((string) microtime(true)),
]));

$tracer->endActiveSpan();
}
echo PHP_EOL . 'OTLPExample complete! ';
} else {
echo PHP_EOL . 'OTLPExample tracing is not enabled';
}

echo PHP_EOL;
Loading

0 comments on commit 094333d

Please sign in to comment.