Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

auto-instrumentation registration #1304

Open
wants to merge 36 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
be76d19
[WIP] Add instrumentation configuration
Nevay Apr 24, 2024
c178431
add autoloading for auto-instrumentations using SPI
brettmc May 6, 2024
31f93c0
allow autoloading and non-autoloading to work
brettmc May 6, 2024
ef343e1
fix attribute
brettmc May 6, 2024
ba765d9
experimental config file
brettmc May 6, 2024
b771c57
fixing invalid dependencies
brettmc May 7, 2024
ba994d5
dont register hook manager globally or in sdk
brettmc May 7, 2024
35feeff
remove unused function, psalm ignore missing extension function
brettmc May 7, 2024
43fc772
possibly fixing type-hint
brettmc May 7, 2024
76ce98f
load config files relative to cwd
brettmc May 7, 2024
73f83e0
fixing hook manager enable/disable + psalm complaints
brettmc May 8, 2024
7e57997
fixing 8.1 psalm error
brettmc May 8, 2024
f29b713
use context to pass providers to instrumentations
brettmc May 8, 2024
20cd7ef
adding tests for sdk::registerGlobal
brettmc May 8, 2024
c8373a3
linting
brettmc May 8, 2024
9ba45be
test coverage for globals
brettmc May 8, 2024
a4d9046
add opentelemetry extension to developer image and actions
brettmc May 9, 2024
e704719
always register instrumentations via SPI
brettmc May 9, 2024
d94d4cc
register globals initializer for file-config sdk
brettmc May 10, 2024
04aa26a
linting
brettmc May 10, 2024
7262680
remove globals init function
brettmc May 10, 2024
579713a
fix phan warning
brettmc May 10, 2024
8fd3724
simplify hook manager
brettmc May 13, 2024
e6f85fe
add todo to deprecate Registry in future
brettmc May 13, 2024
d032942
autoload instrumentations without config
brettmc May 13, 2024
82d62db
fixing phan ref, update doc
brettmc May 14, 2024
0e5ffac
remove phan suppress
brettmc May 14, 2024
de4262b
fix example
brettmc May 14, 2024
be50116
bump SPI to 0.2.1
brettmc May 14, 2024
5798a40
Merge branch 'main' into auto-instrumentation-registration
brettmc May 16, 2024
7acacb6
adding late-binding tracer+provider
brettmc May 17, 2024
77653d4
adding late binding logger and meter providers
brettmc May 18, 2024
c0cd4ed
more late binding test coverage
brettmc May 18, 2024
fe5fe3e
tidy
brettmc May 18, 2024
9459cb0
Merge branch 'main' into auto-instrumentation-registration
brettmc May 21, 2024
1c0e1b5
dont use CoversMethod yet
brettmc May 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 9 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,15 @@
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Logs\\LogRecordExporterConsole",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Logs\\LogRecordExporterOtlp",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Logs\\LogRecordProcessorBatch",
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Logs\\LogRecordProcessorSimple"
"OpenTelemetry\\Config\\SDK\\ComponentProvider\\Logs\\LogRecordProcessorSimple",

"OpenTelemetry\\Example\\ExampleConfigProvider"
],
"OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManager": [
"OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\ExtensionHookManager"
],
"OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\Instrumentation": [
"OpenTelemetry\\Example\\ExampleInstrumentation"
]
}
}
Expand Down
38 changes: 38 additions & 0 deletions examples/instrumentation/configure_instrumentation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace _;

use Nevay\SPI\ServiceLoader;
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\ExtensionHookManager;
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\Instrumentation;
use OpenTelemetry\Config\SDK\Configuration;
use OpenTelemetry\Config\SDK\Configuration\Context;
use OpenTelemetry\Example\Example;
use const PHP_EOL;

/**
* This example uses SPI (see root composer.json extra.spi) to configure an example auto-instrumentation from a YAML file
*/
// EXAMPLE_INSTRUMENTATION_SPAN_NAME=test1234 php examples/instrumentation/configure_instrumentation.php

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

$sdk = Configuration::parseFile(__DIR__ . '/otel-sdk.yaml')->create(new Context())->setAutoShutdown(true)->setHookManager(new ExtensionHookManager())->build();
$configuration = \OpenTelemetry\Config\SDK\Instrumentation::parseFile(__DIR__ . '/otel-instrumentation.yaml')->create();

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

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

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

try {
echo (new Example())->test(), PHP_EOL;
} finally {
$scope->detach();
}
20 changes: 20 additions & 0 deletions examples/instrumentation/configure_instrumentation_global.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace _;

use OpenTelemetry\Example\Example;
use const PHP_EOL;

/**
* This example uses SPI (see root composer.json extra.spi) to configure an example auto-instrumentation from a YAML file
*/
// EXAMPLE_INSTRUMENTATION_SPAN_NAME=test1234 php examples/instrumentation/configure_instrumentation.php
putenv('OTEL_PHP_AUTOLOAD_ENABLED=true');
putenv(sprintf('OTEL_PHP_SDK_CONFIG_FILE=%s/%s', __DIR__, 'otel-sdk.yaml'));
putenv(sprintf('OTEL_PHP_INSTRUMENTATION_CONFIG_FILE=%s/%s', __DIR__, 'otel-instrumentation.yaml'));

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

echo (new Example())->test(), PHP_EOL;
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: {}
14 changes: 14 additions & 0 deletions examples/src/Example.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Example;

