Skip to content
Open
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
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"src/SDK/Metrics/MetricExporter/_register.php",
"src/SDK/Propagation/_register.php",
"src/SDK/Trace/SpanExporter/_register.php",
"src/SDK/Trace/Sampler/_register.php",
"src/SDK/Common/Dev/Compatibility/_load.php",
"src/SDK/Common/Util/functions.php",
"src/SDK/_autoload.php"
Expand Down
13 changes: 13 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ includes:
parameters:
tmpDir: var/cache/phpstan
level: 5
reportUnmatchedIgnoredErrors: false
paths:
- ./src
- ./tests
Expand Down Expand Up @@ -62,3 +63,15 @@ parameters:
message: "#^Caught class .* not found\\.#"
paths:
- src/Config/SDK/Configuration/Environment/Adapter/
-
message: "#Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeParentInterface::.+\\(\\)#"
paths:
- src/Config/SDK
- tests/Integration/Config
- tests/Unit/Config
-
message: "#PHPDoc tag @extends contains generic type Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\.*NodeDefinition<.*> but class .* is not generic#"
paths:
- src/Config/SDK
- tests/Integration/Config
- tests/Unit/Config
35 changes: 35 additions & 0 deletions src/SDK/Registry.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use OpenTelemetry\SDK\Logs\LogRecordExporterFactoryInterface;
use OpenTelemetry\SDK\Metrics\MetricExporterFactoryInterface;
use OpenTelemetry\SDK\Resource\ResourceDetectorInterface;
use OpenTelemetry\SDK\Trace\Sampler\SamplerFactoryInterface;
use OpenTelemetry\SDK\Trace\SpanExporter\SpanExporterFactoryInterface;
use RuntimeException;
use TypeError;
Expand All @@ -28,6 +29,7 @@ class Registry
private static array $logRecordExporterFactories = [];
private static array $resourceDetectors = [];
private static array $responsePropagators = [];
private static array $samplerFactories = [];

/**
* @param TransportFactoryInterface|class-string<TransportFactoryInterface> $factory
Expand Down Expand Up @@ -134,6 +136,27 @@ public static function registerResponsePropagator(string $name, ResponsePropagat
self::$responsePropagators[$name] = $responsePropagator;
}

/**
* @param SamplerFactoryInterface|class-string<SamplerFactoryInterface> $factory
* @throws TypeError
*/
public static function registerSamplerFactory(string $sampler, SamplerFactoryInterface|string $factory, bool $clobber = false): void
{
if (!$clobber && array_key_exists($sampler, self::$samplerFactories)) {
return;
}
if (!is_subclass_of($factory, SamplerFactoryInterface::class)) {
throw new TypeError(
sprintf(
'Cannot register sampler factory: %s must exist and implement %s',
is_string($factory) ? $factory : $factory::class,
SamplerFactoryInterface::class
)
);
}
self::$samplerFactories[$sampler] = $factory;
}

