Skip to content

Commit

Permalink
Instana exporter implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
kothiga committed Dec 20, 2024
1 parent 4a021fb commit 5277567
Show file tree
Hide file tree
Showing 18 changed files with 968 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .gitsplit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ splits:
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/transport-grpc.git"
- prefix: "src/Contrib/Zipkin"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/exporter-zipkin.git"
- prefix: "src/Contrib/Instana"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/exporter-instana.git"
- prefix: "src/Extension/Propagator/B3"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/extension-propagator-b3.git"
- prefix: "src/Extension/Propagator/CloudTrace"
Expand Down
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"open-telemetry/context": "1.0.x-dev",
"open-telemetry/exporter-otlp": "1.0.x-dev",
"open-telemetry/exporter-zipkin": "1.0.x-dev",
"open-telemetry/exporter-instana": "1.0.x-dev",
"open-telemetry/extension-propagator-b3": "1.0.x-dev",
"open-telemetry/extension-propagator-jaeger": "0.0.2",
"open-telemetry/gen-otlp-protobuf": "1.0.x-dev",
Expand All @@ -63,6 +64,7 @@
"src/Contrib/Otlp/_register.php",
"src/Contrib/Grpc/_register.php",
"src/Contrib/Zipkin/_register.php",
"src/Contrib/Instana/_register.php",
"src/Extension/Propagator/B3/_register.php",
"src/Extension/Propagator/CloudTrace/_register.php",
"src/Extension/Propagator/Jaeger/_register.php",
Expand Down
49 changes: 49 additions & 0 deletions examples/traces/exporters/instana.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Example;

require __DIR__ . '/../../../vendor/autoload.php';

use OpenTelemetry\Contrib\Instana\SpanExporter as InstanaExporter;
use OpenTelemetry\Contrib\Instana\SpanExporterFactory as InstanaSpanExporterFactory;
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;

$tracerProvider = new TracerProvider(
new SimpleSpanProcessor(
(new InstanaSpanExporterFactory)->create()
)
);

$tracer = $tracerProvider->getTracer('io.opentelemetry.contrib.php');

echo 'Starting Instana example';

$root = $span = $tracer->spanBuilder('root')->startSpan();
$scope = $span->activate();

for ($i = 0; $i < 3; $i++) {
// start a span, register some events
$span = $tracer->spanBuilder('loop-' . $i)->startSpan();

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

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

$span->end();
}
$scope->detach();
$root->end();
echo PHP_EOL . 'Instana example complete!';

echo PHP_EOL;
$tracerProvider->shutdown();
217 changes: 217 additions & 0 deletions src/Contrib/Instana/InstanaTransport.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Contrib\Instana;

use OpenTelemetry\SDK\Common\Export\TransportInterface;
use OpenTelemetry\SDK\Common\Future\CancellationInterface;
use OpenTelemetry\SDK\Common\Future\CompletedFuture;
use OpenTelemetry\SDK\Common\Future\ErrorFuture;
use OpenTelemetry\SDK\Common\Future\FutureInterface;
use OpenTelemetry\API\Behavior\LogsMessagesTrait;

use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Uri;

use Psr\Http\Message\ResponseInterface;

use BadMethodCallException;
use Exception;
use RuntimeException;

