Skip to content

Commit 1e9d1f7

Browse files
committed
feat(sdk): add custom sampler registry support
Add ability to register custom sampler factories via the Registry, allowing users to extend the SDK with custom sampling strategies. Implementation: - Add SamplerFactoryInterface for sampler factory contracts - Add factory classes for all built-in samplers (AlwaysOn, AlwaysOff, TraceIdRatioBased, ParentBased variants) - Add Registry::registerSamplerFactory() and Registry::samplerFactory() - Refactor SamplerFactory to use the registry instead of hardcoded logic - Add _register.php to auto-register built-in sampler factories Tests: - Add unit tests for all sampler factory classes - Add registry tests for sampler factory registration - Add tests for custom sampler registration with clobber behavior
1 parent 6957241 commit 1e9d1f7

18 files changed

Lines changed: 410 additions & 23 deletions

src/SDK/Registry.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use OpenTelemetry\SDK\Logs\LogRecordExporterFactoryInterface;
1111
use OpenTelemetry\SDK\Metrics\MetricExporterFactoryInterface;
1212
use OpenTelemetry\SDK\Resource\ResourceDetectorInterface;
13+
use OpenTelemetry\SDK\Trace\Sampler\SamplerFactoryInterface;
1314
use OpenTelemetry\SDK\Trace\SpanExporter\SpanExporterFactoryInterface;
1415
use RuntimeException;
1516
use TypeError;
@@ -28,6 +29,7 @@ class Registry
2829
private static array $logRecordExporterFactories = [];
2930
private static array $resourceDetectors = [];
3031
private static array $responsePropagators = [];
32+
private static array $samplerFactories = [];
3133

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

