Skip to content

Commit

Permalink
refactor environment configuration (#859)
Browse files Browse the repository at this point in the history
refactoring Environment access to allow configuration from multiple sources. Initially, the sources are env ($_SERVER) and php.ini, but this should allow us to add more sources in future without changing the API.
do not modify OTEL_EXPORTER_OTLP_TRACES_PROTOCOL
allow resolvers to return mixed
  • Loading branch information
brettmc authored Nov 9, 2022
1 parent 92e7a0d commit f083b10
Show file tree
Hide file tree
Showing 43 changed files with 1,419 additions and 1,004 deletions.
14 changes: 5 additions & 9 deletions examples/traces/troubleshooting/setting_up_logging.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@

use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use OpenTelemetry\Contrib\Otlp\SpanExporter;
use OpenTelemetry\SDK\Common\Log\LoggerHolder;
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;
use OpenTelemetry\SDK\Trace\TracerProviderFactory;
use Psr\Log\LogLevel;

echo 'Starting SettingUpLogging example' . PHP_EOL;
Expand All @@ -18,13 +16,11 @@
LoggerHolder::set(
new Logger('otel-php', [new StreamHandler(STDOUT, LogLevel::DEBUG)])
);
$transport = (new \OpenTelemetry\Contrib\Grpc\GrpcTransportFactory())->create();
putenv('OTEL_EXPORTER_OTLP_ENDPOINT=http://does-not-exist/endpoint'); //invalid endpoint, export will fail
putenv('OTEL_EXPORTER_OTLP_PROTOCOL=grpc');
$factory = new TracerProviderFactory('otlp-logging-demo');
$tracerProvider = $factory->create();

$tracerProvider = new TracerProvider(
new SimpleSpanProcessor(
new SpanExporter($transport) //default endpoint unavailable, so exporting will fail
)
);
$tracer = $tracerProvider->getTracer('io.opentelemetry.contrib.php');
$span = $tracer->spanBuilder('root-span')->startSpan();
$span->end();
Expand Down
6 changes: 6 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,9 @@ parameters:
# - ./examples TODO: Uncomment this once examples are updated
excludePaths:
- tests/TraceContext/W3CTestService
ignoreErrors:
-
message: "#Call to an undefined method .*#"
paths:
- tests/Unit/SDK/Common/Configuration/Resolver/PhpIniResolverTest.php
- tests/Unit/SDK/Common/Configuration/Resolver/CompositeResolverTest.php
2 changes: 1 addition & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
backupGlobals="false"
backupGlobals="true"
backupStaticAttributes="false"
bootstrap="./tests/bootstrap.php"
cacheResult="false"
Expand Down
2 changes: 1 addition & 1 deletion src/Contrib/Otlp/Protocols.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace OpenTelemetry\Contrib\Otlp;

use OpenTelemetry\SDK\Common\Environment\KnownValues;
use OpenTelemetry\SDK\Common\Configuration\KnownValues;
use UnexpectedValueException;

class Protocols
Expand Down
81 changes: 60 additions & 21 deletions src/Contrib/Otlp/SpanExporterFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,36 @@

use OpenTelemetry\API\Common\Signal\Signals;
use OpenTelemetry\Contrib\Grpc\GrpcTransportFactory;
use OpenTelemetry\SDK\Common\Environment\EnvironmentVariables;
use OpenTelemetry\SDK\Common\Environment\KnownValues;
use OpenTelemetry\SDK\Common\Environment\Variables as Env;
use OpenTelemetry\SDK\Behavior\LogsMessagesTrait;
use OpenTelemetry\SDK\Common\Configuration\Configuration;
use OpenTelemetry\SDK\Common\Configuration\Defaults;
use OpenTelemetry\SDK\Common\Configuration\KnownValues;
use OpenTelemetry\SDK\Common\Configuration\Variables;
use OpenTelemetry\SDK\Common\Export\TransportFactoryInterface;
use OpenTelemetry\SDK\Common\Export\TransportInterface;
use OpenTelemetry\SDK\Common\Otlp\HttpEndpointResolver;
use OpenTelemetry\SDK\Trace\SpanExporterInterface;
use UnexpectedValueException;

class SpanExporterFactory
{
use LogsMessagesTrait;

private ?TransportFactoryInterface $transportFactory;

private const DEFAULT_COMPRESSION = 'none';
private const FACTORIES = [
KnownValues::VALUE_GRPC => GrpcTransportFactory::class,
KnownValues::VALUE_HTTP_PROTOBUF => OtlpHttpTransportFactory::class,
KnownValues::VALUE_HTTP_JSON => OtlpHttpTransportFactory::class,
KnownValues::VALUE_HTTP_NDJSON => OtlpHttpTransportFactory::class,
];

public function __construct(?TransportFactoryInterface $transportFactory = null)
{
$this->transportFactory = $transportFactory;
}

/**
* @psalm-suppress ArgumentTypeCoercion
*/
Expand All @@ -37,31 +51,56 @@ public function fromEnvironment(): SpanExporterInterface
*/
private function buildTransport(): TransportInterface
{
$protocol = EnvironmentVariables::has(Env::OTEL_EXPORTER_OTLP_TRACES_PROTOCOL) ?
EnvironmentVariables::getEnum(Env::OTEL_EXPORTER_OTLP_TRACES_PROTOCOL) :
EnvironmentVariables::getEnum(Env::OTEL_EXPORTER_OTLP_PROTOCOL);
$protocol = $this->getProtocol();
$contentType = Protocols::contentType($protocol);
$endpoint = $this->getEndpoint($protocol);
$headers = $this->getHeaders();
$compression = $this->getCompression();

if (!$this->transportFactory && !array_key_exists($protocol, self::FACTORIES)) {
throw new UnexpectedValueException('Unknown OTLP protocol: ' . $protocol);
}
$factoryClass = self::FACTORIES[$protocol];
$factory = $this->transportFactory ?: new $factoryClass();

return $factory->create($endpoint, $contentType, $headers, $compression);
}

$endpoint = EnvironmentVariables::has(Env::OTEL_EXPORTER_OTLP_TRACES_ENDPOINT)
? EnvironmentVariables::getString(Env::OTEL_EXPORTER_OTLP_TRACES_ENDPOINT)
: EnvironmentVariables::getString(Env::OTEL_EXPORTER_OTLP_ENDPOINT);
private function getProtocol(): string
{
return Configuration::has(Variables::OTEL_EXPORTER_OTLP_TRACES_PROTOCOL) ?
Configuration::getEnum(Variables::OTEL_EXPORTER_OTLP_TRACES_PROTOCOL) :
Configuration::getEnum(Variables::OTEL_EXPORTER_OTLP_PROTOCOL);
}

private function getEndpoint(string $protocol): string
{
if (Configuration::has(Variables::OTEL_EXPORTER_OTLP_TRACES_ENDPOINT)) {
return Configuration::getString(Variables::OTEL_EXPORTER_OTLP_TRACES_ENDPOINT);
}
$endpoint = Configuration::has(Variables::OTEL_EXPORTER_OTLP_ENDPOINT)
? Configuration::getString(Variables::OTEL_EXPORTER_OTLP_ENDPOINT)
: Defaults::OTEL_EXPORTER_OTLP_ENDPOINT;
if ($protocol === Protocols::GRPC) {
$endpoint .= OtlpUtil::method(Signals::TRACE);
} else {
$endpoint = HttpEndpointResolver::create()->resolveToString($endpoint, Signals::TRACE);
return $endpoint . OtlpUtil::method(Signals::TRACE);
}

$headers = EnvironmentVariables::has(Env::OTEL_EXPORTER_OTLP_TRACES_HEADERS) ?
EnvironmentVariables::getMap(Env::OTEL_EXPORTER_OTLP_TRACES_HEADERS) :
EnvironmentVariables::getMap(Env::OTEL_EXPORTER_OTLP_HEADERS);
$headers += OtlpUtil::getUserAgentHeader();
return HttpEndpointResolver::create()->resolveToString($endpoint, Signals::TRACE);
}

$compression = EnvironmentVariables::has(Env::OTEL_EXPORTER_OTLP_TRACES_COMPRESSION) ?
EnvironmentVariables::getEnum(Env::OTEL_EXPORTER_OTLP_TRACES_COMPRESSION) :
EnvironmentVariables::getEnum(Env::OTEL_EXPORTER_OTLP_COMPRESSION, self::DEFAULT_COMPRESSION);
private function getHeaders(): array
{
$headers = Configuration::has(Variables::OTEL_EXPORTER_OTLP_TRACES_HEADERS) ?
Configuration::getMap(Variables::OTEL_EXPORTER_OTLP_TRACES_HEADERS) :
Configuration::getMap(Variables::OTEL_EXPORTER_OTLP_HEADERS);

$factoryClass = self::FACTORIES[$protocol];
return $headers + OtlpUtil::getUserAgentHeader();
}

return (new $factoryClass())->create($endpoint, $contentType, $headers, $compression);
private function getCompression(): string
{
return Configuration::has(Variables::OTEL_EXPORTER_OTLP_TRACES_COMPRESSION) ?
Configuration::getEnum(Variables::OTEL_EXPORTER_OTLP_TRACES_COMPRESSION) :
Configuration::getEnum(Variables::OTEL_EXPORTER_OTLP_COMPRESSION, self::DEFAULT_COMPRESSION);
}
}
172 changes: 172 additions & 0 deletions src/SDK/Common/Configuration/Configuration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\SDK\Common\Configuration;

use OpenTelemetry\SDK\Common\Configuration\Parser\BooleanParser;
use OpenTelemetry\SDK\Common\Configuration\Parser\ListParser;
use OpenTelemetry\SDK\Common\Configuration\Parser\MapParser;
use OpenTelemetry\SDK\Common\Configuration\Parser\RatioParser;
use OpenTelemetry\SDK\Common\Configuration\Resolver\CompositeResolver;
use OpenTelemetry\SDK\Common\Util\ClassConstantAccessor;
use UnexpectedValueException;

/**
* Configuration can come from one or more of the following sources (from highest to lowest priority):
* - values defined in php.ini
* - environment variable ($_SERVER)
* - configuration file (todo)
*/
class Configuration
{
public static function has(string $name): bool
{
return CompositeResolver::instance()->hasVariable($name);
}

public static function getInt(string $key, int $default = null): int
{
return (int) self::validateVariableValue(
CompositeResolver::instance()->resolve(
self::validateVariableType($key, VariableTypes::INTEGER),
$default
),
FILTER_VALIDATE_INT
);
}

public static function getString(string $key, string $default = ''): string
{
return (string) self::validateVariableValue(
CompositeResolver::instance()->resolve(
self::validateVariableType($key, VariableTypes::STRING),
$default
)
);
}

public static function getBoolean(string $key, bool $default = null): bool
{
return BooleanParser::parse(
self::validateVariableValue(
CompositeResolver::instance()->resolve(
self::validateVariableType($key, VariableTypes::BOOL),
null === $default ? $default : ($default ? 'true' : 'false')
)
)
);
}

public static function getMixed(string $key, string $default = null)
{
return self::validateVariableValue(
CompositeResolver::instance()->resolve(
$key,
$default
)
);
}

public static function getMap(string $key, string $default = null): array
{
return MapParser::parse(
CompositeResolver::instance()->resolve(
self::validateVariableType($key, VariableTypes::MAP),
$default
)
);
}

public static function getList(string $key, string $default = null): array
{
return ListParser::parse(
CompositeResolver::instance()->resolve(
self::validateVariableType($key, VariableTypes::LIST),
$default
)
);
}

public static function getEnum(string $key, string $default = null): string
{
return (string) self::validateVariableValue(
CompositeResolver::instance()->resolve(
self::validateVariableType($key, VariableTypes::ENUM),
$default
)
);
}

public static function getFloat(string $key, string $default = null): float
{
return (float) self::validateVariableValue(
CompositeResolver::instance()->resolve(
self::validateVariableType($key, VariableTypes::FLOAT),
$default
),
FILTER_VALIDATE_FLOAT
);
}

public static function getRatio(string $key, float $default = null): float
{
return RatioParser::parse(
self::validateVariableValue(
CompositeResolver::instance()->resolve(
self::validateVariableType($key, VariableTypes::RATIO),
$default
)
)
);
}

public static function getKnownValues(string $variableName): ?array
{
return ClassConstantAccessor::getValue(KnownValues::class, $variableName);
}

public static function getDefault(string $variableName)
{
return ClassConstantAccessor::getValue(Defaults::class, $variableName);
}

public static function getType(string $variableName): ?string
{
return ClassConstantAccessor::getValue(ValueTypes::class, $variableName);
}

public static function isEmpty($value): bool
{
// don't use 'empty()', since '0' is not considered to be empty
return $value === null || $value === '';
}

private static function validateVariableType(string $variableName, string $type): string
{
$variableType = self::getType($variableName);

if ($variableType !== null && $variableType !== $type && $variableType !== VariableTypes::MIXED) {
throw new UnexpectedValueException(
sprintf('Variable "%s" is not supposed to be of type "%s" but type "%s"', $variableName, $type, $variableType)
);
}

return $variableName;
}

private static function validateVariableValue($value, ?int $filterType = null)
{
if ($filterType !== null && filter_var($value, $filterType) === false) {
throw new UnexpectedValueException(sprintf('Value has invalid type "%s"', gettype($value)));
}

if ($value === null || $value === '') {
throw new UnexpectedValueException(
'Variable must not be null or empty'
);
}

return $value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace OpenTelemetry\SDK\Common\Environment;
namespace OpenTelemetry\SDK\Common\Configuration;

/**
* Default values for environment variables defined by the OpenTelemetry specification and language specific variables for the PHP SDK.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

declare(strict_types=1);

namespace OpenTelemetry\SDK\Common\Environment;
namespace OpenTelemetry\SDK\Common\Configuration;

use Psr\Log\LogLevel;

/**
* "Known values" for OpenTelemetry environment variables.
* "Known values" for OpenTelemetry configurataion variables.
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md
* Notice: Values specific to the PHP SDK have been added
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace OpenTelemetry\SDK\Common\Environment\Parser;
namespace OpenTelemetry\SDK\Common\Configuration\Parser;

use InvalidArgumentException;

Expand All @@ -20,8 +20,14 @@ class BooleanParser
'0',
];

public static function parse(string $value): bool
/**
* @param string|bool $value
*/
public static function parse($value): bool
{
if (is_bool($value)) {
return $value;
}
if (in_array(strtolower($value), self::TRUTHY_VALUES)) {
return true;
}
Expand Down
Loading

0 comments on commit f083b10

Please sign in to comment.