opentelemetry-php/src/Config/SDK/ComponentProvider/OpenTelemetrySdk.php

415 lines
19 KiB
PHP

<?php
declare(strict_types=1);
namespace OpenTelemetry\Config\SDK\ComponentProvider;
use OpenTelemetry\API\Common\Time\Clock;
use OpenTelemetry\Config\SDK\Configuration\ComponentPlugin;
use OpenTelemetry\Config\SDK\Configuration\ComponentProvider;
use OpenTelemetry\Config\SDK\Configuration\ComponentProviderRegistry;
use OpenTelemetry\Config\SDK\Configuration\Context;
use OpenTelemetry\Config\SDK\Configuration\Validation;
use OpenTelemetry\Context\Propagation\NoopTextMapPropagator;
use OpenTelemetry\Context\Propagation\TextMapPropagatorInterface;
use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Common\Configuration\Parser\MapParser;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeFactory;
use OpenTelemetry\SDK\Logs\EventLoggerProvider;
use OpenTelemetry\SDK\Logs\LoggerProvider;
use OpenTelemetry\SDK\Logs\LogRecordProcessorInterface;
use OpenTelemetry\SDK\Logs\Processor\MultiLogRecordProcessor;
use OpenTelemetry\SDK\Metrics\DefaultAggregationProviderInterface;
use OpenTelemetry\SDK\Metrics\InstrumentType;
use OpenTelemetry\SDK\Metrics\MeterProvider;
use OpenTelemetry\SDK\Metrics\MetricReaderInterface;
use OpenTelemetry\SDK\Metrics\StalenessHandler\NoopStalenessHandlerFactory;
use OpenTelemetry\SDK\Metrics\View\CriteriaViewRegistry;
use OpenTelemetry\SDK\Metrics\View\SelectionCriteria\AllCriteria;
use OpenTelemetry\SDK\Metrics\View\SelectionCriteria\InstrumentationScopeNameCriteria;
use OpenTelemetry\SDK\Metrics\View\SelectionCriteria\InstrumentationScopeSchemaUrlCriteria;
use OpenTelemetry\SDK\Metrics\View\SelectionCriteria\InstrumentationScopeVersionCriteria;
use OpenTelemetry\SDK\Metrics\View\SelectionCriteria\InstrumentNameCriteria;
use OpenTelemetry\SDK\Metrics\View\SelectionCriteria\InstrumentTypeCriteria;
use OpenTelemetry\SDK\Metrics\View\ViewTemplate;
use OpenTelemetry\SDK\Resource\ResourceInfo;
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
use OpenTelemetry\SDK\SdkBuilder;
use OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSampler;
use OpenTelemetry\SDK\Trace\Sampler\ParentBased;
use OpenTelemetry\SDK\Trace\SamplerInterface;
use OpenTelemetry\SDK\Trace\SpanLimits;
use OpenTelemetry\SDK\Trace\SpanProcessorInterface;
use OpenTelemetry\SDK\Trace\TracerProvider;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
/**
* @internal
*
* @implements ComponentProvider<SdkBuilder>
*/
final class OpenTelemetrySdk implements ComponentProvider
{
/**
* @param array{
* file_format: '0.3',
* disabled: bool,
* resource: array{
* attributes: array,
* attributes_list: ?string,
* schema_url: ?string,
* },
* attribute_limits: array{
* attribute_value_length_limit: ?int<0, max>,
* attribute_count_limit: int<0, max>,
* },
* propagator: ?ComponentPlugin<TextMapPropagatorInterface>,
* tracer_provider: array{
* limits: array{
* attribute_value_length_limit: ?int<0, max>,
* attribute_count_limit: ?int<0, max>,
* event_count_limit: int<0, max>,
* link_count_limit: int<0, max>,
* event_attribute_count_limit: ?int<0, max>,
* link_attribute_count_limit: ?int<0, max>,
* },
* sampler: ?ComponentPlugin<SamplerInterface>,
* processors: list<ComponentPlugin<SpanProcessorInterface>>,
* },
* meter_provider: array{
* views: list<array{
* stream: array{
* name: ?string,
* description: ?string,
* attribute_keys: list<string>,
* aggregation: ?ComponentPlugin<DefaultAggregationProviderInterface>,
* },
* selector: array{
* instrument_type: 'counter'|'histogram'|'observable_counter'|'observable_gauge'|'observable_up_down_counter'|'up_down_counter'|null,
* instrument_name: ?non-empty-string,
* unit: ?string,
* meter_name: ?string,
* meter_version: ?string,
* meter_schema_url: ?string,
* },
* }>,
* readers: list<ComponentPlugin<MetricReaderInterface>>,
* },
* logger_provider: array{
* limits: array{
* attribute_value_length_limit: ?int<0, max>,
* attribute_count_limit: ?int<0, max>,
* },
* processors: list<ComponentPlugin<LogRecordProcessorInterface>>,
* },
* } $properties
*/
public function createPlugin(array $properties, Context $context): SdkBuilder
{
$sdkBuilder = new SdkBuilder();
$propagator = $properties['propagator']?->create($context) ?? NoopTextMapPropagator::getInstance();
$sdkBuilder->setPropagator($propagator);
if ($properties['disabled']) {
return $sdkBuilder;
}
$attributes = array_column($properties['resource']['attributes'], 'value', 'name') + MapParser::parse($properties['resource']['attributes_list']);
$resource = ResourceInfoFactory::defaultResource()
->merge(ResourceInfo::create(
attributes: Attributes::create($attributes),
schemaUrl: $properties['resource']['schema_url'],
));
$spanProcessors = [];
foreach ($properties['tracer_provider']['processors'] as $processor) {
$spanProcessors[] = $processor->create($context);
}
// <editor-fold desc="tracer_provider">
$tracerProvider = new TracerProvider(
spanProcessors: $spanProcessors,
sampler: $properties['tracer_provider']['sampler']?->create($context) ?? new ParentBased(new AlwaysOnSampler()),
resource: $resource,
spanLimits: new SpanLimits(
attributesFactory: Attributes::factory(
attributeCountLimit: $properties['tracer_provider']['limits']['attribute_count_limit']
?? $properties['attribute_limits']['attribute_count_limit'],
attributeValueLengthLimit: $properties['tracer_provider']['limits']['attribute_value_length_limit']
?? $properties['attribute_limits']['attribute_value_length_limit'],
),
eventAttributesFactory: Attributes::factory(
attributeCountLimit: $properties['tracer_provider']['limits']['event_attribute_count_limit']
?? $properties['tracer_provider']['limits']['attribute_count_limit']
?? $properties['attribute_limits']['attribute_count_limit'],
attributeValueLengthLimit: $properties['tracer_provider']['limits']['attribute_value_length_limit']
?? $properties['attribute_limits']['attribute_value_length_limit'],
),
linkAttributesFactory: Attributes::factory(
attributeCountLimit: $properties['tracer_provider']['limits']['link_attribute_count_limit']
?? $properties['tracer_provider']['limits']['attribute_count_limit']
?? $properties['attribute_limits']['attribute_count_limit'],
attributeValueLengthLimit: $properties['tracer_provider']['limits']['attribute_value_length_limit']
?? $properties['attribute_limits']['attribute_value_length_limit'],
),
eventCountLimit: $properties['tracer_provider']['limits']['event_count_limit'],
linkCountLimit: $properties['tracer_provider']['limits']['link_count_limit'],
),
);
// </editor-fold>
// <editor-fold desc="meter_provider">
$metricReaders = [];
foreach ($properties['meter_provider']['readers'] as $reader) {
$metricReaders[] = $reader->create($context);
}
$viewRegistry = new CriteriaViewRegistry();
foreach ($properties['meter_provider']['views'] as $view) {
$criteria = [];
if (isset($view['selector']['instrument_type'])) {
$criteria[] = new InstrumentTypeCriteria(match ($view['selector']['instrument_type']) {
'counter' => InstrumentType::COUNTER,
'histogram' => InstrumentType::HISTOGRAM,
'observable_counter' => InstrumentType::ASYNCHRONOUS_COUNTER,
'observable_gauge' => InstrumentType::ASYNCHRONOUS_GAUGE,
'observable_up_down_counter' => InstrumentType::ASYNCHRONOUS_UP_DOWN_COUNTER,
'up_down_counter' => InstrumentType::UP_DOWN_COUNTER,
});
}
if (isset($view['selector']['instrument_name'])) {
$criteria[] = new InstrumentNameCriteria($view['selector']['instrument_name']);
}
if (isset($view['selector']['unit'])) {
// TODO Add unit criteria
}
if (isset($view['selector']['meter_name'])) {
$criteria[] = new InstrumentationScopeNameCriteria($view['selector']['meter_name']);
}
if (isset($view['selector']['meter_version'])) {
$criteria[] = new InstrumentationScopeVersionCriteria($view['selector']['meter_version']);
}
if (isset($view['selector']['meter_schema_url'])) {
$criteria[] = new InstrumentationScopeSchemaUrlCriteria($view['selector']['meter_schema_url']);
}
$viewTemplate = ViewTemplate::create();
if (isset($view['stream']['name'])) {
$viewTemplate = $viewTemplate->withName($view['stream']['name']);
}
if (isset($view['stream']['description'])) {
$viewTemplate = $viewTemplate->withDescription($view['stream']['description']);
}
if ($view['stream']['attribute_keys']) {
$viewTemplate = $viewTemplate->withAttributeKeys($view['stream']['attribute_keys']);
}
if (isset($view['stream']['aggregation'])) {
// TODO Add support for aggregation providers in views to allow usage of advisory
}
$viewRegistry->register(new AllCriteria($criteria), $viewTemplate);
}
/** @psalm-suppress InvalidArgument TODO update metric reader interface */
$meterProvider = new MeterProvider(
contextStorage: null,
resource: $resource,
clock: Clock::getDefault(),
attributesFactory: Attributes::factory(),
instrumentationScopeFactory: new InstrumentationScopeFactory(Attributes::factory()),
metricReaders: $metricReaders, // @phpstan-ignore-line
viewRegistry: $viewRegistry,
exemplarFilter: null,
stalenessHandlerFactory: new NoopStalenessHandlerFactory(),
);
// </editor-fold>
// <editor-fold desc="logger_provider">
$logRecordProcessors = [];
foreach ($properties['logger_provider']['processors'] as $processor) {
$logRecordProcessors[] = $processor->create($context);
}
// TODO Allow injecting log record attributes factory
$loggerProvider = new LoggerProvider(
processor: new MultiLogRecordProcessor($logRecordProcessors),
instrumentationScopeFactory: new InstrumentationScopeFactory(Attributes::factory()),
resource: $resource,
);
$eventLoggerProvider = new EventLoggerProvider($loggerProvider);
// </editor-fold>
$sdkBuilder->setTracerProvider($tracerProvider);
$sdkBuilder->setMeterProvider($meterProvider);
$sdkBuilder->setLoggerProvider($loggerProvider);
$sdkBuilder->setEventLoggerProvider($eventLoggerProvider);
return $sdkBuilder;
}
public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinition
{
$node = new ArrayNodeDefinition('open_telemetry');
$node
->addDefaultsIfNotSet()
->ignoreExtraKeys()
->children()
->scalarNode('file_format')
->isRequired()
->example('0.1')
->validate()->always(Validation::ensureString())->end()
->validate()->ifNotInArray(['0.3'])->thenInvalid('unsupported version')->end()
->end()
->booleanNode('disabled')->defaultFalse()->end()
->append($this->getResourceConfig())
->append($this->getAttributeLimitsConfig())
->append($registry->component('propagator', TextMapPropagatorInterface::class))
->append($this->getTracerProviderConfig($registry))
->append($this->getMeterProviderConfig($registry))
->append($this->getLoggerProviderConfig($registry))
->end();
return $node;
}
private function getResourceConfig(): ArrayNodeDefinition
{
$node = new ArrayNodeDefinition('resource');
$node
->addDefaultsIfNotSet()
->children()
->arrayNode('attributes')
->arrayPrototype()
->children()
->scalarNode('name')->isRequired()->end()
->variableNode('value')->isRequired()->end()
// @todo use type to validate and/or cast attributes
->enumNode('type')->defaultNull()
->values(['string', 'bool', 'int', 'double', 'string_array', 'bool_array', 'int_array', 'double_array'])
->end()
->end()
->end()
->end()
->scalarNode('attributes_list')->defaultNull()->validate()->always(Validation::ensureString())->end()->end()
->scalarNode('schema_url')->defaultNull()->validate()->always(Validation::ensureString())->end()->end()
->end();
return $node;
}
private function getAttributeLimitsConfig(): ArrayNodeDefinition
{
$node = new ArrayNodeDefinition('attribute_limits');
$node
->addDefaultsIfNotSet()
->children()
->integerNode('attribute_value_length_limit')->min(0)->defaultNull()->end()
->integerNode('attribute_count_limit')->min(0)->defaultValue(128)->end()
->end();
return $node;
}
private function getTracerProviderConfig(ComponentProviderRegistry $registry): ArrayNodeDefinition
{
$node = new ArrayNodeDefinition('tracer_provider');
$node
->addDefaultsIfNotSet()
->children()
->arrayNode('limits')
->addDefaultsIfNotSet()
->children()
->integerNode('attribute_value_length_limit')->min(0)->defaultNull()->end()
->integerNode('attribute_count_limit')->min(0)->defaultNull()->end()
->integerNode('event_count_limit')->min(0)->defaultValue(128)->end()
->integerNode('link_count_limit')->min(0)->defaultValue(128)->end()
->integerNode('event_attribute_count_limit')->min(0)->defaultNull()->end()
->integerNode('link_attribute_count_limit')->min(0)->defaultNull()->end()
->end()
->end()
->append($registry->component('sampler', SamplerInterface::class))
->append($registry->componentArrayList('processors', SpanProcessorInterface::class))
->end()
;
return $node;
}
private function getMeterProviderConfig(ComponentProviderRegistry $registry): ArrayNodeDefinition
{
$node = new ArrayNodeDefinition('meter_provider');
$node
->addDefaultsIfNotSet()
->children()
->arrayNode('views')
->arrayPrototype()
->children()
->arrayNode('stream')
->addDefaultsIfNotSet()
->children()
->scalarNode('name')->defaultNull()->validate()->always(Validation::ensureString())->end()->end()
->scalarNode('description')->defaultNull()->validate()->always(Validation::ensureString())->end()->end()
->arrayNode('attribute_keys')
->scalarPrototype()->validate()->always(Validation::ensureString())->end()->end()
->end()
->append($registry->component('aggregation', DefaultAggregationProviderInterface::class))
->end()
->end()
->arrayNode('selector')
->addDefaultsIfNotSet()
->children()
->enumNode('instrument_type')
->values([
'counter',
'histogram',
'observable_counter',
'observable_gauge',
'observable_up_down_counter',
'up_down_counter',
])
->defaultNull()
->end()
->scalarNode('instrument_name')->defaultNull()->validate()->always(Validation::ensureString())->end()->cannotBeEmpty()->end()
->scalarNode('unit')->defaultNull()->validate()->always(Validation::ensureString())->end()->end()
->scalarNode('meter_name')->defaultNull()->validate()->always(Validation::ensureString())->end()->end()
->scalarNode('meter_version')->defaultNull()->validate()->always(Validation::ensureString())->end()->end()
->scalarNode('meter_schema_url')->defaultNull()->validate()->always(Validation::ensureString())->end()->end()
->end()
->end()
->end()
->end()
->end()
->append($registry->componentArrayList('readers', MetricReaderInterface::class))
->end()
;
return $node;
}
private function getLoggerProviderConfig(ComponentProviderRegistry $registry): ArrayNodeDefinition
{
$node = new ArrayNodeDefinition('logger_provider');
$node
->addDefaultsIfNotSet()
->children()
->arrayNode('limits')
->addDefaultsIfNotSet()
->children()
->integerNode('attribute_value_length_limit')->min(0)->defaultNull()->end()
->integerNode('attribute_count_limit')->min(0)->defaultNull()->end()
->end()
->end()
->append($registry->componentArrayList('processors', LogRecordProcessorInterface::class))
->end()
;
return $node;
}
}