Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Instana exporter implementation. #1462

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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