Skip to content

Commit

Permalink
[WIP] Add instrumentation configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
Nevay committed Apr 24, 2024
1 parent 5019361 commit 2ecf4b2
Show file tree
Hide file tree
Showing 13 changed files with 357 additions and 0 deletions.
99 changes: 99 additions & 0 deletions examples/instrumentation/configure_instrumentation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php declare(strict_types=1);
namespace _;

use Nevay\SPI\ServiceLoader;
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\ConfigurationRegistry;
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\ExtensionHookManager;
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\HookManager;
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\Instrumentation;
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\InstrumentationConfiguration;
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\NoopHookManager;
use OpenTelemetry\Config\SDK\Configuration;
use OpenTelemetry\Config\SDK\Configuration\ComponentPlugin;
use OpenTelemetry\Config\SDK\Configuration\ComponentProvider;
use OpenTelemetry\Config\SDK\Configuration\ComponentProviderRegistry;
use OpenTelemetry\Config\SDK\Configuration\ConfigurationFactory;
use OpenTelemetry\Config\SDK\Configuration\Context;
use OpenTelemetry\Config\SDK\Configuration\Environment\EnvSourceReader;
use OpenTelemetry\Config\SDK\Configuration\Environment\PhpIniEnvSource;
use OpenTelemetry\Config\SDK\Configuration\Environment\ServerEnvSource;
use OpenTelemetry\Example\Example;
use OpenTelemetry\Example\ExampleConfigProvider;
use OpenTelemetry\Example\ExampleInstrumentation;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use const PHP_EOL;

// EXAMPLE_INSTRUMENTATION_SPAN_NAME=test1234 php examples/instrumentation/configure_instrumentation.php

require __DIR__ . '/../../vendor/autoload.php';

ServiceLoader::register(HookManager::class, ExtensionHookManager::class);
ServiceLoader::register(ComponentProvider::class, ExampleConfigProvider::class);
ServiceLoader::register(Instrumentation::class, ExampleInstrumentation::class);

$sdk = Configuration::parseFile(__DIR__ . '/otel-sdk.yaml')->create(new Context())->setAutoShutdown(true)->build();
$configuration = parseInstrumentationConfig(__DIR__ . '/otel-instrumentation.yaml')->create(new Context());

$hookManager = hookManager();
$context = new Context($sdk->getTracerProvider());
$storage = \OpenTelemetry\Context\Context::storage();

foreach (ServiceLoader::load(Instrumentation::class) as $instrumentation) {
$instrumentation->register($hookManager, $context, $configuration, $storage);
}

$scope = $storage->attach($hookManager->enable($storage->current()));

try {
echo (new Example())->test(), PHP_EOL;
} finally {
$scope->detach();
}


function hookManager(): HookManager {
foreach (ServiceLoader::load(HookManager::class) as $hookManager) {
return $hookManager;
}

return new NoopHookManager();
}

function parseInstrumentationConfig(string $file): ComponentPlugin {
// TODO Include in SDK config?
return (new ConfigurationFactory(
ServiceLoader::load(ComponentProvider::class),
new class implements ComponentProvider {

/**
* @param array{
* config: list<ComponentPlugin<InstrumentationConfiguration>>,
* } $properties
*/
public function createPlugin(array $properties, Context $context): ConfigurationRegistry {
$configurationRegistry = new ConfigurationRegistry();
foreach ($properties['config'] as $configuration) {
$configurationRegistry->add($configuration->create($context));
}

return $configurationRegistry;
}

public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinition {
$root = new ArrayNodeDefinition('instrumentation');
$root
->children()
// TODO add disabled_instrumentations arrayNode to allow disabling specific instrumentation classes?
->append($registry->componentList('config', InstrumentationConfiguration::class))
->end()
;

return $root;
}
},
new EnvSourceReader([
new ServerEnvSource(),
new PhpIniEnvSource(),
]),
))->parseFile($file);
}
3 changes: 3 additions & 0 deletions examples/instrumentation/otel-instrumentation.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
config:
- example_instrumentation:
span_name: ${EXAMPLE_INSTRUMENTATION_SPAN_NAME:-example span}
7 changes: 7 additions & 0 deletions examples/instrumentation/otel-sdk.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
file_format: '0.1'

tracer_provider:
processors:
- simple:
exporter:
console: {}
9 changes: 9 additions & 0 deletions examples/src/Example.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php declare(strict_types=1);
namespace OpenTelemetry\Example;

final class Example {

public function test(): int {
return 42;
}
}
12 changes: 12 additions & 0 deletions examples/src/ExampleConfig.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php declare(strict_types=1);
namespace OpenTelemetry\Example;

use OpenTelemetry\API\Instrumentation\AutoInstrumentation\InstrumentationConfiguration;

final class ExampleConfig implements InstrumentationConfiguration {

public function __construct(
public readonly string $spanName,
public readonly bool $enabled = true,
) {}
}
40 changes: 40 additions & 0 deletions examples/src/ExampleConfigProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php declare(strict_types=1);
namespace OpenTelemetry\Example;

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

/**
* @implements ComponentProvider<InstrumentationConfiguration>
*/
final class ExampleConfigProvider implements ComponentProvider {

/**
* @param array{
* span_name: string,
* enabled: bool,
* } $properties
*/
public function createPlugin(array $properties, Context $context): InstrumentationConfiguration {
return new ExampleConfig(
spanName: $properties['span_name'],
enabled: $properties['enabled'],
);
}

public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinition {
$root = new ArrayNodeDefinition('example_instrumentation');
$root
->children()
->scalarNode('span_name')->isRequired()->validate()->always(Validation::ensureString())->end()->end()
->end()
->canBeDisabled()
;

return $root;
}
}
47 changes: 47 additions & 0 deletions examples/src/ExampleInstrumentation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php declare(strict_types=1);
namespace OpenTelemetry\Example;