final class Example
{

public function test(): int
{
return 42;
}
}
17 changes: 17 additions & 0 deletions examples/src/ExampleConfig.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?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,
) {
}
}
50 changes: 50 additions & 0 deletions examples/src/ExampleConfigProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?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
{

/**
* @psalm-suppress MoreSpecificImplementedParamType
* @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'],
);
}

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

Check failure on line 44 in examples/src/ExampleConfigProvider.php

View workflow job for this annotation

GitHub Actions / php (8.1, false)

PossiblyNullReference

examples/src/ExampleConfigProvider.php:44:15: PossiblyNullReference: Cannot call method end on possibly null value (see https://psalm.dev/083)
->canBeDisabled()
;

return $root;
}
}
53 changes: 53 additions & 0 deletions examples/src/ExampleInstrumentation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Example;

use Exception;
use OpenTelemetry\API\Globals;
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 ? $context->tracerProvider->getTracer('example-instrumentation') : Globals::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();
}
);
}
}
17 changes: 15 additions & 2 deletions src/API/Globals.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use function assert;
use Closure;
use const E_USER_WARNING;
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\HookManager;
use OpenTelemetry\API\Instrumentation\Configurator;
use OpenTelemetry\API\Instrumentation\ContextKeys;
use OpenTelemetry\API\Logs\EventLoggerProviderInterface;
Expand Down Expand Up @@ -34,6 +35,7 @@ public function __construct(
private readonly LoggerProviderInterface $loggerProvider,
private readonly EventLoggerProviderInterface $eventLoggerProvider,
private readonly TextMapPropagatorInterface $propagator,
private readonly HookManager $hookManager,
brettmc marked this conversation as resolved.
Show resolved Hide resolved
) {
}

Expand Down Expand Up @@ -62,6 +64,11 @@ public static function eventLoggerProvider(): EventLoggerProviderInterface
return Context::getCurrent()->get(ContextKeys::eventLoggerProvider()) ?? self::globals()->eventLoggerProvider;
}

public static function hookManager(): HookManager
{
return Context::getCurrent()->get(ContextKeys::hookManager()) ?? self::globals()->hookManager;
}

/**
* @param Closure(Configurator): Configurator $initializer
*
Expand Down Expand Up @@ -103,10 +110,16 @@ private static function globals(): self
$propagator = $context->get(ContextKeys::propagator());
$loggerProvider = $context->get(ContextKeys::loggerProvider());
$eventLoggerProvider = $context->get(ContextKeys::eventLoggerProvider());
$hookManager = $context->get(ContextKeys::hookManager());

assert(isset($tracerProvider, $meterProvider, $loggerProvider, $eventLoggerProvider, $propagator));
assert(isset($tracerProvider, $meterProvider, $loggerProvider, $eventLoggerProvider, $propagator, $hookManager));

return self::$globals = new self($tracerProvider, $meterProvider, $loggerProvider, $eventLoggerProvider, $propagator);
return self::$globals = new self($tracerProvider, $meterProvider, $loggerProvider, $eventLoggerProvider, $propagator, $hookManager);
}

public static function init(): void
{
self::globals();
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?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,74 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\API\Instrumentation\AutoInstrumentation;

use function assert;
use Closure;
use function extension_loaded;
use OpenTelemetry\Context\Context;
use OpenTelemetry\Context\ContextKeyInterface;
use ReflectionFunction;

// #[Nevay\SPI\ServiceProviderDependency\ExtensionDependency('opentelemetry', '^1.0')]
brettmc marked this conversation as resolved.
Show resolved Hide resolved
final class ExtensionHookManager implements HookManager
{

private readonly ContextKeyInterface $contextKey;

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

/**
* @phan-suppress PhanUndeclaredFunction
*/
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));

Check failure on line 33 in src/API/Instrumentation/AutoInstrumentation/ExtensionHookManager.php

View workflow job for this annotation

GitHub Actions / php (8.2, false)

UndefinedFunction

src/API/Instrumentation/AutoInstrumentation/ExtensionHookManager.php:33:9: UndefinedFunction: Function OpenTelemetry\Instrumentation\hook does not exist (see https://psalm.dev/021)

Check failure on line 33 in src/API/Instrumentation/AutoInstrumentation/ExtensionHookManager.php

View workflow job for this annotation

GitHub Actions / php (8.3, false)

UndefinedFunction

src/API/Instrumentation/AutoInstrumentation/ExtensionHookManager.php:33:9: UndefinedFunction: Function OpenTelemetry\Instrumentation\hook does not exist (see https://psalm.dev/021)

Check failure on line 33 in src/API/Instrumentation/AutoInstrumentation/ExtensionHookManager.php

View workflow job for this annotation

GitHub Actions / php (8.1, false)

UndefinedFunction

src/API/Instrumentation/AutoInstrumentation/ExtensionHookManager.php:33:9: UndefinedFunction: Function OpenTelemetry\Instrumentation\hook does not exist (see https://psalm.dev/021)
}

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 {
brettmc marked this conversation as resolved.
Show resolved Hide resolved
/*if (!Context::getCurrent()->get($contextKey)) {
brettmc marked this conversation as resolved.
Show resolved Hide resolved
return;
}*/

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

return static function (mixed ...$args) use ($closure, $contextKey): mixed {
/*if (!Context::getCurrent()->get($contextKey)) {
return null;
brettmc marked this conversation as resolved.
Show resolved Hide resolved
}*/

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