293 lines
11 KiB
PHP
293 lines
11 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace OpenTelemetry\SDK;
|
|
|
|
use function class_exists;
|
|
use Nevay\SPI\ServiceLoader;
|
|
use OpenTelemetry\API\Behavior\LogsMessagesTrait;
|
|
use OpenTelemetry\API\Configuration\ConfigEnv\EnvComponentLoader;
|
|
use OpenTelemetry\API\Configuration\ConfigProperties;
|
|
use OpenTelemetry\API\Globals;
|
|
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\ConfigurationRegistry;
|
|
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\Context as InstrumentationContext;
|
|
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\GeneralInstrumentationConfiguration;
|
|
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\HookManager;
|
|
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\HookManagerInterface;
|
|
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\Instrumentation;
|
|
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\InstrumentationConfiguration;
|
|
use OpenTelemetry\API\Instrumentation\AutoInstrumentation\NoopHookManager;
|
|
use OpenTelemetry\API\Instrumentation\Configurator;
|
|
use OpenTelemetry\API\Logs\LateBindingLoggerProvider;
|
|
use OpenTelemetry\API\Logs\LoggerProviderInterface;
|
|
use OpenTelemetry\API\Metrics\LateBindingMeterProvider;
|
|
use OpenTelemetry\API\Metrics\MeterProviderInterface;
|
|
use OpenTelemetry\API\Trace\LateBindingTracerProvider;
|
|
use OpenTelemetry\API\Trace\TracerProviderInterface;
|
|
use OpenTelemetry\Config\SDK\Configuration as SdkConfiguration;
|
|
use OpenTelemetry\Config\SDK\Instrumentation as SdkInstrumentation;
|
|
use OpenTelemetry\Context\Context;
|
|
use OpenTelemetry\Context\Propagation\TextMapPropagatorInterface;
|
|
use OpenTelemetry\SDK\Common\Configuration\Configuration;
|
|
use OpenTelemetry\SDK\Common\Configuration\EnvComponentLoaderRegistry;
|
|
use OpenTelemetry\SDK\Common\Configuration\EnvResolver;
|
|
use OpenTelemetry\SDK\Common\Configuration\Variables;
|
|
use OpenTelemetry\SDK\Common\Util\ShutdownHandler;
|
|
use OpenTelemetry\SDK\Logs\LoggerProviderFactory;
|
|
use OpenTelemetry\SDK\Metrics\MeterProviderFactory;
|
|
use OpenTelemetry\SDK\Propagation\LateBindingTextMapPropagator;
|
|
use OpenTelemetry\SDK\Propagation\PropagatorFactory;
|
|
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
|
|
use OpenTelemetry\SDK\Trace\AutoRootSpan;
|
|
use OpenTelemetry\SDK\Trace\ExporterFactory;
|
|
use OpenTelemetry\SDK\Trace\SamplerFactory;
|
|
use OpenTelemetry\SDK\Trace\SpanProcessorFactory;
|
|
use OpenTelemetry\SDK\Trace\TracerProviderBuilder;
|
|
use RuntimeException;
|
|
use Throwable;
|
|
|
|
/**
|
|
* @psalm-suppress RedundantCast
|
|
*/
|
|
class SdkAutoloader
|
|
{
|
|
use LogsMessagesTrait;
|
|
|
|
public static function autoload(): bool
|
|
{
|
|
if (!self::isEnabled() || self::isExcludedUrl()) {
|
|
return false;
|
|
}
|
|
if (Configuration::has(Variables::OTEL_EXPERIMENTAL_CONFIG_FILE)) {
|
|
if (!class_exists(SdkConfiguration::class)) {
|
|
throw new RuntimeException('File-based configuration requires open-telemetry/sdk-configuration');
|
|
}
|
|
Globals::registerInitializer(fn ($configurator) => self::fileBasedInitializer($configurator));
|
|
} else {
|
|
Globals::registerInitializer(fn ($configurator) => self::environmentBasedInitializer($configurator));
|
|
}
|
|
self::registerInstrumentations();
|
|
|
|
if (AutoRootSpan::isEnabled()) {
|
|
$request = AutoRootSpan::createRequest();
|
|
if ($request) {
|
|
AutoRootSpan::create($request);
|
|
AutoRootSpan::registerShutdownHandler();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @phan-suppress PhanDeprecatedClass,PhanDeprecatedFunction
|
|
*/
|
|
private static function environmentBasedInitializer(Configurator $configurator): Configurator
|
|
{
|
|
$propagator = (new PropagatorFactory())->create();
|
|
if (Sdk::isDisabled()) {
|
|
//@see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/sdk-environment-variables.md#general-sdk-configuration
|
|
return $configurator->withPropagator($propagator);
|
|
}
|
|
$emitMetrics = Configuration::getBoolean(Variables::OTEL_PHP_INTERNAL_METRICS_ENABLED);
|
|
|
|
$resource = ResourceInfoFactory::defaultResource();
|
|
$exporter = (new ExporterFactory())->create();
|
|
$meterProvider = (new MeterProviderFactory())->create($resource);
|
|
$spanProcessor = (new SpanProcessorFactory())->create($exporter, $emitMetrics ? $meterProvider : null);
|
|
$tracerProvider = (new TracerProviderBuilder())
|
|
->addSpanProcessor($spanProcessor)
|
|
->setResource($resource)
|
|
->setSampler((new SamplerFactory())->create())
|
|
->build();
|
|
|
|
$loggerProvider = (new LoggerProviderFactory())->create($emitMetrics ? $meterProvider : null, $resource);
|
|
|
|
ShutdownHandler::register($tracerProvider->shutdown(...));
|
|
ShutdownHandler::register($meterProvider->shutdown(...));
|
|
ShutdownHandler::register($loggerProvider->shutdown(...));
|
|
|
|
return $configurator
|
|
->withTracerProvider($tracerProvider)
|
|
->withMeterProvider($meterProvider)
|
|
->withLoggerProvider($loggerProvider)
|
|
->withPropagator($propagator)
|
|
;
|
|
}
|
|
|
|
/**
|
|
* @phan-suppress PhanPossiblyUndeclaredVariable,PhanDeprecatedFunction
|
|
*/
|
|
private static function fileBasedInitializer(Configurator $configurator): Configurator
|
|
{
|
|
$file = Configuration::getString(Variables::OTEL_EXPERIMENTAL_CONFIG_FILE);
|
|
$config = SdkConfiguration::parseFile($file);
|
|
|
|
//disable hook manager during SDK to avoid autoinstrumenting SDK exporters.
|
|
$scope = HookManager::disable(Context::getCurrent())->activate();
|
|
|
|
try {
|
|
$sdk = $config
|
|
->create()
|
|
->setAutoShutdown(true)
|
|
->build();
|
|
} finally {
|
|
$scope->detach();
|
|
}
|
|
|
|
return $configurator
|
|
->withTracerProvider($sdk->getTracerProvider())
|
|
->withMeterProvider($sdk->getMeterProvider())
|
|
->withLoggerProvider($sdk->getLoggerProvider())
|
|
->withPropagator($sdk->getPropagator())
|
|
;
|
|
}
|
|
|
|
/**
|
|
* Register all {@link Instrumentation} configured through SPI
|
|
* @psalm-suppress ArgumentTypeCoercion
|
|
*/
|
|
private static function registerInstrumentations(): void
|
|
{
|
|
$files = Configuration::has(Variables::OTEL_EXPERIMENTAL_CONFIG_FILE)
|
|
? Configuration::getList(Variables::OTEL_EXPERIMENTAL_CONFIG_FILE)
|
|
: [];
|
|
if (class_exists(SdkInstrumentation::class) && $files) {
|
|
$configuration = SdkInstrumentation::parseFile($files)->create();
|
|
} else {
|
|
$configuration = self::loadConfigPropertiesFromEnv();
|
|
}
|
|
$hookManager = self::getHookManager();
|
|
$tracerProvider = self::createLateBindingTracerProvider();
|
|
$meterProvider = self::createLateBindingMeterProvider();
|
|
$loggerProvider = self::createLateBindingLoggerProvider();
|
|
$propagator = self::createLateBindingTextMapPropagator();
|
|
$context = new InstrumentationContext($tracerProvider, $meterProvider, $loggerProvider, $propagator);
|
|
|
|
foreach (ServiceLoader::load(Instrumentation::class) as $instrumentation) {
|
|
/** @var Instrumentation $instrumentation */
|
|
try {
|
|
$instrumentation->register($hookManager, $configuration, $context);
|
|
} catch (Throwable $t) {
|
|
self::logError(sprintf('Unable to load instrumentation: %s', $instrumentation::class), ['exception' => $t]);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static function loadConfigPropertiesFromEnv(): ConfigProperties
|
|
{
|
|
$loaderRegistry = new EnvComponentLoaderRegistry();
|
|
foreach (ServiceLoader::load(EnvComponentLoader::class) as $loader) {
|
|
$loaderRegistry->register($loader);
|
|
}
|
|
|
|
$env = new EnvResolver();
|
|
$context = new \OpenTelemetry\API\Configuration\Context();
|
|
|
|
$configuration = new ConfigurationRegistry();
|
|
foreach ($loaderRegistry->loadAll(GeneralInstrumentationConfiguration::class, $env, $context) as $instrumentation) {
|
|
$configuration->add($instrumentation);
|
|
}
|
|
foreach ($loaderRegistry->loadAll(InstrumentationConfiguration::class, $env, $context) as $instrumentation) {
|
|
$configuration->add($instrumentation);
|
|
}
|
|
|
|
return $configuration;
|
|
}
|
|
|
|
private static function createLateBindingTracerProvider(): TracerProviderInterface
|
|
{
|
|
return new LateBindingTracerProvider(static function (): TracerProviderInterface {
|
|
$scope = Context::getRoot()->activate();
|
|
|
|
try {
|
|
return Globals::tracerProvider();
|
|
} finally {
|
|
$scope->detach();
|
|
}
|
|
});
|
|
}
|
|
|
|
private static function createLateBindingMeterProvider(): MeterProviderInterface
|
|
{
|
|
return new LateBindingMeterProvider(static function (): MeterProviderInterface {
|
|
$scope = Context::getRoot()->activate();
|
|
|
|
try {
|
|
return Globals::meterProvider();
|
|
} finally {
|
|
$scope->detach();
|
|
}
|
|
});
|
|
}
|
|
private static function createLateBindingLoggerProvider(): LoggerProviderInterface
|
|
{
|
|
return new LateBindingLoggerProvider(static function (): LoggerProviderInterface {
|
|
$scope = Context::getRoot()->activate();
|
|
|
|
try {
|
|
return Globals::loggerProvider();
|
|
} finally {
|
|
$scope->detach();
|
|
}
|
|
});
|
|
}
|
|
|
|
private static function createLateBindingTextMapPropagator(): TextMapPropagatorInterface
|
|
{
|
|
return new LateBindingTextMapPropagator(static function (): TextMapPropagatorInterface {
|
|
$scope = Context::getRoot()->activate();
|
|
|
|
try {
|
|
return Globals::propagator();
|
|
} finally {
|
|
$scope->detach();
|
|
}
|
|
});
|
|
}
|
|
|
|
private static function getHookManager(): HookManagerInterface
|
|
{
|
|
/** @var HookManagerInterface $hookManager */
|
|
foreach (ServiceLoader::load(HookManagerInterface::class) as $hookManager) {
|
|
return $hookManager;
|
|
}
|
|
|
|
return new NoopHookManager();
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
public static function isEnabled(): bool
|
|
{
|
|
return Configuration::getBoolean(Variables::OTEL_PHP_AUTOLOAD_ENABLED);
|
|
}
|
|
|
|
/**
|
|
* Test whether a request URI is set, and if it matches the excluded urls configuration option
|
|
*
|
|
* @internal
|
|
*/
|
|
public static function isExcludedUrl(): bool
|
|
{
|
|
$excludedUrls = Configuration::getList(Variables::OTEL_PHP_EXCLUDED_URLS, []);
|
|
if ($excludedUrls === []) {
|
|
return false;
|
|
}
|
|
$url = $_SERVER['REQUEST_URI'] ?? null;
|
|
if (!$url) {
|
|
return false;
|
|
}
|
|
foreach ($excludedUrls as $excludedUrl) {
|
|
if (preg_match(sprintf('|%s|', $excludedUrl), (string) $url) === 1) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
}
|