Skip to content

Commit 2ecf4b2

Browse files
committed
[WIP] Add instrumentation configuration
1 parent 5019361 commit 2ecf4b2

13 files changed

+357
-0
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php declare(strict_types=1);
2+
namespace _;
3+
4+
use Nevay\SPI\ServiceLoader;
5+
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\ConfigurationRegistry;
6+
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\ExtensionHookManager;
7+
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\HookManager;
8+
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\Instrumentation;
9+
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\InstrumentationConfiguration;
10+
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\NoopHookManager;
11+
use OpenTelemetry\Config\SDK\Configuration;
12+
use OpenTelemetry\Config\SDK\Configuration\ComponentPlugin;
13+
use OpenTelemetry\Config\SDK\Configuration\ComponentProvider;
14+
use OpenTelemetry\Config\SDK\Configuration\ComponentProviderRegistry;
15+
use OpenTelemetry\Config\SDK\Configuration\ConfigurationFactory;
16+
use OpenTelemetry\Config\SDK\Configuration\Context;
17+
use OpenTelemetry\Config\SDK\Configuration\Environment\EnvSourceReader;
18+
use OpenTelemetry\Config\SDK\Configuration\Environment\PhpIniEnvSource;
19+
use OpenTelemetry\Config\SDK\Configuration\Environment\ServerEnvSource;
20+
use OpenTelemetry\Example\Example;
21+
use OpenTelemetry\Example\ExampleConfigProvider;
22+
use OpenTelemetry\Example\ExampleInstrumentation;
23+
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
24+
use const PHP_EOL;
25+
26+
// EXAMPLE_INSTRUMENTATION_SPAN_NAME=test1234 php examples/instrumentation/configure_instrumentation.php
27+
28+
require __DIR__ . '/../../vendor/autoload.php';
29+
30+
ServiceLoader::register(HookManager::class, ExtensionHookManager::class);
31+
ServiceLoader::register(ComponentProvider::class, ExampleConfigProvider::class);
32+
ServiceLoader::register(Instrumentation::class, ExampleInstrumentation::class);
33+
34+
$sdk = Configuration::parseFile(__DIR__ . '/otel-sdk.yaml')->create(new Context())->setAutoShutdown(true)->build();
35+
$configuration = parseInstrumentationConfig(__DIR__ . '/otel-instrumentation.yaml')->create(new Context());
36+
37+
$hookManager = hookManager();
38+
$context = new Context($sdk->getTracerProvider());
39+
$storage = \OpenTelemetry\Context\Context::storage();
40+
41+
foreach (ServiceLoader::load(Instrumentation::class) as $instrumentation) {
42+
$instrumentation->register($hookManager, $context, $configuration, $storage);
43+
}
44+
45+
$scope = $storage->attach($hookManager->enable($storage->current()));
46+
47+
try {
48+
echo (new Example())->test(), PHP_EOL;
49+
} finally {
50+
$scope->detach();
51+
}
52+
53+
54+
function hookManager(): HookManager {
55+
foreach (ServiceLoader::load(HookManager::class) as $hookManager) {
56+
return $hookManager;
57+
}
58+
59+
return new NoopHookManager();
60+
}
61+
62+
function parseInstrumentationConfig(string $file): ComponentPlugin {
63+
// TODO Include in SDK config?
64+
return (new ConfigurationFactory(
65+
ServiceLoader::load(ComponentProvider::class),
66+
new class implements ComponentProvider {
67+
68+
/**
69+
* @param array{
70+
* config: list<ComponentPlugin<InstrumentationConfiguration>>,
71+
* } $properties
72+
*/
73+
public function createPlugin(array $properties, Context $context): ConfigurationRegistry {
74+
$configurationRegistry = new ConfigurationRegistry();
75+
foreach ($properties['config'] as $configuration) {
76+
$configurationRegistry->add($configuration->create($context));
77+
}
78+
79+
return $configurationRegistry;
80+
}
81+
82+
public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinition {
83+
$root = new ArrayNodeDefinition('instrumentation');
84+
$root
85+
->children()
86+
// TODO add disabled_instrumentations arrayNode to allow disabling specific instrumentation classes?
87+
->append($registry->componentList('config', InstrumentationConfiguration::class))
88+
->end()
89+
;
90+
91+
return $root;
92+
}
93+
},
94+
new EnvSourceReader([
95+
new ServerEnvSource(),
96+
new PhpIniEnvSource(),
97+
]),
98+
))->parseFile($file);
99+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
config:
2+
- example_instrumentation:
3+
span_name: ${EXAMPLE_INSTRUMENTATION_SPAN_NAME:-example span}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
file_format: '0.1'
2+
3+
tracer_provider:
4+
processors:
5+
- simple:
6+
exporter:
7+
console: {}

examples/src/Example.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php declare(strict_types=1);
2+
namespace OpenTelemetry\Example;
3+
4+
final class Example {
5+
6+
public function test(): int {
7+
return 42;
8+
}
9+
}

examples/src/ExampleConfig.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php declare(strict_types=1);
2+
namespace OpenTelemetry\Example;
3+
4+
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\InstrumentationConfiguration;
5+
6+
final class ExampleConfig implements InstrumentationConfiguration {
7+
8+
public function __construct(
9+
public readonly string $spanName,
10+
public readonly bool $enabled = true,
11+
) {}
12+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php declare(strict_types=1);
2+
namespace OpenTelemetry\Example;
3+
4+
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\InstrumentationConfiguration;
5+
use OpenTelemetry\Config\SDK\Configuration\ComponentProvider;
6+
use OpenTelemetry\Config\SDK\Configuration\ComponentProviderRegistry;
7+
use OpenTelemetry\Config\SDK\Configuration\Context;
8+
use OpenTelemetry\Config\SDK\Configuration\Validation;
9+
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
10+
11+
/**
12+
* @implements ComponentProvider<InstrumentationConfiguration>
13+
*/
14+
final class ExampleConfigProvider implements ComponentProvider {
15+
16+
/**
17+
* @param array{
18+
* span_name: string,
19+
* enabled: bool,
20+
* } $properties
21+
*/
22+
public function createPlugin(array $properties, Context $context): InstrumentationConfiguration {
23+
return new ExampleConfig(
24+
spanName: $properties['span_name'],
25+
enabled: $properties['enabled'],
26+
);
27+
}
28+
29+
public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinition {
30+
$root = new ArrayNodeDefinition('example_instrumentation');
31+
$root
32+
->children()
33+
->scalarNode('span_name')->isRequired()->validate()->always(Validation::ensureString())->end()->end()
34+
->end()
35+
->canBeDisabled()
36+
;
37+
38+
return $root;
39+
}
40+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php declare(strict_types=1);
2+
namespace OpenTelemetry\Example;
3+
4+
use Exception;
5+
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\ConfigurationRegistry;
6+
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\HookManager;
7+
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\Instrumentation;
8+
use OpenTelemetry\API\Trace\Span;
9+
use OpenTelemetry\Config\SDK\Configuration\Context;
10+
use OpenTelemetry\Context\ContextStorageInterface;
11+
12+
final class ExampleInstrumentation implements Instrumentation {
13+
14+
public function register(HookManager $hookManager, Context $context, ConfigurationRegistry $configuration, ContextStorageInterface $storage): void {
15+
$config = $configuration->get(ExampleConfig::class) ?? throw new Exception('example instrumentation must be configured');
16+
if (!$config->enabled) {
17+
return;
18+
}
19+
20+
$tracer = $context->tracerProvider->getTracer('example-instrumentation');
21+
22+
$hookManager->hook(
23+
Example::class,
24+
'test',
25+
static function() use ($tracer, $config, $storage): void {
26+
$context = $storage->current();
27+
28+
$span = $tracer
29+
->spanBuilder($config->spanName)
30+
->setParent($context)
31+
->startSpan();
32+
33+
$storage->attach($span->storeInContext($context));
34+
},
35+
static function() use ($storage): void {
36+
if (!$scope = $storage->scope()) {
37+
return;
38+
}
39+
40+
$scope->detach();
41+
42+
$span = Span::fromContext($scope->context());
43+
$span->end();
44+
}
45+
);
46+
}
47+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php declare(strict_types=1);
2+
namespace OpenTelemetry\API\Instrumentation\AutoInstrumentation;
3+
4+
final class ConfigurationRegistry {
5+
6+
private array $configurations = [];
7+
8+
public function add(InstrumentationConfiguration $configuration): self {
9+
$this->configurations[$configuration::class] = $configuration;
10+
11+
return $this;
12+
}
13+
14+
/**
15+
* @template C of InstrumentationConfiguration
16+
* @param class-string<C> $id
17+
* @return C|null
18+
*/
19+
public function get(string $id): ?InstrumentationConfiguration {
20+
return $this->configurations[$id] ?? null;
21+
}
22+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php declare(strict_types=1);
2+
namespace OpenTelemetry\API\Instrumentation\AutoInstrumentation;
3+
4+
use Closure;
5+
use Nevay\SPI\ServiceProviderDependency\ExtensionDependency;
6+
use OpenTelemetry\Context\Context;
7+
use OpenTelemetry\Context\ContextKeyInterface;
8+
use ReflectionFunction;
9+
use function assert;
10+
use function extension_loaded;
11+
12+
#[ExtensionDependency('opentelemetry', '^1.0')]
13+
final class ExtensionHookManager implements HookManager {
14+
15+
private readonly ContextKeyInterface $contextKey;
16+
17+
public function __construct() {
18+
$this->contextKey = Context::createKey(self::class);
19+
}
20+
21+
public function hook(?string $class, string $function, ?Closure $preHook = null, ?Closure $postHook = null): void {
22+
assert(extension_loaded('opentelemetry'));
23+
24+
/** @noinspection PhpFullyQualifiedNameUsageInspection */
25+
\OpenTelemetry\Instrumentation\hook($class, $function, $this->bindHookScope($preHook), $this->bindHookScope($postHook));
26+
}
27+
28+
public function enable(Context $context): Context {
29+
return $context->with($this->contextKey, true);
30+
}
31+
32+
public function disable(Context $context): Context {
33+
return $context->with($this->contextKey, null);
34+
}
35+
36+
private function bindHookScope(?Closure $closure): ?Closure {
37+
if (!$closure) {
38+
return null;
39+
}
40+
41+
$contextKey = $this->contextKey;
42+
$reflection = new ReflectionFunction($closure);
43+
44+
// TODO Add an option flag to ext-opentelemetry `hook` that configures whether return values should be used?
45+
if (!$reflection->getReturnType() || (string) $reflection->getReturnType() === 'void') {
46+
return static function(mixed ...$args) use ($closure, $contextKey): void {
47+
if (!Context::getCurrent()->get($contextKey)) {
48+
return;
49+
}
50+
51+
$closure(...$args);
52+
};
53+
}
54+
55+
return static function(mixed ...$args) use ($closure, $contextKey): mixed {
56+
if (!Context::getCurrent()->get($contextKey)) {
57+
return null;
58+
}
59+
60+
return $closure(...$args);
61+
};
62+
}
63+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php declare(strict_types=1);
2+
namespace OpenTelemetry\API\Instrumentation\AutoInstrumentation;
3+
4+
use Closure;
5+
use OpenTelemetry\Context\Context;
6+
use Throwable;
7+
8+
interface HookManager {
9+
10+
/**
11+
* @param Closure(object|string|null,array,string,string,string|null,int|null):void|null $preHook
12+
* @param Closure(object|string|null,array,mixed,Throwable|null,string,string,string|null,int|null):void|null $postHook
13+
*/
14+
public function hook(?string $class, string $function, ?Closure $preHook = null, ?Closure $postHook = null): void;
15+
16+
public function enable(Context $context): Context;
17+
18+
public function disable(Context $context): Context;
19+
}

0 commit comments

Comments
 (0)