public static function spanExporterFactory(string $exporter): SpanExporterFactoryInterface
{
if (!array_key_exists($exporter, self::$spanExporterFactories)) {
Expand Down Expand Up @@ -221,4 +244,16 @@ public static function resourceDetectors(): array
{
return array_values(self::$resourceDetectors);
}

public static function samplerFactory(string $sampler): SamplerFactoryInterface
{
if (!array_key_exists($sampler, self::$samplerFactories)) {
throw new RuntimeException('Sampler factory not registered for: ' . $sampler);
}
$class = self::$samplerFactories[$sampler];
$factory = (is_callable($class)) ? $class : new $class();
assert($factory instanceof SamplerFactoryInterface);

return $factory;
}
}
16 changes: 16 additions & 0 deletions src/SDK/Trace/Sampler/AlwaysOffSamplerFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\SDK\Trace\Sampler;

use OpenTelemetry\SDK\Trace\SamplerInterface;

class AlwaysOffSamplerFactory implements SamplerFactoryInterface
{
#[\Override]
public function create(): SamplerInterface
{
return new AlwaysOffSampler();
}
}
16 changes: 16 additions & 0 deletions src/SDK/Trace/Sampler/AlwaysOnSamplerFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\SDK\Trace\Sampler;

use OpenTelemetry\SDK\Trace\SamplerInterface;

class AlwaysOnSamplerFactory implements SamplerFactoryInterface
{
#[\Override]
public function create(): SamplerInterface
{
return new AlwaysOnSampler();
}
}
16 changes: 16 additions & 0 deletions src/SDK/Trace/Sampler/ParentBasedAlwaysOffSamplerFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\SDK\Trace\Sampler;

use OpenTelemetry\SDK\Trace\SamplerInterface;

class ParentBasedAlwaysOffSamplerFactory implements SamplerFactoryInterface
{
#[\Override]
public function create(): SamplerInterface
{
return new ParentBased(new AlwaysOffSampler());
}
}
16 changes: 16 additions & 0 deletions src/SDK/Trace/Sampler/ParentBasedAlwaysOnSamplerFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\SDK\Trace\Sampler;

use OpenTelemetry\SDK\Trace\SamplerInterface;

class ParentBasedAlwaysOnSamplerFactory implements SamplerFactoryInterface
{
#[\Override]
public function create(): SamplerInterface
{
return new ParentBased(new AlwaysOnSampler());
}
}
20 changes: 20 additions & 0 deletions src/SDK/Trace/Sampler/ParentBasedTraceIdRatioSamplerFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\SDK\Trace\Sampler;

use OpenTelemetry\SDK\Common\Configuration\Configuration;
use OpenTelemetry\SDK\Common\Configuration\Variables;
use OpenTelemetry\SDK\Trace\SamplerInterface;

class ParentBasedTraceIdRatioSamplerFactory implements SamplerFactoryInterface
{
#[\Override]
public function create(): SamplerInterface
{
$ratio = Configuration::getRatio(Variables::OTEL_TRACES_SAMPLER_ARG);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$ratio = Configuration::getRatio(Variables::OTEL_TRACES_SAMPLER_ARG);
$ratio = Configuration::getRatio(Variables::OTEL_TRACES_SAMPLER_ARG, 1.);


return new ParentBased(new TraceIdRatioBasedSampler($ratio));
}
}
12 changes: 12 additions & 0 deletions src/SDK/Trace/Sampler/SamplerFactoryInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\SDK\Trace\Sampler;

use OpenTelemetry\SDK\Trace\SamplerInterface;

interface SamplerFactoryInterface
Copy link
Contributor

@Nevay Nevay Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer if we use EnvComponentLoader instead of adding another {type}Factory to work towards #1681 / to align with file-based config ComponentProviders.

If we want to start with just samplers:

class SamplerFactory
{
    public function create(): SamplerInterface
    {
        $registry = new EnvComponentLoaderRegistry();
        foreach (ServiceLoader::load(EnvComponentLoader::class) as $loader) {
            $registry->register($loader);
        }

        $env = new EnvResolver();
        $context = new Context();

        $samplerName = $env->string(Variables::OTEL_TRACES_SAMPLER) ?? Defaults::OTEL_TRACES_SAMPLER;

        return $registry->load(SamplerInterface::class, $samplerName, $env, $context);
    }
}
/**
 * @implements EnvComponentLoader<SamplerInterface>
 */
final class SamplerLoaderTraceIdRatioBased implements EnvComponentLoader
{
    #[\Override]
    public function load(EnvResolver $env, EnvComponentLoaderRegistry $registry, Context $context): SamplerInterface
    {
        return new TraceIdRatioBasedSampler($env->numeric(Variables::OTEL_TRACES_SAMPLER_ARG, max: 1) ?? 1.);
    }

    #[\Override]
    public function name(): string
    {
        return 'traceidratio';
    }
}
# _register.php
ServiceLoader::register(EnvComponentLoader::class, SamplerLoaderTraceIdRatioBased::class);
// ...

{
public function create(): SamplerInterface;
}
20 changes: 20 additions & 0 deletions src/SDK/Trace/Sampler/TraceIdRatioBasedSamplerFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\SDK\Trace\Sampler;

use OpenTelemetry\SDK\Common\Configuration\Configuration;
use OpenTelemetry\SDK\Common\Configuration\Variables;
use OpenTelemetry\SDK\Trace\SamplerInterface;

class TraceIdRatioBasedSamplerFactory implements SamplerFactoryInterface
{
#[\Override]
public function create(): SamplerInterface
{
$ratio = Configuration::getRatio(Variables::OTEL_TRACES_SAMPLER_ARG);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$ratio = Configuration::getRatio(Variables::OTEL_TRACES_SAMPLER_ARG);
$ratio = Configuration::getRatio(Variables::OTEL_TRACES_SAMPLER_ARG, 1.);


return new TraceIdRatioBasedSampler($ratio);
}
}
10 changes: 10 additions & 0 deletions src/SDK/Trace/Sampler/_register.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

\OpenTelemetry\SDK\Registry::registerSamplerFactory('always_on', \OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSamplerFactory::class);
\OpenTelemetry\SDK\Registry::registerSamplerFactory('always_off', \OpenTelemetry\SDK\Trace\Sampler\AlwaysOffSamplerFactory::class);
\OpenTelemetry\SDK\Registry::registerSamplerFactory('traceidratio', \OpenTelemetry\SDK\Trace\Sampler\TraceIdRatioBasedSamplerFactory::class);
\OpenTelemetry\SDK\Registry::registerSamplerFactory('parentbased_always_on', \OpenTelemetry\SDK\Trace\Sampler\ParentBasedAlwaysOnSamplerFactory::class);
\OpenTelemetry\SDK\Registry::registerSamplerFactory('parentbased_always_off', \OpenTelemetry\SDK\Trace\Sampler\ParentBasedAlwaysOffSamplerFactory::class);
\OpenTelemetry\SDK\Registry::registerSamplerFactory('parentbased_traceidratio', \OpenTelemetry\SDK\Trace\Sampler\ParentBasedTraceIdRatioSamplerFactory::class);
30 changes: 7 additions & 23 deletions src/SDK/Trace/SamplerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,22 @@

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\Trace\Sampler\AlwaysOffSampler;
use OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSampler;
use OpenTelemetry\SDK\Trace\Sampler\ParentBased;
use OpenTelemetry\SDK\Trace\Sampler\TraceIdRatioBasedSampler;
use OpenTelemetry\SDK\Registry;
use RuntimeException;

class SamplerFactory
{
private const TRACEIDRATIO_PREFIX = 'traceidratio';

public function create(): SamplerInterface
{
$name = Configuration::getString(Env::OTEL_TRACES_SAMPLER);

if (str_contains($name, self::TRACEIDRATIO_PREFIX)) {
$arg = Configuration::getRatio(Env::OTEL_TRACES_SAMPLER_ARG);
try {
$factory = Registry::samplerFactory($name);

switch ($name) {
case Values::VALUE_TRACE_ID_RATIO:
return new TraceIdRatioBasedSampler($arg);
case Values::VALUE_PARENT_BASED_TRACE_ID_RATIO:
return new ParentBased(new TraceIdRatioBasedSampler($arg));
}
return $factory->create();
} catch (RuntimeException) {
throw new InvalidArgumentException(sprintf('Unknown sampler: %s', $name));
}

return match ($name) {
Values::VALUE_ALWAYS_ON => new AlwaysOnSampler(),
Values::VALUE_ALWAYS_OFF => new AlwaysOffSampler(),
Values::VALUE_PARENT_BASED_ALWAYS_ON => new ParentBased(new AlwaysOnSampler()),
Values::VALUE_PARENT_BASED_ALWAYS_OFF => new ParentBased(new AlwaysOffSampler()),
default => throw new InvalidArgumentException(sprintf('Unknown sampler: %s', $name)),
};
}
}
1 change: 1 addition & 0 deletions src/SDK/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"Metrics/MetricExporter/_register.php",
"Propagation/_register.php",
"Trace/SpanExporter/_register.php",
"Trace/Sampler/_register.php",
"Common/Dev/Compatibility/_load.php",
"_autoload.php"
]
Expand Down
46 changes: 46 additions & 0 deletions tests/Unit/SDK/FactoryRegistryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@
use OpenTelemetry\SDK\Logs\LogRecordExporterFactoryInterface;
use OpenTelemetry\SDK\Metrics\MetricExporterFactoryInterface;
use OpenTelemetry\SDK\Registry;
use OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSamplerFactory;
use OpenTelemetry\SDK\Trace\Sampler\SamplerFactoryInterface;
use OpenTelemetry\SDK\Trace\SpanExporter\SpanExporterFactoryInterface;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use RuntimeException;
use TypeError;

#[CoversClass(Registry::class)]
Expand Down Expand Up @@ -121,6 +124,42 @@ public static function responsePropagator(): array
];
}