use Exception;
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\ConfigurationRegistry;
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\HookManager;
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\Instrumentation;
use OpenTelemetry\API\Trace\Span;
use OpenTelemetry\Config\SDK\Configuration\Context;
use OpenTelemetry\Context\ContextStorageInterface;

final class ExampleInstrumentation implements Instrumentation {

public function register(HookManager $hookManager, Context $context, ConfigurationRegistry $configuration, ContextStorageInterface $storage): void {
$config = $configuration->get(ExampleConfig::class) ?? throw new Exception('example instrumentation must be configured');
if (!$config->enabled) {
return;
}

$tracer = $context->tracerProvider->getTracer('example-instrumentation');

$hookManager->hook(
Example::class,
'test',
static function() use ($tracer, $config, $storage): void {
$context = $storage->current();

$span = $tracer
->spanBuilder($config->spanName)
->setParent($context)
->startSpan();

$storage->attach($span->storeInContext($context));
},
static function() use ($storage): void {
if (!$scope = $storage->scope()) {
return;
}

$scope->detach();

$span = Span::fromContext($scope->context());
$span->end();
}
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php declare(strict_types=1);
namespace OpenTelemetry\API\Instrumentation\AutoInstrumentation;

final class ConfigurationRegistry {

private array $configurations = [];

public function add(InstrumentationConfiguration $configuration): self {
$this->configurations[$configuration::class] = $configuration;

return $this;
}

/**
* @template C of InstrumentationConfiguration
* @param class-string<C> $id
* @return C|null
*/
public function get(string $id): ?InstrumentationConfiguration {
return $this->configurations[$id] ?? null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php declare(strict_types=1);
namespace OpenTelemetry\API\Instrumentation\AutoInstrumentation;

use Closure;
use Nevay\SPI\ServiceProviderDependency\ExtensionDependency;
use OpenTelemetry\Context\Context;
use OpenTelemetry\Context\ContextKeyInterface;
use ReflectionFunction;
use function assert;
use function extension_loaded;

#[ExtensionDependency('opentelemetry', '^1.0')]
final class ExtensionHookManager implements HookManager {

private readonly ContextKeyInterface $contextKey;

public function __construct() {
$this->contextKey = Context::createKey(self::class);
}

public function hook(?string $class, string $function, ?Closure $preHook = null, ?Closure $postHook = null): void {
assert(extension_loaded('opentelemetry'));

/** @noinspection PhpFullyQualifiedNameUsageInspection */
\OpenTelemetry\Instrumentation\hook($class, $function, $this->bindHookScope($preHook), $this->bindHookScope($postHook));
}

public function enable(Context $context): Context {
return $context->with($this->contextKey, true);
}

public function disable(Context $context): Context {
return $context->with($this->contextKey, null);
}

private function bindHookScope(?Closure $closure): ?Closure {
if (!$closure) {
return null;
}

$contextKey = $this->contextKey;
$reflection = new ReflectionFunction($closure);

// TODO Add an option flag to ext-opentelemetry `hook` that configures whether return values should be used?
if (!$reflection->getReturnType() || (string) $reflection->getReturnType() === 'void') {
return static function(mixed ...$args) use ($closure, $contextKey): void {
if (!Context::getCurrent()->get($contextKey)) {
return;
}

$closure(...$args);
};
}

return static function(mixed ...$args) use ($closure, $contextKey): mixed {
if (!Context::getCurrent()->get($contextKey)) {
return null;
}

return $closure(...$args);
};
}
}
19 changes: 19 additions & 0 deletions src/API/Instrumentation/AutoInstrumentation/HookManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php declare(strict_types=1);
namespace OpenTelemetry\API\Instrumentation\AutoInstrumentation;

use Closure;
use OpenTelemetry\Context\Context;
use Throwable;

interface HookManager {

/**
* @param Closure(object|string|null,array,string,string,string|null,int|null):void|null $preHook
* @param Closure(object|string|null,array,mixed,Throwable|null,string,string,string|null,int|null):void|null $postHook
*/
public function hook(?string $class, string $function, ?Closure $preHook = null, ?Closure $postHook = null): void;

public function enable(Context $context): Context;

public function disable(Context $context): Context;
}
10 changes: 10 additions & 0 deletions src/API/Instrumentation/AutoInstrumentation/Instrumentation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php declare(strict_types=1);
namespace OpenTelemetry\API\Instrumentation\AutoInstrumentation;

use OpenTelemetry\Config\SDK\Configuration\Context;
use OpenTelemetry\Context\ContextStorageInterface;

interface Instrumentation {

public function register(HookManager $hookManager, Context $context, ConfigurationRegistry $configuration, ContextStorageInterface $storage): void;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php declare(strict_types=1);
namespace OpenTelemetry\API\Instrumentation\AutoInstrumentation;

interface InstrumentationConfiguration {

}
20 changes: 20 additions & 0 deletions src/API/Instrumentation/AutoInstrumentation/NoopHookManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php declare(strict_types=1);
namespace OpenTelemetry\API\Instrumentation\AutoInstrumentation;

use Closure;
use OpenTelemetry\Context\Context;

final class NoopHookManager implements HookManager {

public function hook(?string $class, string $function, ?Closure $preHook = null, ?Closure $postHook = null): void {
// no-op
}

public function enable(Context $context): Context {
return $context;
}

public function disable(Context $context): Context {
return $context;
}
}

0 comments on commit 2ecf4b2

Please sign in to comment.