Skip to content
Merged
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
20 changes: 15 additions & 5 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,6 @@ private function getTracingProvidersNode(): ArrayNodeDefinition
->values(array_map(fn (TraceProviderEnum $enum) => $enum->value, TraceProviderEnum::cases()))
->isRequired()
->end()
// TODO: Add support for service
->arrayNode('sampler')
->beforeNormalization()
->ifString()
Expand Down Expand Up @@ -368,10 +367,21 @@ private function getMetricProvidersNode(): ArrayNodeDefinition
->scalarNode('exporter')
->cannotBeEmpty()
->end()
// TODO: Add support for service
->enumNode('filter')
->defaultValue(ExemplarFilterEnum::None->value)
->values(array_map(fn (ExemplarFilterEnum $enum) => $enum->value, ExemplarFilterEnum::cases()))
->arrayNode('filter')
->beforeNormalization()
->ifString()
->then(static fn ($v) => ['type' => $v])
->end()
->children()
->enumNode('type')
->defaultValue(ExemplarFilterEnum::None->value)
->values(array_map(fn (ExemplarFilterEnum $enum) => $enum->value, ExemplarFilterEnum::cases()))
->end()
->scalarNode('service_id')
->cannotBeEmpty()
->info('Required if exemplar filter type is service')
->end()
->end()
->end()
->end()
->end();
Expand Down
17 changes: 14 additions & 3 deletions src/DependencyInjection/OpenTelemetryMetricsExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,24 @@ private function loadMetricExporter(string $name, array $config): void
* @param array{
* type: string,
* exporter?: string,
* filter?: string
* filter?: array{type: string, service_id?: string, options?: array<int, mixed>}
* } $config
*/
private function loadMetricProvider(string $name, array $config): void
{
$filter = (new ChildDefinition('open_telemetry.metrics.exemplar_filter_factory'))
->setArguments([$config['filter'] ?? ExemplarFilterEnum::All->value]);
$filter = (new ChildDefinition('open_telemetry.metrics.exemplar_filter_factory'));

$params = [];
if (isset($config['filter']['type']) && ExemplarFilterEnum::Service->value === $config['filter']['type']) {
if (!array_key_exists('service_id', $config['filter'])) {
throw new \LogicException('To configure an exemplar filter of type service, you must specify the service_id key');
}
$params['service_id'] = new Reference($config['filter']['service_id']);
}
$filter->setArguments([
$config['filter']['type'] ?? ExemplarFilterEnum::All->value,
$params,
]);

$this->container
->setDefinition(
Expand Down
2 changes: 1 addition & 1 deletion src/DependencyInjection/OpenTelemetryTracesExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ private function loadTraceProcessor(string $name, array $config): void
/**
* @param array{
* type: string,
* sampler?: array{type: string, options?: array<int, mixed>},
* sampler?: array{type: string, service_id?: string, options?: array<int, mixed>},
* processors?: string[]
* } $config
*/
Expand Down
1 change: 1 addition & 0 deletions src/OpenTelemetry/Metric/ExemplarFilterEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ enum ExemplarFilterEnum: string
case All = 'all';
case None = 'none';
case WithSampledTrace = 'with_sampled_trace';
case Service = 'service';
}
10 changes: 9 additions & 1 deletion src/OpenTelemetry/Metric/ExemplarFilterFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,22 @@

final class ExemplarFilterFactory
{
public static function create(string $name): ExemplarFilterInterface
/**
* @param array{service_id?: mixed} $params
*/
public static function create(string $name, array $params = []): ExemplarFilterInterface
{
$filter = ExemplarFilterEnum::tryFrom($name);

if (isset($params['service_id']) && false === $params['service_id'] instanceof ExemplarFilterInterface) {
throw new \InvalidArgumentException('Parameter service_id must be an instance of ExemplarFilterInterface');
}

return match ($filter) {
ExemplarFilterEnum::All => new AllExemplarFilter(),
ExemplarFilterEnum::None => new NoneExemplarFilter(),
ExemplarFilterEnum::WithSampledTrace => new WithSampledTraceExemplarFilter(),
ExemplarFilterEnum::Service => $params['service_id'],
default => throw new \InvalidArgumentException(sprintf('Unknown exemplar filter: %s', $name)),
};
}
Expand Down
2 changes: 1 addition & 1 deletion src/OpenTelemetry/Trace/SamplerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public static function create(string $name, array $params = []): SamplerInterfac
{
$sampler = TraceSamplerEnum::tryFrom($name);

if (isset($params['service_id']) && false === $params['service_id']instanceof SamplerInterface) {
if (isset($params['service_id']) && false === $params['service_id'] instanceof SamplerInterface) {
throw new \InvalidArgumentException('Parameter service_id must be an instance of SamplerInterface');
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ open_telemetry:
providers:
default:
type: default
filter: 'none'
exporter: 'open_telemetry.metrics.exporters.in_memory'
exporters:
in_memory:
Expand Down
26 changes: 26 additions & 0 deletions tests/Unit/BundleInitializationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -280,4 +280,30 @@ public function testTracesServiceSamplerException(): void
$kernel->addTestConfig(__DIR__.'/Fixtures/yml/traces-service-sampler-exception.yml');
}]);
}

public function testMetricsServiceExemplarFilter(): void
{
$kernel = self::bootKernel(['config' => function (TestKernel $kernel) {
$kernel->addTestConfig(__DIR__.'/Fixtures/yml/metrics-service-exemplar-filter.yml');
}]);

$publicContainer = $kernel->getContainer();
$privateContainer = self::getContainer();

self::assertTrue($privateContainer->has('MyAllExemplarFilter'));

self::assertFalse($publicContainer->has('open_telemetry.metrics.providers.default'));
self::assertTrue($privateContainer->has('open_telemetry.metrics.providers.default'));
$provider = $privateContainer->get('open_telemetry.metrics.providers.default');
self::assertInstanceOf(MeterProvider::class, $provider);
}

public function testMetricsServiceExemplarFilterException(): void
{
self::expectExceptionObject(new \LogicException('To configure an exemplar filter of type service, you must specify the service_id key'));

self::bootKernel(['config' => function (TestKernel $kernel) {
$kernel->addTestConfig(__DIR__.'/Fixtures/yml/metrics-service-exemplar-filter-exception.yml');
}]);
}
}
6 changes: 5 additions & 1 deletion tests/Unit/DependencyInjection/ConfigurationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,11 @@ public function testReferenceConfiguration(): void
provider:
type: default # One of "noop"; "default", Required
exporter: ~
filter: none # One of "all"; "none"; "with_sampled_trace"
filter:
type: none # One of "all"; "none"; "with_sampled_trace"; "service"