#[DataProvider('samplerFactoryProvider')]
public function test_default_sampler_factories(string $name): void
{
$factory = Registry::samplerFactory($name);
$this->assertInstanceOf(SamplerFactoryInterface::class, $factory);
}

public static function samplerFactoryProvider(): array
{
return [
['always_on'],
['always_off'],
['traceidratio'],
['parentbased_always_on'],
['parentbased_always_off'],
['parentbased_traceidratio'],
];
}

public function test_sampler_factory_throws_for_unknown_sampler(): void
{
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Sampler factory not registered for: unknown_sampler');
Registry::samplerFactory('unknown_sampler');
}

public function test_register_and_retrieve_sampler_factory(): void
{
Registry::registerSamplerFactory('test_sampler', AlwaysOnSamplerFactory::class);

$factory = Registry::samplerFactory('test_sampler');

$this->assertInstanceOf(SamplerFactoryInterface::class, $factory);
$this->assertInstanceOf(AlwaysOnSamplerFactory::class, $factory);
}

#[DataProvider('invalidFactoryProvider')]
public function test_register_invalid_transport_factory($factory): void
{
Expand Down Expand Up @@ -149,6 +188,13 @@ public function test_register_invalid_log_record_exporter_factory($factory): voi
Registry::registerLogRecordExporterFactory('foo', $factory, true);
}

