-
Notifications
You must be signed in to change notification settings - Fork 191
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added OTLP/HTTP JSON Exporter (#210)
* 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
1 parent
16c3f97
commit 094333d
Showing
5 changed files
with
535 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.