Skip to content

Commit d536ec8

Browse files
committed
OtlpGrpc
1 parent 7a3fdcd commit d536ec8

File tree

3 files changed

+227
-2
lines changed

3 files changed

+227
-2
lines changed

composer.json

+6-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
"guzzlehttp/guzzle": "^6.5",
1010
"psr/http-client": "^1.0",
1111
"php-http/guzzle6-adapter": "^2.0",
12-
"endclothing/prometheus_client_php": "^1.0"
12+
"endclothing/prometheus_client_php": "^1.0",
13+
"grpc/grpc": "^1.30",
14+
"google/protobuf": "^v3.3.0"
15+
1316
},
1417
"authors": [
1518
{
@@ -30,7 +33,8 @@
3033
"OpenTelemetry\\Context\\": "Context",
3134
"OpenTelemetry\\": "api",
3235
"OpenTelemetry\\Sdk\\": "sdk",
33-
"OpenTelemetry\\Contrib\\": "contrib"
36+
"OpenTelemetry\\Contrib\\": "contrib",
37+
"Opentelemetry\\Proto\\Collector\\Trace\\V1\\": "proto/Opentelemetry/Proto/Collector/Trace/V1"
3438
}
3539
},
3640
"autoload-dev": {

contrib/OtlpGrpc/Exporter.php

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
namespace OpenTelemetry\Contrib\OtlpGrpc;
5+
require __DIR__ . '/../../vendor/autoload.php';
6+
7+
8+
use grpc;
9+
use OpenTelemetry\Sdk\Trace;
10+
use OpenTelemetry\Trace as API;
11+
use Opentelemetry\Proto\Collector\Trace\V1\TraceServiceClient;
12+
13+
class Exporter implements Trace\Exporter
14+
{
15+
/**
16+
* @var string
17+
*/
18+
private $endpointURL;
19+
20+
/**
21+
* @var string
22+
*/
23+
private $protocol;
24+
25+
/**
26+
* @var string
27+
*/
28+
private $insecure;
29+
30+
/**
31+
* @var string
32+
*/
33+
private $certificateFile;
34+
35+
/**
36+
* @var array
37+
*/
38+
private $headers;
39+
40+
/**
41+
* @var string
42+
*/
43+
private $compression;
44+
45+
/**
46+
* @var int
47+
*/
48+
private $timeout;
49+
/**
50+
* @var SpanConverter
51+
*/
52+
private $spanConverter;
53+
54+
/**
55+
* @var bool
56+
*/
57+
private $running = true;
58+
59+
/**
60+
* @var ClientInterface
61+
*/
62+
63+
private $client;
64+
65+
/**
66+
* Exporter constructor.
67+
* @param string $serviceName
68+
*/
69+
public function __construct(
70+
$serviceName,
71+
ClientInterface $client=null
72+
) {
73+
74+
// Set default values based on presence of env variable
75+
$this->endpointURL = getenv('OTEL_EXPORTER_OTLP_ENDPOINT') ?: 'localhost:55680';
76+
$this->protocol = getenv('OTEL_EXPORTER_OTLP_PROTOCOL') ?: 'grpc';
77+
$this->insecure = getenv('OTEL_EXPORTER_OTLP_INSECURE') ?: 'false';
78+
$this->certificateFile = getenv('OTEL_EXPORTER_OTLP_CERTIFICATE') ?: 'none';
79+
$this->headers[] = getenv('OTEL_EXPORTER_OTLP_HEADERS') ?: 'none';
80+
$this->compression = getenv('OTEL_EXPORTER_OTLP_COMPRESSION') ?: 'none';
81+
$this->timeout =(int) getenv('OTEL_EXPORTER_OTLP_TIMEOUT') ?: 10;
82+
83+
$this->client = $client ?? new TraceServiceClient($endpointURL, [
84+
'credentials' => Grpc\ChannelCredentials::createInsecure(),
85+
]);
86+
// $this->spanConverter = new SpanConverter($serviceName);
87+
}
88+
89+
/**
90+
* Exports the provided Span data via the OTLP protocol
91+
*
92+
* @param iterable<API\Span> $spans Array of Spans
93+
* @return int return code, defined on the Exporter interface
94+
*/
95+
public function export(iterable $spans): int
96+
{
97+
if (!$this->running) {
98+
return Exporter::FAILED_NOT_RETRYABLE;
99+
}
100+
101+
if (empty($spans)) {
102+
return Trace\Exporter::SUCCESS;
103+
}
104+
105+
$convertedSpans = [];
106+
foreach ($spans as $span) {
107+
array_push($convertedSpans, $this->spanConverter->convert($span));
108+
}
109+
110+
111+
// try {
112+
// $json = json_encode($convertedSpans);
113+
114+
// $this->headers[] = '';
115+
116+
// if ($this->protocol == 'json') {
117+
// $headers = ['content-type' => 'application/json', 'Content-Encoding' => 'gzip'];
118+
// }
119+
120+
// $request = new Request('POST', $this->endpointURL, $this->headers, $json);
121+
// $response = $this->client->sendRequest($request);
122+
// } catch (RequestExceptionInterface $e) {
123+
// return Trace\Exporter::FAILED_NOT_RETRYABLE;
124+
// } catch (NetworkExceptionInterface | ClientExceptionInterface $e) {
125+
// return Trace\Exporter::FAILED_RETRYABLE;
126+
// }
127+
128+
if ($response->getStatusCode() >= 400 && $response->getStatusCode() < 500) {
129+
return Trace\Exporter::FAILED_NOT_RETRYABLE;
130+
}
131+
132+
if ($response->getStatusCode() >= 500 && $response->getStatusCode() < 600) {
133+
return Trace\Exporter::FAILED_RETRYABLE;
134+
}
135+
136+
return Trace\Exporter::SUCCESS;
137+
}
138+
139+
public function shutdown(): void
140+
{
141+
$this->running = false;
142+
}
143+
144+
}

contrib/OtlpGrpc/SpanConverter.php

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Contrib\OtlpGrpc;
6+
7+
use OpenTelemetry\Trace\Span;
8+
9+
class SpanConverter
10+
{
11+
/**
12+
* @var string
13+
*/
14+
private $serviceName;
15+
16+
public function __construct(string $serviceName)
17+
{
18+
$this->serviceName = $serviceName;
19+
}
20+
21+
private function sanitiseTagValue($value)
22+
{
23+
// Casting false to string makes an empty string
24+
if (is_bool($value)) {
25+
return $value ? 'true' : 'false';
26+
}
27+
28+
// OTLP tags must be strings, but opentelemetry
29+
// accepts strings, booleans, numbers, and lists of each.
30+
if (is_array($value)) {
31+
return join(',', array_map([$this, 'sanitiseTagValue'], $value));
32+
}
33+
34+
// Floats will lose precision if their string representation
35+
// is >=14 or >=17 digits, depending on PHP settings.
36+
// Can also throw E_RECOVERABLE_ERROR if $value is an object
37+
// without a __toString() method.
38+
// This is possible because OpenTelemetry\Trace\Span does not verify
39+
// setAttribute() $value input.
40+
return (string) $value;
41+
}
42+
43+
public function convert(Span $span)
44+
{
45+
$spanParent = $span->getParent();
46+
$row = [
47+
'id' => $span->getContext()->getSpanId(),
48+
'traceId' => $span->getContext()->getTraceId(),
49+
'parentId' => $spanParent ? $spanParent->getSpanId() : null,
50+
'localEndpoint' => [
51+
'serviceName' => $this->serviceName,
52+
],
53+
'name' => $span->getSpanName(),
54+
'timestamp' => (int) ($span->getStartEpochTimestamp() / 1e3), // RealtimeClock in microseconds
55+
'duration' => (int) (($span->getEnd() - $span->getStart()) / 1e3), // Diff in microseconds
56+
];
57+
58+
foreach ($span->getAttributes() as $k => $v) {
59+
if (!array_key_exists('tags', $row)) {
60+
$row['tags'] = [];
61+
}
62+
$row['tags'][$k] = $this->sanitiseTagValue($v->getValue());
63+
}
64+
65+
foreach ($span->getEvents() as $event) {
66+
if (!array_key_exists('annotations', $row)) {
67+
$row['annotations'] = [];
68+
}
69+
$row['annotations'][] = [
70+
'timestamp' => (int) ($event->getTimestamp() / 1e3), // RealtimeClock in microseconds
71+
'value' => $event->getName(),
72+
];
73+
}
74+
75+
return $row;
76+
}
77+
}

0 commit comments

Comments
 (0)