#[DataProvider('invalidFactoryProvider')]
public function test_register_invalid_sampler_factory($factory): void
{
$this->expectException(TypeError::class);
Registry::registerSamplerFactory('foo', $factory, true);
}

public static function invalidFactoryProvider(): array
{
return [
Expand Down
21 changes: 21 additions & 0 deletions tests/Unit/SDK/Trace/Sampler/AlwaysOffSamplerFactoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Tests\Unit\SDK\Trace\Sampler;

use OpenTelemetry\SDK\Trace\Sampler\AlwaysOffSampler;
use OpenTelemetry\SDK\Trace\Sampler\AlwaysOffSamplerFactory;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;

#[CoversClass(AlwaysOffSamplerFactory::class)]
class AlwaysOffSamplerFactoryTest extends TestCase
{
public function test_create(): void
{
$factory = new AlwaysOffSamplerFactory();

$this->assertInstanceOf(AlwaysOffSampler::class, $factory->create());
}
}
21 changes: 21 additions & 0 deletions tests/Unit/SDK/Trace/Sampler/AlwaysOnSamplerFactoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Tests\Unit\SDK\Trace\Sampler;

use OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSampler;
use OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSamplerFactory;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;

#[CoversClass(AlwaysOnSamplerFactory::class)]
class AlwaysOnSamplerFactoryTest extends TestCase
{
public function test_create(): void
{
$factory = new AlwaysOnSamplerFactory();

$this->assertInstanceOf(AlwaysOnSampler::class, $factory->create());
}
}
Loading
Loading