class InstanaTransport implements TransportInterface
{
use LogsMessagesTrait;

const CONTENT_TYPE = 'application/json';
const ATTEMPTS = 3;

private Client $client;
private ?string $agent_uuid = null;
private ?int $pid = null;
private array $secrets = [];
private array $tracing = [];

private bool $closed = true;
private array $headers = [];

public function __construct(
private readonly string $endpoint,
private readonly float $timeout = 0.0
) {
$this->headers += ['Content-Type' => self::CONTENT_TYPE];
if ($timeout > 0.0) {
$this->headers += ['timeout' => $timeout];
}

$this->client = new Client(['base_uri' => $endpoint]);

for ($attempt = 0; $attempt < self::ATTEMPTS && !$this->announce(); $attempt++) {
self::logDebug("Discovery request failed, attempt " . $attempt);
sleep(5);
}

if (is_null($this->agent_uuid) || is_null($this->pid)) {
throw new Exception('Failed announcement in transport');
}
}

public function contentType(): string
{
return self::CONTENT_TYPE;
}

public function send(string $payload, ?CancellationInterface $cancellation = null): FutureInterface
{
if ($this->closed) {
return new ErrorFuture(new BadMethodCallException('Transport closed'));
}

$response = $this->sendPayload($payload);

$code = $response->getStatusCode();
if ($code != 204 && $code != 307) {
self::logDebug("Sending failed with code " . $code);
return new ErrorFuture(new RuntimeException('Payload failed to send with code ' . $code));
}

return new CompletedFuture('Payload successfully sent');
}

private function sendPayload(string $payload): ResponseInterface
{
return $this->client->sendRequest(
new Request(
method: 'POST',
uri: new Uri('/com.instana.plugin.php/traces.' . $this->pid),
headers: $this->headers,
body: $payload
)
);
}

public function shutdown(?CancellationInterface $cancellation = null): bool
{
if ($this->closed) {
return false;
}

return $this->closed = true;
}

public function forceFlush(?CancellationInterface $cancellation = null): bool
{
return !$this->closed;
}

private function announce(): bool
{
self::logDebug("Announcing to " . $this->endpoint);

// Phase 1) Host lookup.
$response = $this->client->sendRequest(
new Request(method: 'GET', uri: new Uri('/'), headers: $this->headers)
);

$code = $response->getStatusCode();
$msg = $response->getBody()->getContents();

if ($code != 200 && !array_key_exists('version', json_decode($msg, true))) {
self::LogError("Failed to lookup host. Received code " . $code . " with message: " . $msg);
$this->closed = true;
return false;
}

self::logDebug("Phase 1 announcement response code " . $code);

// Phase 2) Announcement.
$response = $this->client->sendRequest(
new Request(
method: 'PUT',
uri: new Uri('/com.instana.plugin.php.discovery'),
headers: $this->headers,
body: $this->getAnnouncementPayload()
)
);

$code = $response->getStatusCode();
$msg = $response->getBody()->getContents();

self::logDebug("Phase 2 announcement response code " . $code);

if ($code < 200 || $code >= 300) {
self::LogError("Failed announcement. Received code " . $code . " with message: " . $msg);
$this->closed = true;
return false;
}

$content = json_decode($msg, true);
if (!array_key_exists('pid', $content)) {
self::LogError("Failed to receive a pid from agent");
$this->closed = true;
return false;
}

$this->pid = $content['pid'];
$this->agent_uuid = $content['agentUuid'];

// Optional values that we may receive from the agent.
if (array_key_exists('secrets', $content)) $this->secrets = $content['secrets'];
if (array_key_exists('tracing', $content)) $this->tracing = $content['tracing'];

// Phase 3) Wait for the agent ready signal.
for ($retry = 0; $retry < 5; $retry++) {
if ($retry) self::logDebug("Agent not yet ready, attempt " . $retry);

$response = $this->client->sendRequest(
new Request(
method: 'HEAD',
uri: new Uri('/com.instana.plugin.php.' . $this->pid),
headers: $this->headers
)
);

$code = $response->getStatusCode();
self::logDebug("Phase 3 announcement endpoint status " . $code);
if ($code >= 200 && $code < 300) {
$this->closed = false;
return true;
}

sleep(1);
}

$this->closed = true;
return false;
}

private function getAnnouncementPayload(): string
{
$cmdline_args = file_get_contents("/proc/self/cmdline");
$cmdline_args = explode("\0", $cmdline_args);
$cmdline_args = array_slice($cmdline_args, 1, count($cmdline_args) - 2);

return json_encode(array(
"pid" => getmypid(),
"pidFromParentNS" => false,
"pidNamespace" => readlink("/proc/self/ns/pid"),
"name" => readlink("/proc/self/exe"),
"args" => $cmdline_args,
"cpuSetFileContent" => "/",
"fd" => null,
"inode" => null
));
}

public function getPid(): ?string
{
return is_null($this->pid) ? null : strval($this->pid);
}

public function getUuid(): ?string
{
return $this->agent_uuid;
}
}
22 changes: 22 additions & 0 deletions src/Contrib/Instana/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[![Releases](https://img.shields.io/badge/releases-purple)](https://github.com/opentelemetry-php/exporter-instana/releases)
[![Source](https://img.shields.io/badge/source-exporter--instana-green)](https://github.com/open-telemetry/opentelemetry-php/tree/main/src/Contrib/Instana)
[![Mirror](https://img.shields.io/badge/mirror-opentelemetry--php:exporter--instana-blue)](https://github.com/opentelemetry-php/exporter-instana)
[![Latest Version](http://poser.pugx.org/open-telemetry/exporter-instana/v/unstable)](https://packagist.org/packages/open-telemetry/exporter-instana/)
[![Stable](http://poser.pugx.org/open-telemetry/exporter-instana/v/stable)](https://packagist.org/packages/open-telemetry/exporter-instana/)

# OpenTelemetry Instana Exporter

Instana exporter for OpenTelemetry.

## Documentation

https://opentelemetry.io/docs/instrumentation/php/exporters/#instana

## Usage

See https://github.com/open-telemetry/opentelemetry-php/blob/main/examples/traces/exporters/instana.php

## Contributing

This repository is a read-only git subtree split.
To contribute, please see the main [OpenTelemetry PHP monorepo](https://github.com/open-telemetry/opentelemetry-php).
Loading

0 comments on commit 5277567

Please sign in to comment.