opentelemetry-php/src/SDK/Trace/ExporterFactory.php

150 lines
5.5 KiB
PHP

<?php
declare(strict_types=1);
namespace OpenTelemetry\SDK\Trace;
use InvalidArgumentException;
use OpenTelemetry\SDK\Common\Configuration\Configuration;
use OpenTelemetry\SDK\Common\Configuration\KnownValues as Values;
use OpenTelemetry\SDK\Common\Configuration\Variables as Env;
use OpenTelemetry\SDK\Common\Dsn\DsnInterface;
use OpenTelemetry\SDK\Common\Dsn\Parser;
use OpenTelemetry\SDK\Common\Dsn\ParserInterface;
class ExporterFactory
{
private const KNOWN_EXPORTERS = [
'console' => '\OpenTelemetry\SDK\Trace\SpanExporter\ConsoleSpanExporter',
'memory' => '\OpenTelemetry\SDK\Trace\SpanExporter\InMemoryExporter',
'logger+file' => '\OpenTelemetry\SDK\Trace\SpanExporter\LoggerExporter',
'jaeger+http' => '\OpenTelemetry\Contrib\Jaeger\Exporter',
'zipkin+http' => '\OpenTelemetry\Contrib\Zipkin\Exporter',
'otlp+grpc' => '\OpenTelemetry\Contrib\Otlp\SpanExporter',
'otlp+http' => '\OpenTelemetry\Contrib\Otlp\SpanExporter',
'otlp+json' => '\OpenTelemetry\Contrib\Otlp\SpanExporter',
'newrelic+http' => '\OpenTelemetry\Contrib\Newrelic\Exporter',
'zipkintonewrelic+http' => '\OpenTelemetry\Contrib\ZipkinToNewrelic\Exporter',
// this entry exists only for testing purposes
'test+http' => '\OpenTelemetry\Contrib\Test\Exporter',
];
private const DEFAULT_SERVICE_NAME = 'unknown_service';
private string $serviceName;
private ParserInterface $parser;
public function __construct(string $serviceName = self::DEFAULT_SERVICE_NAME, ParserInterface $parser = null)
{
$this->serviceName = $serviceName;
$this->parser = $parser ?? Parser::create();
}
/**
* Returns the corresponding Exporter via the configuration string
*
* @param string $exporterDsn String containing information for Exporter creation
* Should follow the format: type+baseUri?option1=a
* Query string is optional and based on the Exporter
*/
public function fromConnectionString(string $exporterDsn): SpanExporterInterface
{
if (in_array($exporterDsn, ['console', 'memory'])) {
return self::buildExporter($exporterDsn);
}
$dsn = $this->parser->parse($exporterDsn);
self::validateProtocol($dsn->getProtocol());
$endpoint = $dsn->getEndpoint();
$serviceName = $this->resolveServiceName($dsn);
if (in_array(self::normalizeProtocol($dsn->getProtocol()), ['newrelic+http', 'zipkintonewrelic+http'])) {
return self::buildExporter(
$dsn->getProtocol(),
$endpoint,
$serviceName,
self::getOptionFromDsn($dsn, 'licenseKey')
);
}
return self::buildExporter(
$dsn->getProtocol(),
$endpoint,
$serviceName
);
}
public function fromEnvironment(): ?SpanExporterInterface
{
$envValue = Configuration::getString(Env::OTEL_TRACES_EXPORTER, '');
$exporters = explode(',', $envValue);
//TODO "The SDK MAY accept a comma-separated list to enable setting multiple exporters"
if (1 !== count($exporters)) {
throw new InvalidArgumentException(sprintf('Env Var %s requires exactly 1 exporter', Env::OTEL_TRACES_EXPORTER));
}
$exporter = $exporters[0];
switch ($exporter) {
case Values::VALUE_NONE:
return null;
case Values::VALUE_OTLP:
$factory = '\OpenTelemetry\Contrib\Otlp\SpanExporterFactory';
return (new $factory())->fromEnvironment();
case 'console':
return self::buildExporter('console');
case Values::VALUE_JAEGER:
case Values::VALUE_ZIPKIN:
case Values::VALUE_NEWRELIC:
case 'zipkintonewrelic':
throw new InvalidArgumentException(sprintf('Exporter %s cannot be created from environment', $exporter));
default:
throw new InvalidArgumentException(sprintf('Invalid exporter name "%s"', $exporter));
}
}
private function resolveServiceName(DsnInterface $dsn): string
{
return self::getOptionFromDsn($dsn, 'serviceName') ?? $this->serviceName;
}
private static function getOptionFromDsn(DsnInterface $dsn, string $parameter): ?string
{
foreach ([$parameter, strtolower($parameter)] as $name) {
if (($option = $dsn->getOption($name)) !== null) {
return $option;
}
}
return null;
}
private static function buildExporter($protocol, string $endpoint = null, string $name = null, $args = null): SpanExporterInterface
{
$exporterClass = self::KNOWN_EXPORTERS[self::normalizeProtocol($protocol)];
self::validateExporterClass($exporterClass);
return call_user_func([$exporterClass, 'fromConnectionString'], $endpoint, $name, $args);
}
private static function validateProtocol(string $protocol): void
{
if (!array_key_exists(self::normalizeProtocol($protocol), self::KNOWN_EXPORTERS)) {
throw new InvalidArgumentException('Invalid exporter protocol: ' . $protocol);
}
}
private static function validateExporterClass(string $class): void
{
if (!class_exists($class)) {
throw new InvalidArgumentException('Could not find class: ' . $class);
}
}
private static function normalizeProtocol(string $scheme): string
{
return str_replace('https', 'http', $scheme);
}
}