Skip to content

Commit

Permalink
auto-instrumentation registration (#1304)
Browse files Browse the repository at this point in the history
* [WIP] Add instrumentation configuration

* add autoloading for auto-instrumentations using SPI

* allow autoloading and non-autoloading to work

* fix attribute

* experimental config file

* fixing invalid dependencies
- deptrac was rightly complaining that API (indirectly) depended on SDK through config/sdk. For now,
remove usage of Config\SDK\Configuration\Context
- update deptrac config to allow some new dependencies

* dont register hook manager globally or in sdk

* remove unused function, psalm ignore missing extension function

* possibly fixing type-hint
psalm doesn't complain now, so that should be good

* load config files relative to cwd

* fixing hook manager enable/disable + psalm complaints

* fixing 8.1 psalm error

* use context to pass providers to instrumentations
- make "register global" a function of Sdk, but keep the sdk builder's interface intact
- invent an API instrumentation context, similar to the one in config/sdk, to pass providers
  to instrumentations
- add an initial test of autoloading from a config file

* adding tests for sdk::registerGlobal
in passing, remove some dead code for handling invalid booleans - config already handles this correctly

* linting

* test coverage for globals

* add opentelemetry extension to developer image and actions

* always register instrumentations via SPI

* register globals initializer for file-config sdk
allow SDK created from config file to coexist with components using globals initializer

* linting

* remove globals init function

* fix phan warning

* simplify hook manager
- drop storage from hook manager: can't guarantee that something else, eg swoole, won't modify storage
- drop context from hook manager: we must lazy-load globals via initializers, because not all instrumentations use SPI (although that may change in future)
- default hook manager to enabled

* add todo to deprecate Registry in future

* autoload instrumentations without config
if no config provided, still try to load them. wrap registration in a try/catch/log

* fixing phan ref, update doc

* remove phan suppress

* fix example

* bump SPI to 0.2.1

* adding late-binding tracer+provider
per review from Nevay, this will allow instrumentations to get things from Globals as late as possible

* adding late binding logger and meter providers

* more late binding test coverage

* tidy

* dont use CoversMethod yet
not available in phpunit 10, so comment out and leave a todo

* kitchen sink, remove unused var

* instrumentation config as a map, per config file spec

* adding general config

* move general config into sdk

* test config caching

* move general instrumentation config to api

* avoid bad version of sebastian/exporter

* bump config yaml files to 0.3

* fix tests

* disable hook manager during file-based init

* cleanup

* support multiple config files in autoloader

* move hook manager enable/disable out of extension hook manager
The most obvious place to do this is in a class named HookManager, which _was_ an
interface for hook managers. Rename existing HookManager to HookManagerInterface,
then re-purpose HookManager for globally enabling/disabling hook managers as well
as determining the disabled status.

* update spi config

---------

Co-authored-by: Tobias Bachert <[email protected]>
  • Loading branch information
brettmc and Nevay authored Jul 22, 2024
1 parent 30e3039 commit c9aead8
Show file tree
Hide file tree
Showing 10 changed files with 294 additions and 25 deletions.
54 changes: 54 additions & 0 deletions ComponentProvider/Instrumentation/General/HttpConfigProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Config\SDK\ComponentProvider\Instrumentation\General;

use OpenTelemetry\API\Instrumentation\AutoInstrumentation\GeneralInstrumentationConfiguration;
use OpenTelemetry\API\Instrumentation\Configuration\General\HttpConfig;
use OpenTelemetry\Config\SDK\Configuration\ComponentProvider;
use OpenTelemetry\Config\SDK\Configuration\ComponentProviderRegistry;
use OpenTelemetry\Config\SDK\Configuration\Context;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;

/**
* @implements ComponentProvider<GeneralInstrumentationConfiguration>
*/
class HttpConfigProvider implements ComponentProvider
{

public function createPlugin(array $properties, Context $context): GeneralInstrumentationConfiguration
{
return new HttpConfig($properties);
}

public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinition
{
$node = new ArrayNodeDefinition('http');
$node
->children()
->append($this->capturedHeaders('client'))
->append($this->capturedHeaders('server'))
->end()
;

return $node;
}

private function capturedHeaders(string $name): ArrayNodeDefinition
{
$node = new ArrayNodeDefinition($name);
$node
->children()
->arrayNode('request_captured_headers')
->scalarPrototype()->end()
->end()
->arrayNode('response_captured_headers')
->scalarPrototype()->end()
->end()
->end()
;

return $node;
}
}
42 changes: 42 additions & 0 deletions ComponentProvider/Instrumentation/General/PeerConfigProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Config\SDK\ComponentProvider\Instrumentation\General;

use OpenTelemetry\API\Instrumentation\AutoInstrumentation\GeneralInstrumentationConfiguration;
use OpenTelemetry\API\Instrumentation\Configuration\General\PeerConfig;
use OpenTelemetry\Config\SDK\Configuration\ComponentProvider;
use OpenTelemetry\Config\SDK\Configuration\ComponentProviderRegistry;
use OpenTelemetry\Config\SDK\Configuration\Context;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;

/**
* @implements ComponentProvider<GeneralInstrumentationConfiguration>
*/
class PeerConfigProvider implements ComponentProvider
{
public function createPlugin(array $properties, Context $context): GeneralInstrumentationConfiguration
{
return new PeerConfig($properties);
}

public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinition
{
$node = new ArrayNodeDefinition('peer');
$node
->children()
->arrayNode('service_mapping')
->arrayPrototype()
->children()
->scalarNode('peer')->end()
->scalarNode('service')->end()
->end()
->end()
->end()
->end()
;

return $node;
}
}
64 changes: 64 additions & 0 deletions ComponentProvider/InstrumentationConfigurationRegistry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Config\SDK\ComponentProvider;

use OpenTelemetry\API\Instrumentation\AutoInstrumentation\ConfigurationRegistry;
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\GeneralInstrumentationConfiguration;
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\InstrumentationConfiguration;
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 Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;

/**
* @internal
* @todo In a future release, when all instrumentations use SPI (and not autoload.files), this could be moved into {@see OpenTelemetrySdk}>
* @implements ComponentProvider<ConfigurationRegistry>
*/
class InstrumentationConfigurationRegistry implements ComponentProvider
{
/**
* @param array{
* instrumentation: array{
* php: list<ComponentPlugin<InstrumentationConfiguration>>,
* general: list<ComponentPlugin<InstrumentationConfiguration>>
* }
* } $properties
*/
public function createPlugin(array $properties, Context $context): ConfigurationRegistry
{
$configurationRegistry = new ConfigurationRegistry();
/** @phpstan-ignore-next-line */
foreach ($properties['instrumentation']['php'] ?? [] as $configuration) {
$configurationRegistry->add($configuration->create($context));
}
/** @phpstan-ignore-next-line */
foreach ($properties['instrumentation']['general'] ?? [] as $configuration) {
$configurationRegistry->add($configuration->create($context));
}

return $configurationRegistry;
}

public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinition
{
$root = new ArrayNodeDefinition('open_telemetry');
$root
->ignoreExtraKeys()
->children()
->arrayNode('instrumentation')
->ignoreExtraKeys()
->children()
->append($registry->componentList('php', InstrumentationConfiguration::class))
->append($registry->componentList('general', GeneralInstrumentationConfiguration::class))
->end()
->end()
->end()
;

return $root;
}
}
10 changes: 5 additions & 5 deletions ComponentProvider/OpenTelemetrySdk.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ final class OpenTelemetrySdk implements ComponentProvider

/**
* @param array{
* file_format: '0.1',
* file_format: '0.3',
* disabled: bool,
* resource: array{
* attributes: array,
Expand Down Expand Up @@ -263,7 +263,7 @@ public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinit
->isRequired()
->example('0.1')
->validate()->always(Validation::ensureString())->end()
->validate()->ifNotInArray(['0.1'])->thenInvalid('unsupported version')->end()
->validate()->ifNotInArray(['0.3'])->thenInvalid('unsupported version')->end()
->end()
->booleanNode('disabled')->defaultFalse()->end()
->append($this->getResourceConfig())
Expand Down Expand Up @@ -323,7 +323,7 @@ private function getTracerProviderConfig(ComponentProviderRegistry $registry): A
->end()
->end()
->append($registry->component('sampler', SamplerInterface::class))
->append($registry->componentList('processors', SpanProcessorInterface::class))
->append($registry->componentArrayList('processors', SpanProcessorInterface::class))
->end()
;

Expand Down Expand Up @@ -374,7 +374,7 @@ private function getMeterProviderConfig(ComponentProviderRegistry $registry): Ar
->end()
->end()
->end()
->append($registry->componentList('readers', MetricReaderInterface::class))
->append($registry->componentArrayList('readers', MetricReaderInterface::class))
->end()
;

Expand All @@ -394,7 +394,7 @@ private function getLoggerProviderConfig(ComponentProviderRegistry $registry): A
->integerNode('attribute_count_limit')->min(0)->defaultNull()->end()
->end()
->end()
->append($registry->componentList('processors', LogRecordProcessorInterface::class))
->append($registry->componentArrayList('processors', LogRecordProcessorInterface::class))
->end()
;

Expand Down
1 change: 1 addition & 0 deletions Configuration/ComponentProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public function createPlugin(array $properties, Context $context): mixed;
*
* @see ComponentProviderRegistry::component()
* @see ComponentProviderRegistry::componentList()
* @see ComponentProviderRegistry::componentArrayList()
* @see ComponentProviderRegistry::componentNames()
*/
public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinition;
Expand Down
23 changes: 21 additions & 2 deletions Configuration/ComponentProviderRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,26 @@ public function component(string $name, string $type): NodeDefinition;
*
* ```
* $name:
* provider1:
* property: value
* anotherProperty: value
* provider2:
* property: value
* anotherProperty: value
* ```
*
* @param string $name name of configuration node
* @param string $type type of the component plugin
*/
public function componentList(string $name, string $type): ArrayNodeDefinition;

/**
* Creates a node to specify a list of component plugins represented as an array.
*
* `$name: list<ComponentPlugin<$type>>`
*
* ```
* $name:
* - provider1:
* property: value
* anotherProperty: value
Expand All @@ -48,8 +68,7 @@ public function component(string $name, string $type): NodeDefinition;
* @param string $name name of configuration node
* @param string $type type of the component plugin
*/
public function componentList(string $name, string $type): ArrayNodeDefinition;

public function componentArrayList(string $name, string $type): ArrayNodeDefinition;
/**
* Creates a node to specify a list of component plugin names.
*
Expand Down
5 changes: 3 additions & 2 deletions Configuration/ConfigurationFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use function class_exists;
use Exception;
use function getcwd;
use function is_file;
use OpenTelemetry\Config\SDK\Configuration\Environment\EnvReader;
use OpenTelemetry\Config\SDK\Configuration\Environment\EnvResourceChecker;
Expand Down Expand Up @@ -98,7 +99,7 @@ public function parseFile(
}

$loader = new ConfigurationLoader($resources);
$locator = new FileLocator();
$locator = new FileLocator(getcwd());
$fileLoader = new DelegatingLoader(new LoaderResolver([
new YamlSymfonyFileLoader($loader, $locator),
new YamlExtensionFileLoader($loader, $locator),
Expand All @@ -115,7 +116,7 @@ public function parseFile(
class_exists(VarExporter::class)
? sprintf('<?php return %s;', VarExporter::export($configuration))
: sprintf('<?php return unserialize(%s);', var_export(serialize($configuration), true)),
$resources->toArray() //@todo $resources possible null
$resources->toArray()
);

return $configuration;
Expand Down
50 changes: 36 additions & 14 deletions Configuration/Internal/ComponentProviderRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ public function component(string $name, string $type): NodeDefinition
}

public function componentList(string $name, string $type): ArrayNodeDefinition
{
$node = new ArrayNodeDefinition($name);
$this->applyToArrayNode($node, $type, true);

return $node;
}

public function componentArrayList(string $name, string $type): ArrayNodeDefinition
{
$node = new ArrayNodeDefinition($name);
$this->applyToArrayNode($node->arrayPrototype(), $type);
Expand Down Expand Up @@ -107,7 +115,7 @@ public function componentNames(string $name, string $type): ArrayNodeDefinition
return $node;
}

private function applyToArrayNode(ArrayNodeDefinition $node, string $type): void
private function applyToArrayNode(ArrayNodeDefinition $node, string $type, bool $forceArray = false): void
{
$node->info(sprintf('Component "%s"', $type));
$node->performNoDeepMerging();
Expand All @@ -122,21 +130,35 @@ private function applyToArrayNode(ArrayNodeDefinition $node, string $type): void
}
}

$node->validate()->always(function (array $value) use ($type): ComponentPlugin {
if (count($value) !== 1) {
throw new InvalidArgumentException(sprintf(
'Component "%s" must have exactly one element defined, got %s',
$type,
implode(', ', array_map(json_encode(...), array_keys($value)) ?: ['none'])
));
}
if ($forceArray) {
// if the config was a map rather than an array, force it back to an array
$node->validate()->always(function (array $value) use ($type): array {
$validated = [];
foreach ($value as $name => $v) {
$provider = $this->providers[$type][$name];
$this->resources?->addClassResource($provider);
$validated[] = new ComponentPlugin($v, $this->providers[$type][$name]);
}

return $validated;
});
} else {
$node->validate()->always(function (array $value) use ($type): ComponentPlugin {
if (count($value) !== 1) {
throw new InvalidArgumentException(sprintf(
'Component "%s" must have exactly one element defined, got %s',
$type,
implode(', ', array_map(json_encode(...), array_keys($value)) ?: ['none'])
));
}

$name = array_key_first($value);
$provider = $this->providers[$type][$name];
$this->resources?->addClassResource($provider);
$name = array_key_first($value);
$provider = $this->providers[$type][$name];
$this->resources?->addClassResource($provider);

return new ComponentPlugin($value[$name], $this->providers[$type][$name]);
});
return new ComponentPlugin($value[$name], $this->providers[$type][$name]);
});
}
}

/**
Expand Down
Loading

0 comments on commit c9aead8

Please sign in to comment.