# Required if exemplar filter type is service
service_id: ~
exporters:

# Prototype
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,14 @@ public static function exporters(): \Generator
];
}

/**
* @param array{
* type: string,
* service_id?: string
* } $filter
*/
#[DataProvider('providers')]
public function testProviders(string $type, ?string $exporter, string $filter): void
public function testProviders(string $type, ?string $exporter, array $filter): void
{
$providerConfig = [
'type' => $type,
Expand All @@ -246,10 +252,18 @@ public function testProviders(string $type, ?string $exporter, string $filter):
0,
null !== $exporter ? new Reference($exporter) : null,
);

$filterArg = [
$filter['type'],
[],
];
if (array_key_exists('service_id', $filter)) {
$filterArg[1]['service_id'] = new Reference($filter['service_id']);
}
self::assertContainerBuilderHasServiceDefinitionWithArgument(
'open_telemetry.metrics.providers.main',
1,
(new ChildDefinition('open_telemetry.metrics.exemplar_filter_factory'))->setArguments([$filter]),
(new ChildDefinition('open_telemetry.metrics.exemplar_filter_factory'))->setArguments($filterArg),
);
self::assertContainerBuilderHasServiceDefinitionWithArgument(
'open_telemetry.metrics.providers.main',
Expand All @@ -264,21 +278,34 @@ public function testProviders(string $type, ?string $exporter, string $filter):
* @return \Generator<string, array{
* type: string,
* exporter: ?string,
* filter: string,
* filter: array{type: string, service_id?: string},
* }>
*/
public static function providers(): \Generator
{
yield 'default' => [
'type' => 'default',
'exporter' => 'open_telemetry.metrics.exporters.default',
'filter' => 'all',
'filter' => [
'type' => 'all',
],
];

yield 'noop' => [
'type' => 'noop',
'exporter' => null,
'filter' => 'none',
'filter' => [
'type' => 'none',
],
];

yield 'filter service' => [
'type' => 'default',
'exporter' => 'open_telemetry.metrics.exporters.default',
'filter' => [
'type' => 'service',
'service_id' => 'my_filter',
],
];
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
open_telemetry:
service:
namespace: 'FriendsOfOpenTelemetry/OpenTelemetry'
name: 'Test'
version: '0.0.0'
environment: test
metrics:
meters:
main:
provider: 'open_telemetry.metrics.providers.default'
providers:
default:
type: default
filter:
type: service
exporter: 'open_telemetry.metrics.exporters.otlp'
exporters:
otlp:
dsn: http+otlp://localhost
23 changes: 23 additions & 0 deletions tests/Unit/Fixtures/yml/metrics-service-exemplar-filter.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
services:
MyAllExemplarFilter:
class: OpenTelemetry\SDK\Metrics\Exemplar\ExemplarFilter\AllExemplarFilter
open_telemetry:
service:
namespace: 'FriendsOfOpenTelemetry/OpenTelemetry'
name: 'Test'
version: '0.0.0'
environment: test
metrics:
meters:
main:
provider: 'open_telemetry.metrics.providers.default'
providers:
default:
type: default
filter:
type: service
service_id: 'MyAllExemplarFilter'
exporter: 'open_telemetry.metrics.exporters.otlp'
exporters:
otlp:
dsn: http+otlp://localhost
48 changes: 40 additions & 8 deletions tests/Unit/OpenTelemetry/Metric/ExemplarFilterFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

use FriendsOfOpenTelemetry\OpenTelemetryBundle\OpenTelemetry\Metric\ExemplarFilterEnum;
use FriendsOfOpenTelemetry\OpenTelemetryBundle\OpenTelemetry\Metric\ExemplarFilterFactory;
use OpenTelemetry\Context\ContextInterface;
use OpenTelemetry\SDK\Common\Attribute\AttributesInterface;
use OpenTelemetry\SDK\Metrics\Exemplar\ExemplarFilter\AllExemplarFilter;
use OpenTelemetry\SDK\Metrics\Exemplar\ExemplarFilter\NoneExemplarFilter;
use OpenTelemetry\SDK\Metrics\Exemplar\ExemplarFilter\WithSampledTraceExemplarFilter;
Expand All @@ -15,22 +17,25 @@
#[CoversClass(ExemplarFilterFactory::class)]
class ExemplarFilterFactoryTest extends TestCase
{
/**
* @param ?array<int, mixed> $params
*/
#[DataProvider('exemplarFilter')]
public function testCreate(string $name, ?string $class): void
public function testCreate(string $name, ?string $expectedClass, ?array $params = [], ?\Exception $exception = null): void
{
if (null === $class) {
self::expectExceptionObject(
new \InvalidArgumentException(sprintf('Unknown exemplar filter: %s', $name)),
);
if ($exception instanceof \Exception) {
self::expectExceptionObject($exception);
}

self::assertInstanceOf($class, ExemplarFilterFactory::create($name));
self::assertInstanceOf($expectedClass, ExemplarFilterFactory::create($name, $params));
}

/**
* @return \Generator<string, array{
* string,
* ?class-string<ExemplarFilterInterface>,
* 0: string,
* 1: ?class-string<ExemplarFilterInterface>,
* 2?: array<int|string, mixed>,
* 3?: ?\Exception
* }>
*/
public static function exemplarFilter(): \Generator
Expand All @@ -50,9 +55,36 @@ public static function exemplarFilter(): \Generator
WithSampledTraceExemplarFilter::class,
];

$anonymousFilter = new class implements ExemplarFilterInterface {
/** @phpstan-ignore-next-line */
public function accepts(float|int $value, AttributesInterface $attributes, ContextInterface $context, int $timestamp): bool
{
return true;
}
};

yield 'service' => [
ExemplarFilterEnum::Service->value,
ExemplarFilterInterface::class,
[
'service_id' => $anonymousFilter,
],
];

yield 'service with stdClass' => [
ExemplarFilterEnum::Service->value,
ExemplarFilterInterface::class,
[
'service_id' => new \stdClass(),
],
new \InvalidArgumentException('Parameter service_id must be an instance of ExemplarFilterInterface'),
];

yield 'unknown' => [
'unknown',
null,
[],
new \InvalidArgumentException('Unknown exemplar filter: unknown'),
];
}
}
8 changes: 8 additions & 0 deletions tests/Unit/OpenTelemetry/Trace/SamplerFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,5 +128,13 @@ public function getDescription(): string
],
new \InvalidArgumentException('Parameter service_id must be an instance of SamplerInterface'),
];

yield [
'unknown',
SamplerInterface::class,
'',
[],
new \InvalidArgumentException('Unknown sampler: unknown'),
];
}
}
Loading