139+
/**
140+
* @param SamplerFactoryInterface|class-string<SamplerFactoryInterface> $factory
141+
* @throws TypeError
142+
*/
143+
public static function registerSamplerFactory(string $sampler, SamplerFactoryInterface|string $factory, bool $clobber = false): void
144+
{
145+
if (!$clobber && array_key_exists($sampler, self::$samplerFactories)) {
146+
return;
147+
}
148+
if (!is_subclass_of($factory, SamplerFactoryInterface::class)) {
149+
throw new TypeError(
150+
sprintf(
151+
'Cannot register sampler factory: %s must exist and implement %s',
152+
is_string($factory) ? $factory : $factory::class,
153+
SamplerFactoryInterface::class
154+
)
155+
);
156+
}
157+
self::$samplerFactories[$sampler] = $factory;
158+
}
159+
137160
public static function spanExporterFactory(string $exporter): SpanExporterFactoryInterface
138161
{
139162
if (!array_key_exists($exporter, self::$spanExporterFactories)) {
@@ -221,4 +244,16 @@ public static function resourceDetectors(): array
221244
{
222245
return array_values(self::$resourceDetectors);
223246
}
247+
248+
public static function samplerFactory(string $sampler): SamplerFactoryInterface
249+
{
250+
if (!array_key_exists($sampler, self::$samplerFactories)) {
251+
throw new RuntimeException('Sampler factory not registered for: ' . $sampler);
252+
}
253+
$class = self::$samplerFactories[$sampler];
254+
$factory = (is_callable($class)) ? $class : new $class();
255+
assert($factory instanceof SamplerFactoryInterface);
256+
257+
return $factory;
258+
}
224259
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\SDK\Trace\Sampler;
6+
7+
use OpenTelemetry\SDK\Trace\SamplerInterface;
8+
9+
class AlwaysOffSamplerFactory implements SamplerFactoryInterface
10+
{
11+
#[\Override]
12+
public function create(): SamplerInterface
13+
{
14+
return new AlwaysOffSampler();
15+
}
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\SDK\Trace\Sampler;
6+
7+
use OpenTelemetry\SDK\Trace\SamplerInterface;
8+
9+
class AlwaysOnSamplerFactory implements SamplerFactoryInterface
10+
{
11+
#[\Override]
12+
public function create(): SamplerInterface
13+
{
14+
return new AlwaysOnSampler();
15+
}
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\SDK\Trace\Sampler;
6+
7+
use OpenTelemetry\SDK\Trace\SamplerInterface;
8+
9+
class ParentBasedAlwaysOffSamplerFactory implements SamplerFactoryInterface
10+
{
11+
#[\Override]
12+
public function create(): SamplerInterface
13+
{
14+
return new ParentBased(new AlwaysOffSampler());
15+
}
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\SDK\Trace\Sampler;
6+
7+
use OpenTelemetry\SDK\Trace\SamplerInterface;
8+
9+
class ParentBasedAlwaysOnSamplerFactory implements SamplerFactoryInterface
10+
{
11+
#[\Override]
12+
public function create(): SamplerInterface
13+
{
14+
return new ParentBased(new AlwaysOnSampler());
15+
}
16+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\SDK\Trace\Sampler;
6+
7+
use OpenTelemetry\SDK\Common\Configuration\Configuration;
8+
use OpenTelemetry\SDK\Common\Configuration\Variables;
9+
use OpenTelemetry\SDK\Trace\SamplerInterface;
10+
11+
class ParentBasedTraceIdRatioSamplerFactory implements SamplerFactoryInterface
12+
{
13+
#[\Override]
14+
public function create(): SamplerInterface
15+
{
16+
$ratio = Configuration::getRatio(Variables::OTEL_TRACES_SAMPLER_ARG);
17+
18+
return new ParentBased(new TraceIdRatioBasedSampler($ratio));
19+
}
20+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\SDK\Trace\Sampler;
6+
7+
use OpenTelemetry\SDK\Trace\SamplerInterface;
8+
9+
interface SamplerFactoryInterface
10+
{
11+
public function create(): SamplerInterface;
12+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\SDK\Trace\Sampler;
6+
7+
use OpenTelemetry\SDK\Common\Configuration\Configuration;
8+
use OpenTelemetry\SDK\Common\Configuration\Variables;
9+
use OpenTelemetry\SDK\Trace\SamplerInterface;
10+
11+
class TraceIdRatioBasedSamplerFactory implements SamplerFactoryInterface
12+
{
13+
#[\Override]
14+
public function create(): SamplerInterface
15+
{
16+
$ratio = Configuration::getRatio(Variables::OTEL_TRACES_SAMPLER_ARG);
17+
18+
return new TraceIdRatioBasedSampler($ratio);
19+
}
20+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
\OpenTelemetry\SDK\Registry::registerSamplerFactory('always_on', \OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSamplerFactory::class);
6+
\OpenTelemetry\SDK\Registry::registerSamplerFactory('always_off', \OpenTelemetry\SDK\Trace\Sampler\AlwaysOffSamplerFactory::class);
7+
\OpenTelemetry\SDK\Registry::registerSamplerFactory('traceidratio', \OpenTelemetry\SDK\Trace\Sampler\TraceIdRatioBasedSamplerFactory::class);
8+
\OpenTelemetry\SDK\Registry::registerSamplerFactory('parentbased_always_on', \OpenTelemetry\SDK\Trace\Sampler\ParentBasedAlwaysOnSamplerFactory::class);
9+
\OpenTelemetry\SDK\Registry::registerSamplerFactory('parentbased_always_off', \OpenTelemetry\SDK\Trace\Sampler\ParentBasedAlwaysOffSamplerFactory::class);
10+
\OpenTelemetry\SDK\Registry::registerSamplerFactory('parentbased_traceidratio', \OpenTelemetry\SDK\Trace\Sampler\ParentBasedTraceIdRatioSamplerFactory::class);

src/SDK/Trace/SamplerFactory.php

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,38 +6,22 @@
66

77
use InvalidArgumentException;
88
use OpenTelemetry\SDK\Common\Configuration\Configuration;
9-
use OpenTelemetry\SDK\Common\Configuration\KnownValues as Values;
109
use OpenTelemetry\SDK\Common\Configuration\Variables as Env;
11-
use OpenTelemetry\SDK\Trace\Sampler\AlwaysOffSampler;
12-
use OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSampler;
13-
use OpenTelemetry\SDK\Trace\Sampler\ParentBased;
14-
use OpenTelemetry\SDK\Trace\Sampler\TraceIdRatioBasedSampler;
10+
use OpenTelemetry\SDK\Registry;
11+
use RuntimeException;
1512

1613
class SamplerFactory
1714
{
18-
private const TRACEIDRATIO_PREFIX = 'traceidratio';
19-
2015
public function create(): SamplerInterface
2116
{
2217
$name = Configuration::getString(Env::OTEL_TRACES_SAMPLER);
2318

24-
if (str_contains($name, self::TRACEIDRATIO_PREFIX)) {
25-
$arg = Configuration::getRatio(Env::OTEL_TRACES_SAMPLER_ARG);
19+
try {
20+
$factory = Registry::samplerFactory($name);
2621

27-
switch ($name) {
28-
case Values::VALUE_TRACE_ID_RATIO:
29-
return new TraceIdRatioBasedSampler($arg);
30-
case Values::VALUE_PARENT_BASED_TRACE_ID_RATIO:
31-
return new ParentBased(new TraceIdRatioBasedSampler($arg));
32-
}
22+
return $factory->create();
23+
} catch (RuntimeException) {
24+
throw new InvalidArgumentException(sprintf('Unknown sampler: %s', $name));
3325
}
34-
35-
return match ($name) {
36-
Values::VALUE_ALWAYS_ON => new AlwaysOnSampler(),
37-
Values::VALUE_ALWAYS_OFF => new AlwaysOffSampler(),
38-
Values::VALUE_PARENT_BASED_ALWAYS_ON => new ParentBased(new AlwaysOnSampler()),
39-
Values::VALUE_PARENT_BASED_ALWAYS_OFF => new ParentBased(new AlwaysOffSampler()),
40-
default => throw new InvalidArgumentException(sprintf('Unknown sampler: %s', $name)),
41-
};
4226
}
4327
}

0 commit comments

Comments
 (0)