adding exporter + transport registry (#862)

* adding exporter + transport registry
to remove the hidden dependency between SDK and contrib, move all of the exporter knowledge out of
exporter factory, and invent a registry to hold those values. The registry can be added to by contrib
modules as part of composer autoloading.
This also allows users to override things like transports (eg, provide their own grpc transport).
Remove exporter's fromConnectionString as it's not in spec, and I couldn't get it to work nicely with the
registry idea.

* fixing up examples

* allow callables to be registered as factories
adding an example of replacing a registered transport factory with another implementation

* Registry to FactoryRegistry

* new relic exporter factory, tests

* review suggestions

* tidy

* change fromEnvironment() to create() in factories
This commit is contained in:
Brett McBride 2022-11-24 06:39:24 +11:00 committed by GitHub
parent 116e46d964
commit 904da75ee6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
77 changed files with 766 additions and 774 deletions

View File

@ -63,6 +63,12 @@
"files": [
"src/Context/fiber/initialize_fiber_handler.php",
"src/API/Trace/functions.php",
"src/Contrib/Otlp/_register.php",
"src/Contrib/Grpc/_register.php",
"src/Contrib/Newrelic/_register.php",
"src/Contrib/Zipkin/_register.php",
"src/SDK/Metrics/MetricExporter/_register.php",
"src/SDK/Trace/SpanExporter/_register.php",
"src/SDK/Common/Dev/Compatibility/_load.php",
"src/SDK/Common/Util/functions.php",
"src/SDK/_autoload.php"

View File

@ -10,9 +10,14 @@ putenv('OTEL_EXPORTER_OTLP_METRICS_PROTOCOL=grpc');
putenv('OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4317');
putenv('OTEL_PHP_TRACES_PROCESSOR=batch');
echo 'autoloading SDK example starting...' . PHP_EOL;
// Composer autoloader will execute SDK/_autoload.php which will register global instrumentation from environment configuration
require dirname(__DIR__) . '/vendor/autoload.php';
$instrumentation = new \OpenTelemetry\API\Common\Instrumentation\CachedInstrumentation('demo');
$instrumentation->tracer()->spanBuilder('root')->startSpan()->end();
$instrumentation->meter()->createCounter('cnt')->add(1);
echo 'Finished!' . PHP_EOL;

View File

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
putenv('OTEL_PHP_AUTOLOAD_ENABLED=true');
putenv('OTEL_TRACES_EXPORTER=otlp');
putenv('OTEL_EXPORTER_OTLP_PROTOCOL=grpc');
putenv('OTEL_METRICS_EXPORTER=otlp');
putenv('OTEL_EXPORTER_OTLP_METRICS_PROTOCOL=grpc');
putenv('OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4317');
putenv('OTEL_PHP_TRACES_PROCESSOR=batch');
echo 'autoloading SDK example starting...' . PHP_EOL;
// Composer autoloader will execute SDK/_autoload.php which will register global instrumentation from environment configuration
require dirname(__DIR__) . '/vendor/autoload.php';
//create a transport factory to override the default grpc one (for both traces and metrics):
$factory = new class() implements \OpenTelemetry\SDK\Common\Export\TransportFactoryInterface {
public function create(string $endpoint, string $contentType, array $headers = [], $compression = null, float $timeout = 10., int $retryDelay = 100, int $maxRetries = 3, ?string $cacert = null, ?string $cert = null, ?string $key = null): \OpenTelemetry\SDK\Common\Export\TransportInterface
{
return new class() implements \OpenTelemetry\SDK\Common\Export\TransportInterface {
public function contentType(): string
{
return 'application/x-protobuf';
}
public function send(string $payload, ?\OpenTelemetry\SDK\Common\Future\CancellationInterface $cancellation = null): \OpenTelemetry\SDK\Common\Future\FutureInterface
{
return new \OpenTelemetry\SDK\Common\Future\CompletedFuture(true);
}
public function shutdown(?\OpenTelemetry\SDK\Common\Future\CancellationInterface $cancellation = null): bool
{
return true;
}
public function forceFlush(?\OpenTelemetry\SDK\Common\Future\CancellationInterface $cancellation = null): bool
{
return true;
}
};
}
};
\OpenTelemetry\SDK\FactoryRegistry::registerTransportFactory('grpc', $factory, true);
$instrumentation = new \OpenTelemetry\API\Common\Instrumentation\CachedInstrumentation('demo');
$instrumentation->tracer()->spanBuilder('root')->startSpan()->end();
$instrumentation->meter()->createCounter('cnt')->add(1);
echo 'Finished!' . PHP_EOL;

View File

@ -2,8 +2,9 @@
declare(strict_types=1);
use OpenTelemetry\Contrib\Otlp\StreamMetricExporter;
use OpenTelemetry\Contrib\Otlp\MetricExporter;
use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Common\Export\Stream\StreamTransportFactory;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeFactory;
use OpenTelemetry\SDK\Common\Time\ClockFactory;
use OpenTelemetry\SDK\Metrics\Exemplar\ExemplarFilter\WithSampledTraceExemplarFilter;
@ -27,7 +28,7 @@ var_dump(get_class($tracer));
//metrics
$clock = ClockFactory::getDefault();
$reader = new ExportingReader(new StreamMetricExporter(STDOUT), $clock);
$reader = new ExportingReader(new MetricExporter((new StreamTransportFactory())->create(STDOUT, 'application/x-ndjson')), $clock);
$views = new CriteriaViewRegistry();
$meterProvider = new MeterProvider(
null,

View File

@ -13,7 +13,7 @@ use OpenTelemetry\SDK\Metrics\MetricReader\ExportingReader;
$clock = ClockFactory::getDefault();
$reader = new ExportingReader(
new MetricExporter(
PsrTransportFactory::discover()->create('http://collector:4318/v1/metrics', 'application/json')
PsrTransportFactory::discover()->create('http://collector:4318/v1/metrics', \OpenTelemetry\Contrib\Otlp\ContentTypes::JSON)
),
$clock
);

View File

@ -2,8 +2,6 @@
declare(strict_types=1);
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use OpenTelemetry\API\Common\Instrumentation\CachedInstrumentation;
use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator;
use OpenTelemetry\Contrib\Otlp\MetricExporter;
@ -30,8 +28,7 @@ $spanExporter = new InMemoryExporter();
$reader = new ExportingReader(
new MetricExporter(
(new PsrTransportFactory(new Client(), new HttpFactory(), new HttpFactory()))
->create('http://collector:4318/v1/metrics', 'application/x-protobuf')
PsrTransportFactory::discover()->create('http://collector:4318/v1/metrics', 'application/x-protobuf')
),
ClockFactory::getDefault()
);

View File

@ -4,7 +4,7 @@ declare(strict_types=1);
require __DIR__ . '/../../../vendor/autoload.php';
use OpenTelemetry\SDK\Common\Time\ClockFactory;
use OpenTelemetry\SDK\Trace\SpanExporter\ConsoleSpanExporter;
use OpenTelemetry\SDK\Trace\SpanExporter\ConsoleSpanExporterFactory;
use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;
@ -15,7 +15,7 @@ echo sprintf('Sending batches every %dms and on shutdown', $delayMillis) . PHP_E
$tracerProvider = new TracerProvider(
new BatchSpanProcessor(
new ConsoleSpanExporter(),
(new ConsoleSpanExporterFactory())->create(),
ClockFactory::getDefault(),
2048, //max spans to queue before sending to exporter
$delayMillis, //batch delay milliseconds

View File

@ -3,7 +3,7 @@
declare(strict_types=1);
require __DIR__ . '/../../../vendor/autoload.php';
use OpenTelemetry\SDK\Trace\SpanExporter\ConsoleSpanExporter;
use OpenTelemetry\SDK\Trace\SpanExporter\ConsoleSpanExporterFactory;
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;
@ -15,7 +15,7 @@ echo 'Starting ConsoleSpanExporter' . PHP_EOL;
$tracerProvider = new TracerProvider(
new SimpleSpanProcessor(
new ConsoleSpanExporter()
(new ConsoleSpanExporterFactory())->create()
)
);

View File

@ -11,13 +11,15 @@ putenv('OTEL_SERVICE_NAME=example-app');
putenv('OTEL_LOG_LEVEL=warning');
putenv('OTEL_TRACES_SAMPLER=traceidratio');
putenv('OTEL_TRACES_SAMPLER_ARG=0.95');
putenv('OTEL_TRACES_EXPORTER=console');
putenv('OTEL_TRACES_EXPORTER=otlp');
putenv('OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4318');
putenv('OTEL_EXPORTER_OTLP_PROTOCOL=http/json');
putenv('OTEL_PHP_TRACES_PROCESSOR=batch');
putenv('OTEL_BSP_SCHEDULE_DELAY=10000');
echo 'Creating Exporter From Environment' . PHP_EOL;
$tracerProvider = (new TracerProviderFactory('example'))->create();
$tracerProvider = (new TracerProviderFactory())->create();
$tracer = $tracerProvider->getTracer('io.opentelemetry.contrib.php');

View File

@ -3,14 +3,14 @@
declare(strict_types=1);
require __DIR__ . '/../../../vendor/autoload.php';
use OpenTelemetry\SDK\Trace\SpanExporter\ConsoleSpanExporter;
use OpenTelemetry\SDK\Trace\SpanExporter\ConsoleSpanExporterFactory;
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;
// Boilerplate setup to create a new tracer with console output
$tracerProvider = new TracerProvider(
new SimpleSpanProcessor(
new ConsoleSpanExporter()
(new ConsoleSpanExporterFactory())->create()
)
);
$tracer = $tracerProvider->getTracer('io.opentelemetry.contrib.php');

View File

@ -4,11 +4,15 @@ declare(strict_types=1);
require __DIR__ . '/../../../../vendor/autoload.php';
use OpenTelemetry\Contrib\Jaeger\Exporter as JaegerExporter;
use OpenTelemetry\SDK\Common\Export\Http\PsrTransportFactory;
use OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSampler;
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;
$exporter = JaegerExporter::fromConnectionString('http://jaeger:9412/api/v2/spans', 'AlwaysOnJaegerExample');
$exporter = new JaegerExporter(
'AlwaysOnJaegerExample',
PsrTransportFactory::discover()->create('http://jaeger:9412/api/v2/spans')
);
$tracerProvider = new TracerProvider(
new SimpleSpanProcessor($exporter),
new AlwaysOnSampler(),

View File

@ -3,9 +3,6 @@
declare(strict_types=1);
require __DIR__ . '/../../../../vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use OpenTelemetry\Contrib\Newrelic\Exporter as NewrelicExporter;
use OpenTelemetry\SDK\Common\Time\ClockFactory;
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;
@ -30,21 +27,8 @@ if ($licenseKey == false) {
* Default Trace API endpoint: https://trace-api.newrelic.com/trace/v1
* EU data centers: https://trace-api.eu.newrelic.com/trace/v1
*/
$endpointUrl = getenv('NEW_RELIC_ENDPOINT');
if ($endpointUrl == false) {
$endpointUrl = 'https://trace-api.newrelic.com/trace/v1';
}
$newrelicExporter = new NewrelicExporter(
'alwaysOnNewrelicExample',
$endpointUrl,
$licenseKey,
new Client(),
new HttpFactory(),
new HttpFactory()
);
putenv('NEW_RELIC_ENDPOINT', 'https://trace-api.newrelic.com/trace/v1');
$newrelicExporter = (new \OpenTelemetry\Contrib\Newrelic\SpanExporterFactory())->create();
echo 'Starting Newrelic example';
$tracerProvider = new TracerProvider(new SimpleSpanProcessor($newrelicExporter));

View File

@ -13,7 +13,7 @@ require __DIR__ . '/../../../../vendor/autoload.php';
*/
putenv('OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4317');
putenv('OTEL_EXPORTER_OTLP_PROTOCOL=grpc');
$factory = new TracerProviderFactory('otlp-grpc-demo');
$factory = new TracerProviderFactory();
$tracerProvider = $factory->create();
$tracer = $tracerProvider->getTracer('io.opentelemetry.contrib.php');

View File

@ -10,13 +10,12 @@ require __DIR__ . '/../../../../vendor/autoload.php';
*/
putenv('OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4318');
putenv('OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf');
$factory = new \OpenTelemetry\SDK\Trace\TracerProviderFactory('otlp-http-demo');
$factory = new \OpenTelemetry\SDK\Trace\TracerProviderFactory();
$tracerProvider = $factory->create();
$tracer = $tracerProvider->getTracer('io.opentelemetry.contrib.php');
$root = $span = $tracer->spanBuilder('root')->startSpan();
$root->end();
$tracer->spanBuilder('root')->startSpan()->end();
echo PHP_EOL . 'OTLP http/protobuf example complete! ';
echo PHP_EOL;

View File

@ -3,18 +3,15 @@
declare(strict_types=1);
require __DIR__ . '/../../../../vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use OpenTelemetry\Contrib\Zipkin\Exporter as ZipkinExporter;
use OpenTelemetry\SDK\Common\Export\Http\PsrTransportFactory;
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;
$transport = PsrTransportFactory::discover()->create('http://zipkin:9411/api/v2/spans', 'application/json');
$zipkinExporter = new ZipkinExporter(
'alwaysOnZipkinExample',
'http://zipkin:9411/api/v2/spans',
new Client(),
new HttpFactory(),
new HttpFactory()
$transport
);
$tracerProvider = new TracerProvider(
new SimpleSpanProcessor(

View File

@ -3,9 +3,9 @@
declare(strict_types=1);
require __DIR__ . '/../../../../vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use OpenTelemetry\Contrib\ZipkinToNewrelic\Exporter as ZipkinToNewrelicExporter;
use OpenTelemetry\SDK\Common\Configuration\Configuration;
use OpenTelemetry\SDK\Common\Export\Http\PsrTransportFactory;
use OpenTelemetry\SDK\Common\Time\ClockFactory;
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;
@ -31,19 +31,11 @@ if ($licenseKey == false) {
* EU data centers: https://trace-api.eu.newrelic.com/trace/v1
*/
$endpointUrl = getenv('NEW_RELIC_ENDPOINT');
if ($endpointUrl == false) {
$endpointUrl = 'https://trace-api.newrelic.com/trace/v1';
}
$endpointUrl = Configuration::getString('NEW_RELIC_ENDPOINT', 'https://trace-api.newrelic.com/trace/v1');
$transport = PsrTransportFactory::discover()->create($endpointUrl, $licenseKey);
$zipkinToNewrelicExporter = new ZipkinToNewrelicExporter(
'alwaysOnZipkinToNewrelicExample',
$endpointUrl,
$licenseKey,
new Client(),
new HttpFactory(),
new HttpFactory()
'AlwaysOnZipkinToNewrelicExample',
$transport
);
echo 'Starting ZipkinToNewRelic example';

View File

@ -3,10 +3,9 @@
declare(strict_types=1);
require __DIR__ . '/../../../vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use OpenTelemetry\Contrib\Jaeger\Exporter as JaegerExporter;
use OpenTelemetry\Contrib\Zipkin\Exporter as ZipkinExporter;
use OpenTelemetry\SDK\Common\Export\Http\PsrTransportFactory;
use OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSampler;
use OpenTelemetry\SDK\Trace\Span;
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
@ -18,19 +17,13 @@ $sampler = new AlwaysOnSampler();
// zipkin exporter
$zipkinExporter = new ZipkinExporter(
$serviceName,
'http://zipkin:9411/api/v2/spans',
new Client(),
new HttpFactory(),
new HttpFactory()
PsrTransportFactory::discover()->create('http://zipkin:9411/api/v2/spans')
);
// jaeger exporter
$jaegerExporter = new JaegerExporter(
$serviceName,
'http://jaeger:9412/api/v2/spans',
new Client(),
new HttpFactory(),
new HttpFactory()
PsrTransportFactory::discover()->create('http://jaeger:9411/api/v2/spans')
);
$tracerProvider = new TracerProvider(

View File

@ -11,7 +11,7 @@ putenv('OTEL_RESOURCE_ATTRIBUTES=foo=bar'); //env detector will add this to trac
echo 'Handling Resource Detectors From Environment' . PHP_EOL;
$tracerProvider = (new TracerProviderFactory('example'))->create();
$tracerProvider = (new TracerProviderFactory())->create();
$tracer = $tracerProvider->getTracer('io.opentelemetry.contrib.php');

View File

@ -6,7 +6,7 @@ require __DIR__ . '/../../../vendor/autoload.php';
use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Resource\ResourceInfo;
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
use OpenTelemetry\SDK\Trace\SpanExporter\ConsoleSpanExporter;
use OpenTelemetry\SDK\Trace\SpanExporter\ConsoleSpanExporterFactory;
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;
use OpenTelemetry\SemConv\ResourceAttributes;
@ -24,7 +24,7 @@ $resource = ResourceInfoFactory::merge(ResourceInfo::create(Attributes::create([
$tracerProvider = new TracerProvider(
new SimpleSpanProcessor(
new ConsoleSpanExporter()
(new ConsoleSpanExporterFactory())->create()
),
null,
$resource

View File

@ -3,7 +3,7 @@
declare(strict_types=1);
require __DIR__ . '/../../vendor/autoload.php';
use OpenTelemetry\SDK\Trace\SpanExporter\ConsoleSpanExporter;
use OpenTelemetry\SDK\Trace\SpanExporter\ConsoleSpanExporterFactory;
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;
@ -11,7 +11,7 @@ echo 'Starting ConsoleSpanExporter' . PHP_EOL;
$tracerProvider = new TracerProvider(
new SimpleSpanProcessor(
new ConsoleSpanExporter()
(new ConsoleSpanExporterFactory())->create()
)
);

View File

@ -14,9 +14,8 @@ require __DIR__ . '/../../../vendor/autoload.php';
* will be the last log entry
*/
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use OpenTelemetry\Contrib\Jaeger\Exporter as JaegerExporter;
use OpenTelemetry\SDK\Common\Export\Http\PsrTransportFactory;
use OpenTelemetry\SDK\Common\Time\ClockFactory;
use OpenTelemetry\SDK\Logs\SimplePsrFileLogger;
use OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSampler;
@ -43,10 +42,7 @@ $exporterEndpoint = 'http://jaeger:9412/api/v2/spans';
//$exporterEndpoint = 'http://example.com:9412/api/v2/spans';
$exporter = new JaegerExporter(
'alwaysOnJaegerExample',
'http://jaeger:9412/api/v2/spans',
new Client(),
new HttpFactory(),
new HttpFactory()
PsrTransportFactory::discover()->create('http://jaeger:9412/api/v2/spans', 'application/json'),
);
/**
* Decorate the Exporter

View File

@ -17,3 +17,15 @@ parameters:
paths:
- tests/Unit/SDK/Common/Configuration/Resolver/PhpIniResolverTest.php
- tests/Unit/SDK/Common/Configuration/Resolver/CompositeResolverTest.php
-
message: "#Call to an undefined method .*:allows.*#"
paths:
- tests
-
message: "#Call to an undefined method .*:shouldReceive.*#"
paths:
- tests
-
message: "#Call to an undefined method .*:shouldHaveReceived.*#"
paths:
- tests

View File

@ -0,0 +1,4 @@
<?php
declare(strict_types=1);
\OpenTelemetry\SDK\FactoryRegistry::registerTransportFactory('grpc', \OpenTelemetry\Contrib\Grpc\GrpcTransportFactory::class);

View File

@ -51,13 +51,4 @@ class AgentExporter implements SpanExporterInterface
return true;
}
/** @inheritDoc */
public static function fromConnectionString(string $endpointUrl, string $name, $args = null): AgentExporter
{
return new AgentExporter(
$name,
$endpointUrl
);
}
}

View File

@ -4,21 +4,8 @@ declare(strict_types=1);
namespace OpenTelemetry\Contrib\Jaeger;
use Http\Discovery\HttpClientDiscovery;
use Http\Discovery\Psr17FactoryDiscovery;
use OpenTelemetry\Contrib\Zipkin;
class Exporter extends Zipkin\Exporter
{
/** @inheritDoc */
public static function fromConnectionString(string $endpointUrl, string $name, $args = null)
{
return new Exporter(
$name,
$endpointUrl,
HttpClientDiscovery::find(),
Psr17FactoryDiscovery::findRequestFactory(),
Psr17FactoryDiscovery::findStreamFactory()
);
}
}

View File

@ -4,8 +4,6 @@ declare(strict_types=1);
namespace OpenTelemetry\Contrib\Jaeger;
use Http\Discovery\HttpClientDiscovery;
use Http\Discovery\Psr17FactoryDiscovery;
use OpenTelemetry\SDK\Trace\Behavior\SpanExporterTrait;
use OpenTelemetry\SDK\Trace\Behavior\UsesSpanConverterTrait;
use OpenTelemetry\SDK\Trace\SpanExporterInterface;
@ -45,16 +43,4 @@ class HttpCollectorExporter implements SpanExporterInterface
return true;
}
/** @inheritDoc */
public static function fromConnectionString(string $endpointUrl, string $name, $args = null): HttpCollectorExporter
{
return new HttpCollectorExporter(
$endpointUrl,
$name,
HttpClientDiscovery::find(),
Psr17FactoryDiscovery::findRequestFactory(),
Psr17FactoryDiscovery::findStreamFactory()
);
}
}

View File

@ -4,20 +4,13 @@ declare(strict_types=1);
namespace OpenTelemetry\Contrib\Newrelic;
use Exception;
use Http\Discovery\HttpClientDiscovery;
use Http\Discovery\Psr17FactoryDiscovery;
use JsonException;
use OpenTelemetry\SDK\Behavior\LogsMessagesTrait;
use OpenTelemetry\SDK\Common\Export\Http\PsrTransportFactory;
use OpenTelemetry\SDK\Common\Export\TransportInterface;
use OpenTelemetry\SDK\Common\Future\CancellationInterface;
use OpenTelemetry\SDK\Common\Future\FutureInterface;
use OpenTelemetry\SDK\Trace\Behavior\UsesSpanConverterTrait;
use OpenTelemetry\SDK\Trace\SpanExporterInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Throwable;
/**
@ -33,7 +26,7 @@ class Exporter implements SpanExporterInterface
use LogsMessagesTrait;
use UsesSpanConverterTrait;
private const DATA_FORMAT_VERSION_DEFAULT = '1';
public const DATA_FORMAT_VERSION_DEFAULT = '1';
private TransportInterface $transport;
private string $name;
@ -41,21 +34,13 @@ class Exporter implements SpanExporterInterface
public function __construct(
$name,
TransportInterface $transport,
string $endpointUrl,
string $licenseKey,
ClientInterface $client,
RequestFactoryInterface $requestFactory,
StreamFactoryInterface $streamFactory,
SpanConverter $spanConverter = null,
string $dataFormatVersion = Exporter::DATA_FORMAT_VERSION_DEFAULT
SpanConverter $spanConverter = null
) {
$this->transport = (new PsrTransportFactory($client, $requestFactory, $streamFactory))->create($endpointUrl, 'application/json', [
'Api-Key' => $licenseKey,
'Data-Format' => 'newrelic',
'Data-Format-Version' => $dataFormatVersion,
]);
$this->name = $name;
$this->endpointUrl = $endpointUrl;
$this->transport = $transport;
$this->setSpanConverter($spanConverter ?? new SpanConverter($name));
}
@ -76,23 +61,6 @@ class Exporter implements SpanExporterInterface
'spans' => $this->getSpanConverter()->convert($spans), ]];
}
/** @inheritDoc */
public static function fromConnectionString(string $endpointUrl, string $name, $args)
{
if (!is_string($args)) {
throw new Exception('Invalid license key.');
}
return new Exporter(
$name,
$endpointUrl,
$args,
HttpClientDiscovery::find(),
Psr17FactoryDiscovery::findRequestFactory(),
Psr17FactoryDiscovery::findStreamFactory()
);
}
public function export(iterable $batch, ?CancellationInterface $cancellation = null): FutureInterface
{
return $this->transport

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Contrib\Newrelic;
use OpenTelemetry\SDK\Common\Configuration\Configuration;
use OpenTelemetry\SDK\Common\Export\Http\PsrTransportFactory;
use OpenTelemetry\SDK\Trace\SpanExporter\SpanExporterFactoryInterface;
use OpenTelemetry\SDK\Trace\SpanExporterInterface;
class SpanExporterFactory implements SpanExporterFactoryInterface
{
public function create(): SpanExporterInterface
{
$licenseKey = Configuration::getString('NEW_RELIC_INSERT_KEY');
$endpointUrl = Configuration::getString('NEW_RELIC_ENDPOINT', 'https://trace-api.newrelic.com/trace/v1');
$dataFormatVersion = Exporter::DATA_FORMAT_VERSION_DEFAULT;
$transport = PsrTransportFactory::discover()->create($endpointUrl, 'application/json', [
'Api-Key' => $licenseKey,
'Data-Format' => 'newrelic',
'Data-Format-Version' => $dataFormatVersion,
]);
return new Exporter('newrelic', $transport, $endpointUrl);
}
}

View File

@ -0,0 +1,5 @@
<?php
declare(strict_types=1);
\OpenTelemetry\SDK\FactoryRegistry::registerSpanExporterFactory('newrelic', \OpenTelemetry\Contrib\Newrelic\SpanExporterFactory::class);

View File

@ -20,6 +20,9 @@
"autoload": {
"psr-4": {
"OpenTelemetry\\Contrib\\Newrelic\\": "."
}
},
"files": [
"_register.php"
]
}
}

View File

@ -5,16 +5,16 @@ declare(strict_types=1);
namespace OpenTelemetry\Contrib\Otlp;
use OpenTelemetry\API\Common\Signal\Signals;
use OpenTelemetry\Contrib\Grpc\GrpcTransportFactory;
use OpenTelemetry\SDK\Common\Configuration\Configuration;
use OpenTelemetry\SDK\Common\Configuration\KnownValues;
use OpenTelemetry\SDK\Common\Configuration\Variables;
use OpenTelemetry\SDK\Common\Export\Http\PsrTransportFactory;
use OpenTelemetry\SDK\Common\Export\TransportInterface;
use OpenTelemetry\SDK\FactoryRegistry;
use OpenTelemetry\SDK\Metrics\MetricExporterFactoryInterface;
use OpenTelemetry\SDK\Metrics\MetricExporterInterface;
use UnexpectedValueException;
class MetricExporterFactory
class MetricExporterFactory implements MetricExporterFactoryInterface
{
/**
* @psalm-suppress ArgumentTypeCoercion
@ -44,19 +44,20 @@ class MetricExporterFactory
: Configuration::getMap(Variables::OTEL_EXPORTER_OTLP_HEADERS);
$headers += OtlpUtil::getUserAgentHeader();
$factory = FactoryRegistry::transportFactory($protocol);
switch ($protocol) {
case KnownValues::VALUE_GRPC:
return (new GrpcTransportFactory())->create(
return $factory->create(
$endpoint . OtlpUtil::method(Signals::METRICS),
ContentTypes::PROTOBUF,
$headers,
$headers
);
case KnownValues::VALUE_HTTP_PROTOBUF:
case KnownValues::VALUE_HTTP_JSON:
return PsrTransportFactory::discover()->create(
return $factory->create(
$endpoint,
Protocols::contentType($protocol),
$headers,
$headers
);
default:
throw new UnexpectedValueException('Unknown otlp protocol: ' . $protocol);

View File

@ -5,31 +5,24 @@ declare(strict_types=1);
namespace OpenTelemetry\Contrib\Otlp;
use OpenTelemetry\API\Common\Signal\Signals;
use OpenTelemetry\Contrib\Grpc\GrpcTransportFactory;
use OpenTelemetry\SDK\Behavior\LogsMessagesTrait;
use OpenTelemetry\SDK\Common\Configuration\Configuration;
use OpenTelemetry\SDK\Common\Configuration\Defaults;
use OpenTelemetry\SDK\Common\Configuration\KnownValues;
use OpenTelemetry\SDK\Common\Configuration\Variables;
use OpenTelemetry\SDK\Common\Export\TransportFactoryInterface;
use OpenTelemetry\SDK\Common\Export\TransportInterface;
use OpenTelemetry\SDK\Common\Otlp\HttpEndpointResolver;
use OpenTelemetry\SDK\FactoryRegistry;
use OpenTelemetry\SDK\Trace\SpanExporter\SpanExporterFactoryInterface;
use OpenTelemetry\SDK\Trace\SpanExporterInterface;
use UnexpectedValueException;
class SpanExporterFactory
class SpanExporterFactory implements SpanExporterFactoryInterface
{
use LogsMessagesTrait;
private ?TransportFactoryInterface $transportFactory;
private const DEFAULT_COMPRESSION = 'none';
private const FACTORIES = [
KnownValues::VALUE_GRPC => GrpcTransportFactory::class,
KnownValues::VALUE_HTTP_PROTOBUF => OtlpHttpTransportFactory::class,
KnownValues::VALUE_HTTP_JSON => OtlpHttpTransportFactory::class,
KnownValues::VALUE_HTTP_NDJSON => OtlpHttpTransportFactory::class,
];
public function __construct(?TransportFactoryInterface $transportFactory = null)
{
@ -39,7 +32,7 @@ class SpanExporterFactory
/**
* @psalm-suppress ArgumentTypeCoercion
*/
public function fromEnvironment(): SpanExporterInterface
public function create(): SpanExporterInterface
{
$transport = $this->buildTransport();
@ -48,6 +41,7 @@ class SpanExporterFactory
/**
* @psalm-suppress ArgumentTypeCoercion
* @psalm-suppress UndefinedClass
*/
private function buildTransport(): TransportInterface
{
@ -57,10 +51,7 @@ class SpanExporterFactory
$headers = $this->getHeaders();
$compression = $this->getCompression();
if (!$this->transportFactory && !array_key_exists($protocol, self::FACTORIES)) {
throw new UnexpectedValueException('Unknown OTLP protocol: ' . $protocol);
}
$factoryClass = self::FACTORIES[$protocol];
$factoryClass = FactoryRegistry::transportFactory($protocol);
$factory = $this->transportFactory ?: new $factoryClass();
return $factory->create($endpoint, $contentType, $headers, $compression);

View File

@ -0,0 +1,7 @@
<?php
declare(strict_types=1);
\OpenTelemetry\SDK\FactoryRegistry::registerSpanExporterFactory('otlp', \OpenTelemetry\Contrib\Otlp\SpanExporterFactory::class);
\OpenTelemetry\SDK\FactoryRegistry::registerMetricExporterFactory('otlp', \OpenTelemetry\Contrib\Otlp\MetricExporterFactory::class);
\OpenTelemetry\SDK\FactoryRegistry::registerTransportFactory('http', \OpenTelemetry\Contrib\Otlp\OtlpHttpTransportFactory::class);

View File

@ -4,20 +4,14 @@ declare(strict_types=1);
namespace OpenTelemetry\Contrib\Zipkin;
use Http\Discovery\HttpClientDiscovery;
use Http\Discovery\Psr17FactoryDiscovery;
use JsonException;
use OpenTelemetry\SDK\Behavior\LogsMessagesTrait;
use OpenTelemetry\SDK\Common\Export\Http\PsrTransportFactory;
use OpenTelemetry\SDK\Common\Export\TransportInterface;
use OpenTelemetry\SDK\Common\Future\CancellationInterface;
use OpenTelemetry\SDK\Common\Future\FutureInterface;
use OpenTelemetry\SDK\Trace\Behavior\UsesSpanConverterTrait;
use OpenTelemetry\SDK\Trace\SpanConverterInterface;
use OpenTelemetry\SDK\Trace\SpanExporterInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Throwable;
/**
@ -33,13 +27,10 @@ class Exporter implements SpanExporterInterface
public function __construct(
$name,
string $endpointUrl,
ClientInterface $client,
RequestFactoryInterface $requestFactory,
StreamFactoryInterface $streamFactory,
TransportInterface $transport,
SpanConverterInterface $spanConverter = null
) {
$this->transport = (new PsrTransportFactory($client, $requestFactory, $streamFactory))->create($endpointUrl, 'application/json');
$this->transport = $transport;
$this->setSpanConverter($spanConverter ?? new SpanConverter($name));
}
@ -54,18 +45,6 @@ class Exporter implements SpanExporterInterface
);
}
/** @inheritDoc */
public static function fromConnectionString(string $endpointUrl, string $name, $args = null)
{
return new Exporter(
$name,
$endpointUrl,
HttpClientDiscovery::find(),
Psr17FactoryDiscovery::findRequestFactory(),
Psr17FactoryDiscovery::findStreamFactory()
);
}
public function export(iterable $batch, ?CancellationInterface $cancellation = null): FutureInterface
{
return $this->transport

View File

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Contrib\Zipkin;
use OpenTelemetry\SDK\Common\Configuration\Configuration;
use OpenTelemetry\SDK\Common\Configuration\Variables;
use OpenTelemetry\SDK\Common\Export\Http\PsrTransportFactory;
use OpenTelemetry\SDK\Trace\SpanExporter\SpanExporterFactoryInterface;
use OpenTelemetry\SDK\Trace\SpanExporterInterface;
class SpanExporterFactory implements SpanExporterFactoryInterface
{
public function create(): SpanExporterInterface
{
$endpoint = Configuration::getString(Variables::OTEL_EXPORTER_ZIPKIN_ENDPOINT);
$transport = PsrTransportFactory::discover()->create($endpoint, 'application/json');
return new Exporter('zipkin', $transport);
}
}

View File

@ -0,0 +1,5 @@
<?php
declare(strict_types=1);
\OpenTelemetry\SDK\FactoryRegistry::registerSpanExporterFactory('zipkin', \OpenTelemetry\Contrib\Zipkin\SpanExporterFactory::class);

View File

@ -4,9 +4,6 @@ declare(strict_types=1);
namespace OpenTelemetry\Contrib\ZipkinToNewrelic;
use Exception;
use Http\Discovery\HttpClientDiscovery;
use Http\Discovery\Psr17FactoryDiscovery;
use JsonException;
use OpenTelemetry\SDK\Behavior\LogsMessagesTrait;
use OpenTelemetry\SDK\Common\Export\Http\PsrTransportFactory;
@ -15,9 +12,6 @@ use OpenTelemetry\SDK\Common\Future\CancellationInterface;
use OpenTelemetry\SDK\Common\Future\FutureInterface;
use OpenTelemetry\SDK\Trace\Behavior\UsesSpanConverterTrait;
use OpenTelemetry\SDK\Trace\SpanExporterInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Throwable;
/**
@ -36,20 +30,26 @@ class Exporter implements SpanExporterInterface
private TransportInterface $transport;
public function __construct(
$name,
string $endpointUrl,
string $licenseKey,
ClientInterface $client,
RequestFactoryInterface $requestFactory,
StreamFactoryInterface $streamFactory,
string $name,
TransportInterface $transport,
SpanConverter $spanConverter = null
) {
$this->transport = (new PsrTransportFactory($client, $requestFactory, $streamFactory))->create($endpointUrl, 'application/json', [
$this->transport = $transport;
$this->setSpanConverter($spanConverter ?? new SpanConverter($name));
}
public static function create(
string $name,
string $endpointUrl,
string $licenseKey
): self {
$transport = PsrTransportFactory::discover()->create($endpointUrl, 'application/json', [
'Api-Key' => $licenseKey,
'Data-Format' => 'zipkin',
'Data-Format-Version' => '2',
]);
$this->setSpanConverter($spanConverter ?? new SpanConverter($name));
return new self($name, $transport);
}
/**
@ -63,23 +63,6 @@ class Exporter implements SpanExporterInterface
);
}
/** @inheritDoc */
public static function fromConnectionString(string $endpointUrl, string $name, $args): Exporter
{
if (!is_string($args)) {
throw new Exception('Invalid license key.');
}
return new Exporter(
$name,
$endpointUrl,
$args,
HttpClientDiscovery::find(),
Psr17FactoryDiscovery::findRequestFactory(),
Psr17FactoryDiscovery::findStreamFactory()
);
}
public function export(iterable $batch, ?CancellationInterface $cancellation = null): FutureInterface
{
return $this->transport

View File

@ -103,9 +103,9 @@ interface ValueTypes
* Exporter Selection
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md#exporter-selection
*/
public const OTEL_TRACES_EXPORTER = VariableTypes::STRING;
public const OTEL_METRICS_EXPORTER = VariableTypes::STRING;
public const OTEL_LOGS_EXPORTER = VariableTypes::STRING;
public const OTEL_TRACES_EXPORTER = VariableTypes::LIST;
public const OTEL_METRICS_EXPORTER = VariableTypes::LIST;
public const OTEL_LOGS_EXPORTER = VariableTypes::LIST;
/**
* Metrics SDK Configuration
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md#metrics-sdk-configuration

127
src/SDK/FactoryRegistry.php Normal file
View File

@ -0,0 +1,127 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\SDK;
use OpenTelemetry\SDK\Common\Export\TransportFactoryInterface;
use OpenTelemetry\SDK\Metrics\MetricExporterFactoryInterface;
use OpenTelemetry\SDK\Trace\SpanExporter\SpanExporterFactoryInterface;
use RuntimeException;
class FactoryRegistry
{
private static array $spanExporterFactories = [];
private static array $transportFactories = [];
private static array $metricExporterFactories = [];
/**
* @param TransportFactoryInterface|class-string<TransportFactoryInterface> $factory
*/
public static function registerTransportFactory(string $protocol, $factory, bool $clobber = false): void
{
if (!$clobber && array_key_exists($protocol, self::$transportFactories)) {
return;
}
if (!is_subclass_of($factory, TransportFactoryInterface::class)) {
trigger_error(
sprintf(
'Cannot register transport factory: %s must exist and implement %s',
is_string($factory) ? $factory : get_class($factory),
TransportFactoryInterface::class
),
E_USER_WARNING
);
return;
}
self::$transportFactories[$protocol] = $factory;
}
/**
* @param SpanExporterFactoryInterface|class-string<SpanExporterFactoryInterface> $factory
*/
public static function registerSpanExporterFactory(string $exporter, $factory, bool $clobber = false): void
{
if (!$clobber && array_key_exists($exporter, self::$spanExporterFactories)) {
return;
}
if (!is_subclass_of($factory, SpanExporterFactoryInterface::class)) {
trigger_error(
sprintf(
'Cannot register span exporter factory: %s must exist and implement %s',
is_string($factory) ? $factory : get_class($factory),
SpanExporterFactoryInterface::class
),
E_USER_WARNING
);
return;
}
self::$spanExporterFactories[$exporter] = $factory;
}
/**
* @param MetricExporterFactoryInterface|class-string<MetricExporterFactoryInterface> $factory
*/
public static function registerMetricExporterFactory(string $exporter, $factory, bool $clobber = false): void
{
if (!$clobber && array_key_exists($exporter, self::$metricExporterFactories)) {
return;
}
if (!is_subclass_of($factory, MetricExporterFactoryInterface::class)) {
trigger_error(
sprintf(
'Cannot register metric factory: %s must exist and implement %s',
is_string($factory) ? $factory : get_class($factory),
MetricExporterFactoryInterface::class
),
E_USER_WARNING
);
return;
}
self::$metricExporterFactories[$exporter] = $factory;
}
public static function spanExporterFactory(string $exporter): SpanExporterFactoryInterface
{
if (!array_key_exists($exporter, self::$spanExporterFactories)) {
throw new RuntimeException('Span exporter factory not defined for: ' . $exporter);
}
$class = self::$spanExporterFactories[$exporter];
$factory = (is_callable($class)) ? $class : new $class();
assert($factory instanceof SpanExporterFactoryInterface);
return $factory;
}
/**
* Get transport factory registered for protocol. If $protocol contains a content-type eg `http/xyz` then
* only the first part, `http`, is used.
*/
public static function transportFactory(string $protocol): TransportFactoryInterface
{
$protocol = explode('/', $protocol)[0];
if (!array_key_exists($protocol, self::$transportFactories)) {
throw new RuntimeException('Transport factory not defined for protocol: ' . $protocol);
}
$class = self::$transportFactories[$protocol];
$factory = (is_callable($class)) ? $class : new $class();
assert($factory instanceof TransportFactoryInterface);
return $factory;
}
public static function metricExporterFactory(string $exporter): MetricExporterFactoryInterface
{
if (!array_key_exists($exporter, self::$metricExporterFactories)) {
throw new RuntimeException('Metric exporter factory not registered for protocol: ' . $exporter);
}
$class = self::$metricExporterFactories[$exporter];
$factory = (is_callable($class)) ? $class : new $class();
assert($factory instanceof MetricExporterFactoryInterface);
return $factory;
}
}

View File

@ -4,11 +4,13 @@ declare(strict_types=1);
namespace OpenTelemetry\SDK\Metrics;
use InvalidArgumentException;
use OpenTelemetry\SDK\Behavior\LogsMessagesTrait;
use OpenTelemetry\SDK\Common\Configuration\Configuration;
use OpenTelemetry\SDK\Common\Configuration\KnownValues;
use OpenTelemetry\SDK\Common\Configuration\Variables;
use OpenTelemetry\SDK\Common\Time\ClockFactory;
use OpenTelemetry\SDK\FactoryRegistry;
use OpenTelemetry\SDK\Metrics\Exemplar\ExemplarFilter\AllExemplarFilter;
use OpenTelemetry\SDK\Metrics\Exemplar\ExemplarFilter\NoneExemplarFilter;
use OpenTelemetry\SDK\Metrics\Exemplar\ExemplarFilter\WithSampledTraceExemplarFilter;
@ -22,26 +24,26 @@ class MeterProviderFactory
{
use LogsMessagesTrait;
private const KNOWN_EXPORTER_FACTORIES = [
KnownValues::VALUE_OTLP => '\OpenTelemetry\Contrib\Otlp\MetricExporterFactory',
];
public function create(): MeterProviderInterface
{
if (Sdk::isDisabled()) {
return new NoopMeterProvider();
}
$exporterName = Configuration::getString(Variables::OTEL_METRICS_EXPORTER);
if ($exporterName === KnownValues::VALUE_NONE) {
$exporter = new NoopMetricExporter();
} elseif (!array_key_exists($exporterName, self::KNOWN_EXPORTER_FACTORIES)) {
self::logError('Factory cannot create exporter: ' . $exporterName);
$exporter = new NoopMetricExporter();
} else {
$factoryClass = self::KNOWN_EXPORTER_FACTORIES[$exporterName];
$factory = new $factoryClass();
$exporter = $factory->create();
$exporters = Configuration::getList(Variables::OTEL_METRICS_EXPORTER);
//TODO "The SDK MAY accept a comma-separated list to enable setting multiple exporters"
if (count($exporters) !== 1) {
throw new InvalidArgumentException(sprintf('Configuration %s requires exactly 1 exporter', Variables::OTEL_METRICS_EXPORTER));
}
$exporterName = $exporters[0];
try {
$factory = FactoryRegistry::metricExporterFactory($exporterName);
$exporter = $factory->create();
} catch (\Throwable $t) {
self::logWarning(sprintf('Unable to create %s meter provider: %s', $exporterName, $t->getMessage()));
$exporter = new NoopMetricExporter();
}
$reader = new ExportingReader($exporter, ClockFactory::getDefault());
$resource = ResourceInfoFactory::defaultResource();
$exemplarFilter = $this->createExemplarFilter(Configuration::getEnum(Variables::OTEL_METRICS_EXEMPLAR_FILTER));

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\SDK\Metrics\MetricExporter;
use OpenTelemetry\SDK\Metrics\MetricExporterFactoryInterface;
use OpenTelemetry\SDK\Metrics\MetricExporterInterface;
class InMemoryExporterFactory implements MetricExporterFactoryInterface
{
public function create(): MetricExporterInterface
{
return new InMemoryExporter();
}
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\SDK\Metrics\MetricExporter;
use OpenTelemetry\SDK\Metrics\MetricExporterFactoryInterface;
use OpenTelemetry\SDK\Metrics\MetricExporterInterface;
class NoopMetricExporterFactory implements MetricExporterFactoryInterface
{
public function create(): MetricExporterInterface
{
return new NoopMetricExporter();
}
}

View File

@ -0,0 +1,6 @@
<?php
declare(strict_types=1);
\OpenTelemetry\SDK\FactoryRegistry::registerMetricExporterFactory('memory', \OpenTelemetry\SDK\Metrics\MetricExporter\InMemoryExporterFactory::class);
\OpenTelemetry\SDK\FactoryRegistry::registerMetricExporterFactory('none', \OpenTelemetry\SDK\Metrics\MetricExporter\NoopMetricExporterFactory::class);

View File

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\SDK\Metrics;
interface MetricExporterFactoryInterface
{
public function create(): MetricExporterInterface;
}

View File

@ -33,13 +33,13 @@ class SdkAutoloader
return false;
}
Globals::registerInitializer(function (Configurator $configurator) {
$exporter = (new ExporterFactory())->fromEnvironment();
$exporter = (new ExporterFactory())->create();
$propagator = (new PropagatorFactory())->create();
$meterProvider = (new MeterProviderFactory())->create();
$spanProcessor = (new SpanProcessorFactory())->fromEnvironment($exporter, $meterProvider);
$spanProcessor = (new SpanProcessorFactory())->create($exporter, $meterProvider);
$tracerProvider = (new TracerProviderBuilder())
->addSpanProcessor($spanProcessor)
->setSampler((new SamplerFactory())->fromEnvironment())
->setSampler((new SamplerFactory())->create())
->build();
ShutdownHandler::register([$tracerProvider, 'shutdown']);

View File

@ -27,8 +27,6 @@ trait SpanExporterTrait
return true;
}
abstract public static function fromConnectionString(string $endpointUrl, string $name, string $args);
/**
* @param iterable<SpanDataInterface> $batch
* @return FutureInterface<bool>

View File

@ -6,144 +6,28 @@ 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;
use OpenTelemetry\SDK\Common\Configuration\Variables;
use OpenTelemetry\SDK\FactoryRegistry;
use RuntimeException;
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
* @throws RuntimeException
*/
public function create(): ?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);
$exporters = Configuration::getList(Variables::OTEL_TRACES_EXPORTER);
//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));
throw new InvalidArgumentException(sprintf('Configuration %s requires exactly 1 exporter', Variables::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));
if ($exporter === 'none') {
return null;
}
}
$factory = FactoryRegistry::spanExporterFactory($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);
return $factory->create();
}
}

View File

@ -17,7 +17,7 @@ class SamplerFactory
{
private const TRACEIDRATIO_PREFIX = 'traceidratio';
public function fromEnvironment(): SamplerInterface
public function create(): SamplerInterface
{
$name = Configuration::getString(Env::OTEL_TRACES_SAMPLER);

View File

@ -4,42 +4,54 @@ declare(strict_types=1);
namespace OpenTelemetry\SDK\Trace\SpanExporter;
use OpenTelemetry\SDK\Trace\Behavior\SpanExporterTrait;
use JsonException;
use OpenTelemetry\SDK\Behavior\LogsMessagesTrait;
use OpenTelemetry\SDK\Common\Export\TransportInterface;
use OpenTelemetry\SDK\Common\Future\CancellationInterface;
use OpenTelemetry\SDK\Common\Future\FutureInterface;
use OpenTelemetry\SDK\Trace\Behavior\UsesSpanConverterTrait;
use OpenTelemetry\SDK\Trace\SpanConverterInterface;
use OpenTelemetry\SDK\Trace\SpanExporterInterface;
use Throwable;
class ConsoleSpanExporter implements SpanExporterInterface
{
use SpanExporterTrait;
use UsesSpanConverterTrait;
use LogsMessagesTrait;
public function __construct(?SpanConverterInterface $converter = null)
private TransportInterface $transport;
public function __construct(TransportInterface $transport, ?SpanConverterInterface $converter = null)
{
$this->transport = $transport;
$this->setSpanConverter($converter ?? new FriendlySpanConverter());
}
/** @inheritDoc */
public function doExport(iterable $spans): bool
public function export(iterable $batch, ?CancellationInterface $cancellation = null): FutureInterface
{
try {
foreach ($spans as $span) {
print(json_encode(
$payload = '';
foreach ($batch as $span) {
try {
$payload .= json_encode(
$this->getSpanConverter()->convert([$span]),
JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT
) . PHP_EOL
);
) . PHP_EOL;
} catch (JsonException $e) {
self::logWarning('Error converting span: ' . $e->getMessage());
}
} catch (Throwable $t) {
return false;
}
return true;
return $this->transport->send($payload)
->map(fn () => true)
->catch(fn () => false);
}
public static function fromConnectionString(string $endpointUrl = null, string $name = null, $args = null)
public function shutdown(?CancellationInterface $cancellation = null): bool
{
return new self();
return $this->transport->shutdown();
}
public function forceFlush(?CancellationInterface $cancellation = null): bool
{
return $this->transport->forceFlush();
}
}

View File

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\SDK\Trace\SpanExporter;
use OpenTelemetry\SDK\FactoryRegistry;
use OpenTelemetry\SDK\Trace\SpanExporterInterface;
class ConsoleSpanExporterFactory implements SpanExporterFactoryInterface
{
public function create(): SpanExporterInterface
{
$transport = FactoryRegistry::transportFactory('stream')->create('php://stdout', 'application/json');
return new ConsoleSpanExporter($transport);
}
}

View File

@ -19,11 +19,6 @@ class InMemoryExporter implements SpanExporterInterface
$this->storage = $storage ?? new ArrayObject();
}
public static function fromConnectionString(string $endpointUrl = null, string $name = null, $args = null)
{
return new self();
}
protected function doExport(iterable $spans): bool
{
foreach ($spans as $span) {

View File

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\SDK\Trace\SpanExporter;
use OpenTelemetry\SDK\Trace\SpanExporterInterface;
class InMemorySpanExporterFactory implements SpanExporterFactoryInterface
{
public function create(): SpanExporterInterface
{
return new InMemoryExporter();
}
}

View File

@ -13,7 +13,6 @@ use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Psr\Log\NullLogger;
use RuntimeException;
class LoggerDecorator implements SpanExporterInterface, LoggerAwareInterface
{
@ -31,13 +30,6 @@ class LoggerDecorator implements SpanExporterInterface, LoggerAwareInterface
$this->setSpanConverter($converter ?? new FriendlySpanConverter());
}
public static function fromConnectionString(string $endpointUrl, string $name, string $args): void
{
throw new RuntimeException(
sprintf('%s cannot be instantiated via %s', __CLASS__, __METHOD__)
);
}
protected function beforeExport(iterable $spans): iterable
{
return $spans;

View File

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace OpenTelemetry\SDK\Trace\SpanExporter;
use OpenTelemetry\SDK\Logs\SimplePsrFileLogger;
use OpenTelemetry\SDK\Trace\Behavior\LoggerAwareTrait;
use OpenTelemetry\SDK\Trace\Behavior\SpanExporterTrait;
use OpenTelemetry\SDK\Trace\Behavior\UsesSpanConverterTrait;
@ -24,7 +23,6 @@ class LoggerExporter implements SpanExporterInterface, LoggerAwareInterface
public const GRANULARITY_AGGREGATE = 1;
public const GRANULARITY_SPAN = 2;
public const DEFAULT_LOG_LEVEL = LogLevel::DEBUG;
private string $serviceName;
private int $granularity = self::GRANULARITY_AGGREGATE;
@ -62,20 +60,6 @@ class LoggerExporter implements SpanExporterInterface, LoggerAwareInterface
return true;
}
/**
* @param string $endpointUrl
* @param string $name
* @param string|null $args
* @return LoggerExporter
*/
public static function fromConnectionString(string $endpointUrl, string $name, string $args = null): self
{
return new self(
$name,
new SimplePsrFileLogger($endpointUrl)
);
}
/**
* @param string $serviceName
*/

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\SDK\Trace\SpanExporter;
use OpenTelemetry\SDK\Trace\SpanExporterInterface;
interface SpanExporterFactoryInterface
{
public function create(): SpanExporterInterface;
}

View File

@ -0,0 +1,7 @@
<?php
declare(strict_types=1);
\OpenTelemetry\SDK\FactoryRegistry::registerSpanExporterFactory('console', \OpenTelemetry\SDK\Trace\SpanExporter\ConsoleSpanExporterFactory::class);
\OpenTelemetry\SDK\FactoryRegistry::registerSpanExporterFactory('memory', \OpenTelemetry\SDK\Trace\SpanExporter\InMemorySpanExporterFactory::class);
\OpenTelemetry\SDK\FactoryRegistry::registerTransportFactory('stream', \OpenTelemetry\SDK\Common\Export\Stream\StreamTransportFactory::class);

View File

@ -17,7 +17,7 @@ use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
class SpanProcessorFactory
{
public function fromEnvironment(?SpanExporterInterface $exporter = null, ?MeterProviderInterface $meterProvider = null): SpanProcessorInterface
public function create(?SpanExporterInterface $exporter = null, ?MeterProviderInterface $meterProvider = null): SpanProcessorInterface
{
if ($exporter === null) {
return new NoopSpanProcessor();

View File

@ -16,12 +16,11 @@ final class TracerProviderFactory
private SpanProcessorFactory $spanProcessorFactory;
public function __construct(
string $name,
?ExporterFactory $exporterFactory = null,
?SamplerFactory $samplerFactory = null,
?SpanProcessorFactory $spanProcessorFactory = null
) {
$this->exporterFactory = $exporterFactory ?: new ExporterFactory($name);
$this->exporterFactory = $exporterFactory ?: new ExporterFactory();
$this->samplerFactory = $samplerFactory ?: new SamplerFactory();
$this->spanProcessorFactory = $spanProcessorFactory ?: new SpanProcessorFactory();
}
@ -33,21 +32,21 @@ final class TracerProviderFactory
}
try {
$exporter = $this->exporterFactory->fromEnvironment();
$exporter = $this->exporterFactory->create();
} catch (\Throwable $t) {
self::logWarning('Unable to create exporter', ['exception' => $t]);
$exporter = null;
}
try {
$sampler = $this->samplerFactory->fromEnvironment();
$sampler = $this->samplerFactory->create();
} catch (\Throwable $t) {
self::logWarning('Unable to create sampler', ['exception' => $t]);
$sampler = null;
}
try {
$spanProcessor = $this->spanProcessorFactory->fromEnvironment($exporter);
$spanProcessor = $this->spanProcessorFactory->create($exporter);
} catch (\Throwable $t) {
self::logWarning('Unable to create span processor', ['exception' => $t]);
$spanProcessor = null;

View File

@ -31,6 +31,8 @@
},
"files": [
"Common/Util/functions.php",
"Metrics/MetricExporter/_register.php",
"Trace/SpanExporter/_register.php",
"_autoload.php"
]
},

View File

@ -10,6 +10,7 @@ use OpenTelemetry\API\Trace\NonRecordingSpan;
use OpenTelemetry\API\Trace\SpanContext;
use OpenTelemetry\API\Trace\TraceState;
use OpenTelemetry\Context\Context;
use OpenTelemetry\SDK\Common\Configuration\Variables;
use OpenTelemetry\SDK\Trace\Sampler\AlwaysOffSampler;
use OpenTelemetry\SDK\Trace\SamplerInterface;
use OpenTelemetry\SDK\Trace\SamplingResult;
@ -110,8 +111,8 @@ class TracerTest extends TestCase
public function test_factory_returns_noop_tracer_when_sdk_disabled(): void
{
self::setEnvironmentVariable('OTEL_SDK_DISABLED', 'true');
$tracerProvider = (new TracerProviderFactory('test'))->create();
self::setEnvironmentVariable(Variables::OTEL_SDK_DISABLED, 'true');
$tracerProvider = (new TracerProviderFactory())->create();
$tracer = $tracerProvider->getTracer('foo');
$this->assertInstanceOf(API\NoopTracer::class, $tracer);
}

View File

@ -1,140 +0,0 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Tests\Unit\Contrib;
use InvalidArgumentException;
use Nyholm\Psr7\Response;
use OpenTelemetry\SDK\Trace\SpanExporterInterface;
use OpenTelemetry\Tests\Unit\SDK\Trace\SpanExporter\AbstractExporterTest;
use OpenTelemetry\Tests\Unit\SDK\Util\SpanData;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\NetworkExceptionInterface;
use Psr\Http\Client\RequestExceptionInterface;
abstract class AbstractHttpExporterTest extends AbstractExporterTest
{
use UsesHttpClientTrait;
protected const EXPORTER_DSN = 'https://localhost:1234/foo';
/**
* Must be implemented by concrete TestCases
*
* @param string $dsn
* @return SpanExporterInterface
*/
abstract public function createExporterWithDsn(string $dsn): SpanExporterInterface;
/**
* Must be implemented by concrete TestCases
*
* @return string
*/
abstract public function getExporterClass(): string;
public function createExporter(): SpanExporterInterface
{
return $this->createExporterWithDsn(static::EXPORTER_DSN);
}
/**
* @dataProvider exporterResponseStatusDataProvider
* @psalm-suppress PossiblyUndefinedMethod
*/
public function test_exporter_response_status($responseStatus, $expected): void
{
$this->getClientInterfaceMock()->method('sendRequest')
->willReturn(new Response($responseStatus));
$this->assertEquals(
$expected,
$this->createExporter()->export([
$this->createMock(SpanData::class),
])->await(),
);
}
public function exporterResponseStatusDataProvider(): array
{
return [
'ok' => [200, true],
'not found' => [404, false],
'not authorized' => [401, false],
'bad request' => [402, false],
'too many requests' => [429, false],
'server error' => [500, false],
'timeout' => [503, false],
'bad gateway' => [502, false],
];
}
/**
* @dataProvider invalidDsnDataProvider
*/
public function test_throws_exception_if_invalid_dsn_is_passed($invalidDsn): void
{
$this->expectException(InvalidArgumentException::class);
$this->createExporterWithDsn($invalidDsn);
}
public function invalidDsnDataProvider(): array
{
return [
'missing scheme' => ['host:123/path'],
'missing host' => ['scheme://'],
'invalid port' => ['scheme://host:port/path'],
'invalid scheme' => ['1234://host:port/path'],
'invalid host' => ['scheme:///end:1234/path'],
'unimplemented path' => ['scheme:///host:1234/api/v1/spans'],
];
}
/**
* @dataProvider clientExceptionDataProvider
* @psalm-suppress PossiblyUndefinedMethod
*/
public function test_client_exception_decides_return_code($exception, $expected): void
{
$client = $this->getClientInterfaceMock();
$client->method('sendRequest')
->willThrowException($exception);
$this->assertEquals(
$expected,
$this->createExporter()->export([
$this->createMock(SpanData::class),
])->await(),
);
}
public function clientExceptionDataProvider(): array
{
return [
'client' => [
$this->createMock(ClientExceptionInterface::class),
false,
],
'network' => [
$this->createMock(NetworkExceptionInterface::class),
false,
],
'request' => [
$this->createMock(RequestExceptionInterface::class),
false,
],
];
}
public function test_from_connection_string(): void
{
$exporterClass = static::getExporterClass();
$this->assertNotSame(
call_user_func([$exporterClass, 'fromConnectionString'], self::EXPORTER_DSN, $exporterClass, 'foo'),
call_user_func([$exporterClass, 'fromConnectionString'], self::EXPORTER_DSN, $exporterClass, 'foo')
);
}
}

View File

@ -18,10 +18,7 @@ class AgentExporterTest extends TestCase
{
public function test_happy_path()
{
$exporter = AgentExporter::fromConnectionString(
'http://127.0.0.1:80',
'someServiceName',
);
$exporter = new AgentExporter('someServiceName', 'http://127.0.0.1:80');
$status = $exporter->export([new SpanData()])->await();

View File

@ -5,29 +5,21 @@ declare(strict_types=1);
namespace OpenTelemetry\Tests\Unit\Contrib\Jaeger;
use OpenTelemetry\Contrib\Jaeger\Exporter;
use OpenTelemetry\Tests\Unit\Contrib\AbstractHttpExporterTest;
use OpenTelemetry\Tests\Unit\Contrib\UsesHttpClientTrait;
use OpenTelemetry\SDK\Common\Export\TransportInterface;
use OpenTelemetry\Tests\Unit\SDK\Trace\SpanExporter\AbstractExporterTest;
/**
* @covers OpenTelemetry\Contrib\Jaeger\Exporter
*/
class JaegerExporterTest extends AbstractHttpExporterTest
class JaegerExporterTest extends AbstractExporterTest
{
use UsesHttpClientTrait;
private const EXPORTER_NAME = 'test.jaeger';
/**
* @psalm-suppress PossiblyInvalidArgument
*/
public function createExporterWithDsn(string $dsn): Exporter
public function createExporterWithTransport(TransportInterface $transport): Exporter
{
return new Exporter(
self::EXPORTER_NAME,
$dsn,
$this->getClientInterfaceMock(),
$this->getRequestFactoryInterfaceMock(),
$this->getStreamFactoryInterfaceMock()
$transport
);
}

View File

@ -5,28 +5,23 @@ declare(strict_types=1);
namespace OpenTelemetry\Tests\Unit\Contrib\Newrelic;
use OpenTelemetry\Contrib\Newrelic\Exporter;
use OpenTelemetry\Tests\Unit\Contrib\AbstractHttpExporterTest;
use OpenTelemetry\SDK\Common\Export\TransportInterface;
use OpenTelemetry\Tests\Unit\SDK\Trace\SpanExporter\AbstractExporterTest;
/**
* @covers OpenTelemetry\Contrib\Newrelic\Exporter
*/
class NewrelicExporterTest extends AbstractHttpExporterTest
class NewrelicExporterTest extends AbstractExporterTest
{
protected const EXPORTER_NAME = 'test.newrelic';
protected const LICENSE_KEY = 'abc123';
/**
* @psalm-suppress PossiblyInvalidArgument
*/
public function createExporterWithDsn(string $dsn): Exporter
public function createExporterWithTransport(TransportInterface $transport): Exporter
{
return new Exporter(
self::EXPORTER_NAME,
$dsn,
self::LICENSE_KEY,
$this->getClientInterfaceMock(),
$this->getRequestFactoryInterfaceMock(),
$this->getStreamFactoryInterfaceMock()
$transport,
'http://endpoint.url'
);
}

View File

@ -39,13 +39,13 @@ class SpanExporterFactoryTest extends TestCase
$this->expectException(\UnexpectedValueException::class);
$this->setEnvironmentVariable(Variables::OTEL_EXPORTER_OTLP_PROTOCOL, 'foo');
$factory = new SpanExporterFactory();
$factory->fromEnvironment();
$factory->create();
}
/**
* @dataProvider fromEnvironmentProvider
* @dataProvider configProvider
*/
public function test_create_from_environment(array $env, string $endpoint, string $protocol, string $compression, array $headerKeys = []): void
public function test_create(array $env, string $endpoint, string $protocol, string $compression, array $headerKeys = []): void
{
foreach ($env as $k => $v) {
$this->setEnvironmentVariable($k, $v);
@ -69,10 +69,10 @@ class SpanExporterFactoryTest extends TestCase
// @phpstan-ignore-next-line
$this->transport->method('contentType')->willReturn($protocol);
$factory->fromEnvironment();
$factory->create();
}
public function fromEnvironmentProvider(): array
public function configProvider(): array
{
$defaultHeaderKeys = ['User-Agent'];

View File

@ -6,26 +6,24 @@ namespace OpenTelemetry\Tests\Unit\Contrib\Zipkin;
use OpenTelemetry\Contrib\Zipkin\Exporter;
use OpenTelemetry\Tests\Unit\Contrib\AbstractHttpExporterTest;
use OpenTelemetry\SDK\Common\Export\TransportInterface;
use OpenTelemetry\Tests\Unit\SDK\Trace\SpanExporter\AbstractExporterTest;
/**
* @covers OpenTelemetry\Contrib\Zipkin\Exporter
*/
class ZipkinExporterTest extends AbstractHttpExporterTest
class ZipkinExporterTest extends AbstractExporterTest
{
protected const EXPORTER_NAME = 'test.zipkin';
/**
* @psalm-suppress PossiblyInvalidArgument
*/
public function createExporterWithDsn(string $dsn): Exporter
public function createExporterWithTransport(TransportInterface $transport): Exporter
{
return new Exporter(
self::EXPORTER_NAME,
$dsn,
$this->getClientInterfaceMock(),
$this->getRequestFactoryInterfaceMock(),
$this->getStreamFactoryInterfaceMock()
$transport
);
}

View File

@ -6,12 +6,13 @@ namespace OpenTelemetry\Tests\Unit\Contrib\ZipkinToNewrelic;
use OpenTelemetry\Contrib\ZipkinToNewrelic\Exporter;
use OpenTelemetry\Tests\Unit\Contrib\AbstractHttpExporterTest;
use OpenTelemetry\SDK\Common\Export\TransportInterface;
use OpenTelemetry\Tests\Unit\SDK\Trace\SpanExporter\AbstractExporterTest;
/**
* @covers OpenTelemetry\Contrib\ZipkinToNewrelic\Exporter
*/
class ZipkinToNewrelicExporterTest extends AbstractHttpExporterTest
class ZipkinToNewrelicExporterTest extends AbstractExporterTest
{
protected const EXPORTER_NAME = 'test.zipkinToNR';
protected const LICENSE_KEY = 'abc123';
@ -19,15 +20,11 @@ class ZipkinToNewrelicExporterTest extends AbstractHttpExporterTest
/**
* @psalm-suppress PossiblyInvalidArgument
*/
public function createExporterWithDsn(string $dsn): Exporter
public function createExporterWithTransport(TransportInterface $transport): Exporter
{
return new Exporter(
self::EXPORTER_NAME,
$dsn,
self::LICENSE_KEY,
$this->getClientInterfaceMock(),
$this->getRequestFactoryInterfaceMock(),
$this->getStreamFactoryInterfaceMock()
$transport
);
}

View File

@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Example\Unit\SDK;
use OpenTelemetry\SDK\Common\Export\TransportFactoryInterface;
use OpenTelemetry\SDK\FactoryRegistry;
use OpenTelemetry\SDK\Metrics\MetricExporterFactoryInterface;
use OpenTelemetry\SDK\Trace\SpanExporter\SpanExporterFactoryInterface;
use PHPUnit\Framework\TestCase;
/**
* @covers \OpenTelemetry\SDK\FactoryRegistry
*/
class FactoryRegistryTest extends TestCase
{
/**
* @dataProvider transportProtocolsProvider
*/
public function test_default_transport_factories(string $name): void
{
$factory = FactoryRegistry::transportFactory($name);
$this->assertInstanceOf(TransportFactoryInterface::class, $factory);
}
public function transportProtocolsProvider(): array
{
return [
['grpc'],
['http/protobuf'],
['http/json'],
['http/ndjson'],
['http'],
['http/foo'],
];
}
/**
* @dataProvider spanExporterProvider
*/
public function test_default_span_exporter_factories(string $name): void
{
$factory = FactoryRegistry::spanExporterFactory($name);
$this->assertInstanceOf(SpanExporterFactoryInterface::class, $factory);
}
public function spanExporterProvider(): array
{
return [
['otlp'],
['zipkin'],
['newrelic'],
['console'],
['memory'],
];
}
/**
* @dataProvider metricExporterProvider
*/
public function test_default_metric_exporter_factories(string $name): void
{
$factory = FactoryRegistry::metricExporterFactory($name);
$this->assertInstanceOf(MetricExporterFactoryInterface::class, $factory);
}
public function metricExporterProvider(): array
{
return [
['otlp'],
['memory'],
['none'],
];
}
/**
* @dataProvider invalidFactoryProvider
*/
public function test_register_invalid_transport_factory($factory): void
{
$this->expectWarning();
FactoryRegistry::registerTransportFactory('http', $factory, true);
}
/**
* @dataProvider invalidFactoryProvider
*/
public function test_register_invalid_span_exporter_factory($factory): void
{
$this->expectWarning();
FactoryRegistry::registerSpanExporterFactory('foo', $factory, true);
}
/**
* @dataProvider invalidFactoryProvider
*/
public function test_register_invalid_metric_exporter_factory($factory): void
{
$this->expectWarning();
FactoryRegistry::registerMetricExporterFactory('foo', $factory, true);
}
public function invalidFactoryProvider(): array
{
return [
[new \stdClass()],
['\stdClass'],
];
}
}

View File

@ -11,7 +11,6 @@ use Http\Discovery\Strategy\MockClientStrategy;
use OpenTelemetry\Contrib;
use OpenTelemetry\SDK\Trace\ExporterFactory;
use OpenTelemetry\SDK\Trace\SpanExporter\ConsoleSpanExporter;
use OpenTelemetry\SDK\Trace\SpanExporter\InMemoryExporter;
use PHPUnit\Framework\TestCase;
/**
@ -31,56 +30,14 @@ class ExporterFactoryTest extends TestCase
HttpClientDiscovery::prependStrategy(MockClientStrategy::class);
}
/**
* @dataProvider endpointProvider
*/
public function test_exporter_from_connection_string($name, $input, $expectedClass): void
{
$factory = new ExporterFactory($name);
$exporter = $factory->fromConnectionString($input);
$this->assertInstanceOf($expectedClass, $exporter);
}
public function endpointProvider(): array
{
return [
'zipkin' => ['test.zipkin', 'zipkin+http://zipkin:9411/api/v2/spans', Contrib\Zipkin\Exporter::class],
'jaeger' => ['test.jaeger', 'jaeger+http://jaeger:9412/api/v2/spans', Contrib\Jaeger\Exporter::class],
'newrelic' => ['rest.newrelic', 'newrelic+https://trace-api.newrelic.com/trace/v1?licenseKey=abc23423423', Contrib\Newrelic\Exporter::class],
'zipkintonewrelic' => ['test.zipkintonewrelic', 'zipkintonewrelic+https://trace-api.newrelic.com/trace/v1?licenseKey=abc23423423', Contrib\ZipkinToNewrelic\Exporter::class],
'console' => ['test.console', 'console', ConsoleSpanExporter::class],
'memory' => ['test.memory', 'memory', InMemoryExporter::class],
];
}
/**
* @dataProvider invalidConnectionStringProvider
*/
public function test_invalid_connection_string(string $name, string $input): void
{
$this->expectException(Exception::class);
$factory = new ExporterFactory($name);
$factory->fromConnectionString($input);
}
public function invalidConnectionStringProvider(): array
{
return [
'zipkin without +' => ['test.zipkin', 'zipkinhttp://zipkin:9411/api/v2/spans'],
'zapkin' => ['zipkin.test', 'zapkin+http://zipkin:9411/api/v2/spans'],
'otlp' => ['test.otlp', 'otlp'],
'test' => ['test', 'test+http://test:1345'],
];
}
/**
* @group trace-compliance
*/
public function test_accepts_none_exporter_env_var(): void
{
$this->setEnvironmentVariable('OTEL_TRACES_EXPORTER', 'none');
$factory = new ExporterFactory('test.fromEnv');
$this->assertNull($factory->fromEnvironment());
$factory = new ExporterFactory();
$this->assertNull($factory->create());
}
/**
@ -94,8 +51,8 @@ class ExporterFactoryTest extends TestCase
foreach ($env as $k => $v) {
$this->setEnvironmentVariable($k, $v);
}
$factory = new ExporterFactory('test.fromEnv');
$this->assertInstanceOf($expected, $factory->fromEnvironment());
$factory = new ExporterFactory();
$this->assertInstanceOf($expected, $factory->create());
}
public function envProvider(): array
@ -142,16 +99,15 @@ class ExporterFactoryTest extends TestCase
foreach ($env as $k => $v) {
$this->setEnvironmentVariable($k, $v);
}
$factory = new ExporterFactory('test');
$factory = new ExporterFactory();
$this->expectException(Exception::class);
$factory->fromEnvironment();
$factory->create();
}
public function invalidEnvProvider(): array
{
return [
'jaeger' => ['jaeger'],
'zipkin' => ['zipkin'],
'newrelic' => ['newrelic'],
'zipkintonewrelic' => ['zipkintonewrelic'],
'otlp+invalid protocol' => [

View File

@ -22,7 +22,7 @@ class SamplerFactoryTest extends TestCase
}
/**
* @covers ::fromEnvironment
* @covers ::create
* @dataProvider samplerProvider
*/
public function test_create_sampler_from_environment(string $samplerName, string $expected, string $arg = null): void
@ -30,7 +30,7 @@ class SamplerFactoryTest extends TestCase
$this->setEnvironmentVariable('OTEL_TRACES_SAMPLER', $samplerName);
$this->setEnvironmentVariable('OTEL_TRACES_SAMPLER_ARG', $arg);
$factory = new SamplerFactory();
$sampler = $factory->fromEnvironment();
$sampler = $factory->create();
$this->assertStringContainsString($expected, $sampler->getDescription());
}
@ -47,7 +47,7 @@ class SamplerFactoryTest extends TestCase
];
}
/**
* @covers ::fromEnvironment
* @covers ::create
* @dataProvider invalidSamplerProvider
*/
public function test_throws_exception_for_invalid_or_unsupported(?string $sampler, string $arg = null): void
@ -56,7 +56,7 @@ class SamplerFactoryTest extends TestCase
$this->setEnvironmentVariable('OTEL_TRACES_SAMPLER_ARG', $arg);
$factory = new SamplerFactory();
$this->expectException(Exception::class);
$factory->fromEnvironment();
$factory->create();
}
public function invalidSamplerProvider(): array

View File

@ -4,21 +4,53 @@ declare(strict_types=1);
namespace OpenTelemetry\Tests\Unit\SDK\Trace\SpanExporter;
use Mockery;
use Mockery\Adapter\Phpunit\MockeryTestCase;
use OpenTelemetry\SDK\Common\Export\TransportInterface;
use OpenTelemetry\SDK\Common\Future\CompletedFuture;
use OpenTelemetry\SDK\Common\Future\ErrorFuture;
use OpenTelemetry\SDK\Common\Future\FutureInterface;
use OpenTelemetry\SDK\Trace\SpanExporterInterface;
use OpenTelemetry\Tests\Unit\SDK\Util\SpanData;
use PHPUnit\Framework\TestCase;
abstract class AbstractExporterTest extends TestCase
/**
* @psalm-suppress UndefinedInterfaceMethod
*/
abstract class AbstractExporterTest extends MockeryTestCase
{
protected TransportInterface $transport;
protected FutureInterface $future;
public function setUp(): void
{
$this->future = Mockery::mock(FutureInterface::class);
$this->future->allows([
'map' => $this->future,
'catch' => $this->future,
]);
$this->transport = Mockery::mock(TransportInterface::class)->makePartial();
$this->transport->allows(['send' => $this->future]);
}
/**
* Must be implemented by concrete TestCases
*
* @return SpanExporterInterface
*/
abstract public function createExporter(): SpanExporterInterface;
abstract public function createExporterWithTransport(TransportInterface $transport): SpanExporterInterface;
/**
* Must be implemented by concrete TestCases
*/
abstract public function getExporterClass(): string;
protected function createExporter(): SpanExporterInterface
{
return $this->createExporterWithTransport($this->transport);
}
public function test_shutdown(): void
{
$this->transport->shouldReceive('shutdown')->andReturn(true);
$this->assertTrue(
$this->createExporter()->shutdown()
);
@ -26,18 +58,37 @@ abstract class AbstractExporterTest extends TestCase
public function test_force_flush(): void
{
$this->transport->shouldReceive('forceFlush')->andReturn(true);
$this->assertTrue(
$this->createExporter()->forceFlush()
);
}
public function test_fails_if_not_test_running(): void
/**
* @dataProvider futureProvider
*/
public function test_export(FutureInterface $future, bool $expected): void
{
$exporter = $this->createExporter();
$transport = Mockery::mock(TransportInterface::class);
$exporter = $this->createExporterWithTransport($transport);
$span = $this->createMock(SpanData::class);
$exporter->shutdown();
$transport->shouldReceive('send')->andReturn($future);
$this->assertFalse($exporter->export([$span])->await());
$this->assertSame($expected, $exporter->export([$span])->await());
}
public function futureProvider(): array
{
return [
'error future' => [
new ErrorFuture(new \Exception('foo')),
false,
],
'completed future' => [
new CompletedFuture([]),
true,
],
];
}
}

View File

@ -4,18 +4,21 @@ declare(strict_types=1);
namespace OpenTelemetry\Tests\Unit\SDK\Trace\SpanExporter;
use OpenTelemetry\SDK\Common\Export\TransportInterface;
use OpenTelemetry\SDK\Trace\SpanConverterInterface;
use OpenTelemetry\SDK\Trace\SpanDataInterface;
use OpenTelemetry\SDK\Trace\SpanExporter\ConsoleSpanExporter;
use OpenTelemetry\SDK\Trace\SpanExporterInterface;
/**
* @covers \OpenTelemetry\SDK\Trace\SpanExporter\ConsoleSpanExporter
* @psalm-suppress UndefinedInterfaceMethod
*/
class ConsoleSpanExporterTest extends AbstractExporterTest
{
public function createExporter(): ConsoleSpanExporter
{
return new ConsoleSpanExporter();
return new ConsoleSpanExporter($this->transport);
}
private const TEST_DATA = [
@ -50,44 +53,6 @@ class ConsoleSpanExporterTest extends AbstractExporterTest
],],
];
public function test_export_success(): void
{
$converter = $this->createMock(SpanConverterInterface::class);
$converter->expects($this->once())
->method('convert')
->willReturn(self::TEST_DATA);
ob_start();
$this->assertTrue(
(new ConsoleSpanExporter($converter))->export([
$this->createMock(SpanDataInterface::class),
])->await(),
);
ob_end_clean();
}
public function test_export_failed(): void
{
$resource = fopen('php://stdin', 'rb');
$converter = $this->createMock(SpanConverterInterface::class);
$converter->expects($this->once())
->method('convert')
->willReturn([$resource]);
ob_start();
$this->assertFalse(
(new ConsoleSpanExporter($converter))->export([
$this->createMock(SpanDataInterface::class),
])->await(),
);
ob_end_clean();
fclose($resource);
}
public function test_export_output(): void
{
try {
@ -95,24 +60,29 @@ class ConsoleSpanExporterTest extends AbstractExporterTest
} catch (\Throwable $t) {
$this->fail($t->getMessage());
}
$this->future->allows([
'await' => true,
]);
$converter = $this->createMock(SpanConverterInterface::class);
$converter->expects($this->once())
->method('convert')
->willReturn(self::TEST_DATA);
$this->expectOutputString($expected);
(new ConsoleSpanExporter($converter))->export([
(new ConsoleSpanExporter($this->transport, $converter))->export([
$this->createMock(SpanDataInterface::class),
])->await();
$this->transport->shouldHaveReceived('send')->with($expected);
}
public function test_from_connection_string(): void
public function createExporterWithTransport(TransportInterface $transport): SpanExporterInterface
{
$this->assertInstanceOf(
ConsoleSpanExporter::class,
ConsoleSpanExporter::fromConnectionString()
);
return new ConsoleSpanExporter($transport);
}
public function getExporterClass(): string
{
return ConsoleSpanExporter::class;
}
}

View File

@ -30,14 +30,6 @@ class InMemoryExporterTest extends TestCase
);
}
public function test_from_connection_string(): void
{
$this->assertInstanceOf(
InMemoryExporter::class,
InMemoryExporter::fromConnectionString()
);
}
public function test_get_storage(): void
{
$storage = new ArrayObject();

View File

@ -9,7 +9,6 @@ use OpenTelemetry\SDK\Trace\SpanExporter\LoggerDecorator;
use OpenTelemetry\SDK\Trace\SpanExporterInterface;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LogLevel;
use RuntimeException;
/**
* @covers \OpenTelemetry\SDK\Trace\SpanExporter\LoggerDecorator
@ -21,13 +20,6 @@ class LoggerDecoratorTest extends AbstractLoggerAwareTest
*/
private ?SpanExporterInterface $decorated;
public function test_from_connection_string(): void
{
$this->expectException(RuntimeException::class);
LoggerDecorator::fromConnectionString('foo', 'bar', 'baz');
}
/**
* @psalm-suppress PossiblyUndefinedMethod
*/

View File

@ -6,36 +6,23 @@ namespace OpenTelemetry\Tests\Unit\SDK\Trace\SpanExporter;
use Exception;
use OpenTelemetry\SDK\Trace\SpanExporter\LoggerExporter;
use PHPUnit\Framework\TestCase;
/**
* @covers \OpenTelemetry\SDK\Trace\SpanExporter\LoggerExporter
*/
class LoggerExporterTest extends AbstractExporterTest
class LoggerExporterTest extends TestCase
{
use LoggerAwareTestTrait;
private const SERVICE_NAME = 'LoggerExporterTest';
private const LOG_LEVEL = 'debug';
private const LOG_FILE = 'debug.log';
public function createExporter(): LoggerExporter
{
return new LoggerExporter(self::SERVICE_NAME);
}
public function test_from_connection_string(): void
{
/** @noinspection UnnecessaryAssertionInspection */
$this->assertInstanceOf(
LoggerExporter::class,
LoggerExporter::fromConnectionString(
self::LOG_FILE,
self::SERVICE_NAME,
self::LOG_LEVEL
)
);
}
/**
* @psalm-suppress PossiblyUndefinedMethod
* @psalm-suppress PossiblyInvalidArgument

View File

@ -33,7 +33,7 @@ class SpanProcessorFactoryTest extends TestCase
{
$this->setEnvironmentVariable('OTEL_PHP_TRACES_PROCESSOR', $processorName);
$factory = new SpanProcessorFactory();
$this->assertInstanceOf($expected, $factory->fromEnvironment($this->createMock(SpanExporterInterface::class)));
$this->assertInstanceOf($expected, $factory->create($this->createMock(SpanExporterInterface::class)));
}
public function processorProvider()
@ -50,7 +50,7 @@ class SpanProcessorFactoryTest extends TestCase
{
$factory = new SpanProcessorFactory();
$exporter = $this->createMock(SpanExporterInterface::class);
$this->assertInstanceOf(BatchSpanProcessor::class, $factory->fromEnvironment($exporter));
$this->assertInstanceOf(BatchSpanProcessor::class, $factory->create($exporter));
}
public function test_span_processor_factory_invalid_span_processor(): void
@ -59,6 +59,6 @@ class SpanProcessorFactoryTest extends TestCase
$factory = new SpanProcessorFactory();
$exporter = $this->createMock(SpanExporterInterface::class);
$this->expectException(InvalidArgumentException::class);
$factory->fromEnvironment($exporter);
$factory->create($exporter);
}
}

View File

@ -41,11 +41,11 @@ class TracerProviderFactoryTest extends TestCase
$samplerFactory = $this->createMock(SamplerFactory::class);
$spanProcessorFactory = $this->createMock(SpanProcessorFactory::class);
$exporterFactory->expects($this->once())->method('fromEnvironment');
$samplerFactory->expects($this->once())->method('fromEnvironment');
$spanProcessorFactory->expects($this->once())->method('fromEnvironment');
$exporterFactory->expects($this->once())->method('create');
$samplerFactory->expects($this->once())->method('create');
$spanProcessorFactory->expects($this->once())->method('create');
$factory = new TracerProviderFactory('test', $exporterFactory, $samplerFactory, $spanProcessorFactory);
$factory = new TracerProviderFactory($exporterFactory, $samplerFactory, $spanProcessorFactory);
$factory->create();
}
@ -56,24 +56,24 @@ class TracerProviderFactoryTest extends TestCase
$spanProcessorFactory = $this->createMock(SpanProcessorFactory::class);
$exporterFactory->expects($this->once())
->method('fromEnvironment')
->method('create')
->willThrowException(new \InvalidArgumentException('foo'));
$samplerFactory->expects($this->once())
->method('fromEnvironment')
->method('create')
->willThrowException(new \InvalidArgumentException('foo'));
$spanProcessorFactory->expects($this->once())
->method('fromEnvironment')
->method('create')
->willThrowException(new \InvalidArgumentException('foo'));
$this->logger->expects($this->atLeast(3))->method('log');
$factory = new TracerProviderFactory('test', $exporterFactory, $samplerFactory, $spanProcessorFactory);
$factory = new TracerProviderFactory($exporterFactory, $samplerFactory, $spanProcessorFactory);
$factory->create();
}
public function test_can_be_disabled(): void
{
$this->setEnvironmentVariable('OTEL_SDK_DISABLED', 'true');
$factory = new TracerProviderFactory('test');
$factory = new TracerProviderFactory();
$this->assertInstanceOf(NoopTracerProvider::class, $factory->create());
}
}