Skip to content

Commit

Permalink
Refactor Environment Variable Handling (#561)
Browse files Browse the repository at this point in the history
* Move EnvironmentVariablesTrait

* Add env var constants

* Move EnvironmentVariablesTrait

* Set default to empty string

* Add ClassConstantAccessor

* Use Env Var constants

* Fix naming

* Add Env Var parsers

* Fix boolean values

* Add Resolver

* Fix covers annotation

* Add Accessor

* Allow unknown env vars

* Add test for empty var

* Filter correct types

* Allow empty lists and maps

* Add 'none' to known compressions

* Use inline constants

* Use Env Accessor

* Fix CS

* Make TraceIdRatioBasedSampler construction safe

* Supress wrong Psalm error

* Remove redundant checks

* Remove redundant checks

* Add additional getters to trait

* Remove redundant metadadata handling

* Fix CS

* Fix insecure gRPC credential creation

* Test for cert file
  • Loading branch information
tidal authored Feb 2, 2022
1 parent 7335c41 commit d2a9023
Show file tree
Hide file tree
Showing 33 changed files with 1,901 additions and 288 deletions.
16 changes: 16 additions & 0 deletions src/Contrib/Otlp/ExporterTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Contrib\Otlp;

use OpenTelemetry\SDK\Behavior\LogsMessagesTrait;
use OpenTelemetry\SDK\Common\Environment\EnvironmentVariablesTrait;
use OpenTelemetry\SDK\Trace\Behavior\UsesSpanConverterTrait;

trait ExporterTrait
{
use EnvironmentVariablesTrait;
use LogsMessagesTrait;
use UsesSpanConverterTrait;
}
73 changes: 27 additions & 46 deletions src/Contrib/OtlpGrpc/Exporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,29 @@

use grpc;
use Grpc\ChannelCredentials;
use InvalidArgumentException;
use OpenTelemetry\Contrib\Otlp\ExporterTrait;
use OpenTelemetry\Contrib\Otlp\SpanConverter;
use Opentelemetry\Proto\Collector\Trace\V1\ExportTraceServiceRequest;
use Opentelemetry\Proto\Collector\Trace\V1\TraceServiceClient;
use OpenTelemetry\SDK\Behavior\LogsMessagesTrait;
use OpenTelemetry\SDK\EnvironmentVariablesTrait;
use OpenTelemetry\SDK\Common\Environment\KnownValues as Values;
use OpenTelemetry\SDK\Common\Environment\Resolver as EnvResolver;
use OpenTelemetry\SDK\Common\Environment\Variables as Env;
use OpenTelemetry\SDK\Trace\Behavior\SpanExporterTrait;
use OpenTelemetry\SDK\Trace\Behavior\UsesSpanConverterTrait;
use OpenTelemetry\SDK\Trace\SpanExporterInterface;

class Exporter implements SpanExporterInterface
{
use EnvironmentVariablesTrait;
use LogsMessagesTrait;
use ExporterTrait;
use SpanExporterTrait;
use UsesSpanConverterTrait;

// @todo: Please, check if this code is needed. It creates an error in phpstan, since it's not used
// private $protocol;

private bool $insecure;

private string $certificateFile;
private string $certificateFile = '';

private bool $compression;
private string $compression;

private int $timeout;

Expand All @@ -50,25 +48,25 @@ public function __construct(
int $timeout = 10,
TraceServiceClient $client = null
) {

// Set default values based on presence of env variable
// @todo: Please, check if this code is needed. It creates an error in phpstan, since it's not used
// $this->protocol = getenv('OTEL_EXPORTER_OTLP_PROTOCOL') ?: 'grpc'; // I guess this is redundant?
$this->insecure = $this->getBooleanFromEnvironment('OTEL_EXPORTER_OTLP_INSECURE', $insecure);
$this->certificateFile = $this->getStringFromEnvironment('OTEL_EXPORTER_OTLP_CERTIFICATE', $certificateFile);
$this->compression = $this->getBooleanFromEnvironment('OTEL_EXPORTER_OTLP_COMPRESSION', $compression);
$this->timeout = $this->getIntFromEnvironment('OTEL_EXPORTER_OTLP_TIMEOUT', $timeout);
$this->insecure = $this->getBooleanFromEnvironment(Env::OTEL_EXPORTER_OTLP_INSECURE, $insecure);
if (!empty($certificateFile) || EnvResolver::hasVariable(Env::OTEL_EXPORTER_OTLP_CERTIFICATE)) {
$this->certificateFile = $this->getStringFromEnvironment(Env::OTEL_EXPORTER_OTLP_CERTIFICATE, $certificateFile);
}
$this->compression = $this->getEnumFromEnvironment(
Env::OTEL_EXPORTER_OTLP_COMPRESSION,
$compression ? Values::VALUE_GZIP : Values::VALUE_NONE
);
$this->timeout = $this->getIntFromEnvironment(Env::OTEL_EXPORTER_OTLP_TIMEOUT, $timeout);

$this->setSpanConverter(new SpanConverter());

$this->metadata = $this->metadataFromHeaders(
$this->getStringFromEnvironment('OTEL_EXPORTER_OTLP_HEADERS', $headers)
);
$this->metadata = $this->getMapFromEnvironment(Env::OTEL_EXPORTER_OTLP_HEADERS, $headers);

$opts = $this->getClientOptions();

$this->client = $client ?? new TraceServiceClient(
$this->getStringFromEnvironment('OTEL_EXPORTER_OTLP_ENDPOINT', $endpointURL),
$this->getStringFromEnvironment(Env::OTEL_EXPORTER_OTLP_ENDPOINT, $endpointURL),
$opts
);
}
Expand All @@ -85,7 +83,7 @@ public function getClientOptions(): array
'credentials' => $this->getCredentials(),
];

if ($this->compression) {
if ($this->compression === Values::VALUE_GZIP) {
// gzip is the only specified compression method for now
$opts['grpc.default_compression_algorithm'] = 2;
}
Expand Down Expand Up @@ -139,38 +137,16 @@ protected function doExport(iterable $spans): int

public function setHeader($key, $value): void
{
$this->metadata[$key] = [$value];
// metadata is supposed to be key-value pairs
// @see https://grpc.io/docs/what-is-grpc/core-concepts/#metadata
$this->metadata[$key] = $value;
}

public function getHeaders(): array
{
return $this->metadata;
}

public function metadataFromHeaders($headers): array
{
if (is_array($headers)) {
throw new InvalidArgumentException('Configuring Headers Via');
}

if (strlen($headers) <= 0) {
return [];
}

$pairs = explode(',', $headers);

$metadata = [];
foreach ($pairs as $pair) {
if (!strpos($pair, '=')) {
continue;
}
[$key, $value] = explode('=', $pair, 2);
$metadata[$key] = [$value];
}

return $metadata;
}

private function getCredentials(): ?ChannelCredentials
{
if (!$this->insecure) {
Expand All @@ -182,6 +158,11 @@ private function getCredentials(): ?ChannelCredentials
return Grpc\ChannelCredentials::createInsecure();
}

public function getCertificateFile(): string
{
return $this->certificateFile;
}

public static function fromConnectionString(string $endpointUrl = null, string $name = null, $args = null): Exporter
{
return is_string($endpointUrl) ? new Exporter($endpointUrl) : new Exporter();
Expand Down
53 changes: 13 additions & 40 deletions src/Contrib/OtlpHttp/Exporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
use Http\Discovery\Psr17FactoryDiscovery;
use InvalidArgumentException;
use Nyholm\Dsn\DsnParser;
use OpenTelemetry\Contrib\Otlp\ExporterTrait;
use OpenTelemetry\Contrib\Otlp\SpanConverter;
use Opentelemetry\Proto\Collector\Trace\V1\ExportTraceServiceRequest;
use OpenTelemetry\SDK\EnvironmentVariablesTrait;
use OpenTelemetry\SDK\Common\Environment\Variables as Env;
use OpenTelemetry\SDK\Trace\Behavior\HttpSpanExporterTrait;
use OpenTelemetry\SDK\Trace\Behavior\UsesSpanConverterTrait;
use OpenTelemetry\SDK\Trace\SpanExporterInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
Expand All @@ -21,15 +21,17 @@

class Exporter implements SpanExporterInterface
{
use EnvironmentVariablesTrait;
use UsesSpanConverterTrait;
use ExporterTrait;
use HttpSpanExporterTrait;

private const REQUEST_METHOD = 'POST';
private const HEADER_CONTENT_TYPE = 'content-type';
private const HEADER_CONTENT_ENCODING = 'Content-Encoding';
private const VALUE_CONTENT_TYPE = 'application/x-protobuf';
private const VALUE_CONTENT_ENCODING = 'gzip';
private const DEFAULT_ENDPOINT = 'https://localhost:4318/v1/traces';
private const DEFAULT_COMPRESSION = 'none';
private const OTLP_PROTOCOL = 'http/protobuf';

// @todo: Please, check if this code is needed. It creates an error in phpstan, since it's not used
// private string $insecure;
Expand All @@ -56,23 +58,21 @@ public function __construct(
// Set default values based on presence of env variable
$this->setEndpointUrl(
$this->validateEndpoint(
$this->getStringFromEnvironment('OTEL_EXPORTER_OTLP_ENDPOINT', 'https://localhost:4318/v1/traces')
$this->getStringFromEnvironment(Env::OTEL_EXPORTER_OTLP_ENDPOINT, self::DEFAULT_ENDPOINT)
)
);
// @todo: Please, check if this code is needed. It creates an error in phpstan, since it's not used
// $this->certificateFile = getenv('OTEL_EXPORTER_OTLP_CERTIFICATE') ?: 'none';
$this->headers = $this->processHeaders($this->getStringFromEnvironment('OTEL_EXPORTER_OTLP_HEADERS', ''));
$this->compression = $this->getStringFromEnvironment('OTEL_EXPORTER_OTLP_COMPRESSION', 'none');
// @todo: Please, check if this code is needed. It creates an error in phpstan, since it's not used
// $this->timeout =(int) getenv('OTEL_EXPORTER_OTLP_TIMEOUT') ?: 10;
$this->headers = $this->getMapFromEnvironment(Env::OTEL_EXPORTER_OTLP_HEADERS);
$this->compression = $this->getEnumFromEnvironment(Env::OTEL_EXPORTER_OTLP_COMPRESSION, self::DEFAULT_COMPRESSION);

$this->setClient($client);
$this->setRequestFactory($requestFactory);
$this->setStreamFactory($streamFactory);
$this->setSpanConverter($spanConverter ?? new SpanConverter());

if ((getenv('OTEL_EXPORTER_OTLP_PROTOCOL') ?: 'http/protobuf') !== 'http/protobuf') {
throw new InvalidArgumentException('Invalid OTLP Protocol Specified');
$protocol = $this->getEnumFromEnvironment(Env::OTEL_EXPORTER_OTLP_PROTOCOL, self::OTLP_PROTOCOL);

if ($protocol !== self::OTLP_PROTOCOL) {
throw new InvalidArgumentException(sprintf('Invalid OTLP Protocol "%s" specified', $protocol));
}
}

Expand Down Expand Up @@ -109,33 +109,6 @@ protected function marshallRequest(iterable $spans): RequestInterface
return $request;
}

/**
* processHeaders converts comma separated headers into an array
*/
public function processHeaders(?string $headers): array
{
if (empty($headers)) {
return [];
}

$pairs = explode(',', $headers);

$metadata = [];
foreach ($pairs as $pair) {
$kv = explode('=', $pair, 2);

if (count($kv) !== 2) {
throw new InvalidArgumentException('Invalid headers passed');
}

[$key, $value] = $kv;

$metadata[$key] = $value;
}

return $metadata;
}

/**
* validateEndpoint does two functions, firstly checks that the endpoint is valid
* secondly it appends https:// and /v1/traces should they have been omitted
Expand Down
127 changes: 127 additions & 0 deletions src/SDK/Common/Environment/Accessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\SDK\Common\Environment;

use OpenTelemetry\SDK\Common\Environment\Parser\BooleanParser;
use OpenTelemetry\SDK\Common\Environment\Parser\ListParser;
use OpenTelemetry\SDK\Common\Environment\Parser\MapParser;
use OpenTelemetry\SDK\Common\Environment\Parser\RatioParser;
use UnexpectedValueException;

class Accessor
{
public static function getString(string $variableName, string $default = null): string
{
return (string) self::validateVariableValue(
Resolver::resolveValue(
self::validateVariableType($variableName, VariableTypes::STRING),
$default
)
);
}

public static function getBool(string $variableName, string $default = null): bool
{
return BooleanParser::parse(
self::validateVariableValue(
Resolver::resolveValue(
self::validateVariableType($variableName, VariableTypes::BOOL),
$default
)
)
);
}

public static function getInt(string $variableName, string $default = null): int
{
return (int) self::validateVariableValue(
Resolver::resolveValue(
self::validateVariableType($variableName, VariableTypes::INTEGER),
$default
),
FILTER_VALIDATE_INT
);
}

public static function getRatio(string $variableName, string $default = null): float
{
return RatioParser::parse(
self::validateVariableValue(
Resolver::resolveValue(
self::validateVariableType($variableName, VariableTypes::RATIO),
$default
)
)
);
}

public static function getEnum(string $variableName, string $default = null): string
{
return (string) self::validateVariableValue(
Resolver::resolveValue(
self::validateVariableType($variableName, VariableTypes::ENUM),
$default
)
);
}

public static function getList(string $variableName, string $default = null): array
{
return ListParser::parse(
Resolver::resolveValue(
self::validateVariableType($variableName, VariableTypes::LIST),
$default
)
);
}

public static function getMap(string $variableName, string $default = null): array
{
return MapParser::parse(
Resolver::resolveValue(
self::validateVariableType($variableName, VariableTypes::MAP),
$default
)
);
}

public static function getMixed(string $variableName, string $default = null, ?string $type = null)
{
return self::validateVariableValue(
Resolver::resolveValue(
$variableName,
$default
)
);
}

private static function validateVariableType(string $variableName, string $type): string
{
$variableType = Resolver::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;
}
}
Loading

0 comments on commit d2a9023

Please sign in to comment.